#include <QMessageBox>
#include <QPainter>

#include <cfloat>
#include <math.h>

#include "QFitsWidget3D_GL.h"
#include "QFitsMainWindow.h"
#include "QFitsMainView.h"
#include "QFitsSingleBuffer.h"
#include "QFitsWidget2D.h"
#include "QFitsView2D.h"
#include "QFitsCubeSpectrum.h"
#include "QFitsGlobal.h"
#include "QFitsViewingTools.h"
#include "fits.h"
#include "qtdpuser.h"
#include "lut.h"

/*
 * Code from SGI
 */
#define SPHEREWIRE	0
#define CUBEWIRE	1
#define BOXWIRE		2
#define TORUSWIRE	3
#define CYLINDERWIRE	4
#define ICOSAWIRE	5
#define OCTAWIRE	6
#define TETRAWIRE	7
#define DODECAWIRE	8
#define CONEWIRE	9
#define SPHERESOLID	10
#define CUBESOLID	11
#define BOXSOLID	12
#define TORUSSOLID	13
#define CYLINDERSOLID	14
#define ICOSASOLID	15
#define OCTASOLID	16
#define TETRASOLID	17
#define DODECASOLID	18
#define CONESOLID	19

/*	structure for each geometric object	*/
typedef struct model {
    GLuint list;	/*  display list to render object   */
    struct model *ptr;	/*  pointer to next object	*/
    int numParam;	/*  # of parameters		*/
    GLdouble *params;	/*  array with parameters	*/
} MODEL, *MODELPTR;

/*	array of linked lists--used to keep track of display lists
 *	for each different type of geometric object.
 */
static MODELPTR lists[25] = {
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL
};

GLuint findList (int index, GLdouble *paramArray, int size);
int compareParams (GLdouble *oneArray, GLdouble *twoArray, int size);
GLuint makeModelPtr (int index, GLdouble *sizeArray, int count);

static void drawbox(GLdouble, GLdouble, GLdouble,
    GLdouble, GLdouble, GLdouble, GLenum);

/*	linked lists--display lists for each different
 *	type of geometric objects.  The linked list is
 *	searched, until an object of the requested
 *	size is found.  If no geometric object of that size
 *	has been previously made, a new one is created.
 */
GLuint findList (int index, GLdouble *paramArray, int size)
{
    MODELPTR endList;
    int found = 0;

    endList = lists[index];
    while (endList != NULL) {
    if (compareParams (endList->params, paramArray, size))
        return (endList->list);
    endList = endList->ptr;
    }
/*  if not found, return 0 and calling routine should
 *  make a new list
 */
    return (0);
}

int compareParams (GLdouble *oneArray, GLdouble *twoArray, int size)
{
    int i;
    int matches = 1;

    for (i = 0; (i < size) && matches; i++) {
    if (*oneArray++ != *twoArray++)
        matches = 0;
    }
    return (matches);
}

GLuint makeModelPtr (int index, GLdouble *sizeArray, int count)
{
    MODELPTR newModel;

    newModel = (MODELPTR) malloc (sizeof (MODEL));
    newModel->list = glGenLists (1);
    newModel->numParam = count;
    newModel->params = sizeArray;
    newModel->ptr = lists[index];
    lists[index] = newModel;

    return (newModel->list);
}

/* drawbox:
 *
 * draws a rectangular box with the given x, y, and z ranges.
 * The box is axis-aligned.
 */
void drawbox(GLdouble x0, GLdouble x1, GLdouble y0, GLdouble y1,
    GLdouble z0, GLdouble z1, GLenum type)
{
    static GLdouble n[6][3] = {
    {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0},
    {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0}
    };
    static GLint faces[6][4] = {
    { 0, 1, 2, 3 }, { 3, 2, 6, 7 }, { 7, 6, 5, 4 },
    { 4, 5, 1, 0 }, { 5, 6, 2, 1 }, { 7, 4, 0, 3 }
    };
    GLdouble v[8][3], tmp;
    GLint i;

    if (x0 > x1) {
    tmp = x0; x0 = x1; x1 = tmp;
    }
    if (y0 > y1) {
    tmp = y0; y0 = y1; y1 = tmp;
    }
    if (z0 > z1) {
    tmp = z0; z0 = z1; z1 = tmp;
    }
    v[0][0] = v[1][0] = v[2][0] = v[3][0] = x0;
    v[4][0] = v[5][0] = v[6][0] = v[7][0] = x1;
    v[0][1] = v[1][1] = v[4][1] = v[5][1] = y0;
    v[2][1] = v[3][1] = v[6][1] = v[7][1] = y1;
    v[0][2] = v[3][2] = v[4][2] = v[7][2] = z0;
    v[1][2] = v[2][2] = v[5][2] = v[6][2] = z1;

    for (i = 0; i < 6; i++) {
    glBegin(type);
    glNormal3dv(&n[i][0]);
    glVertex3dv(&v[faces[i][0]][0]);
    glNormal3dv(&n[i][0]);
    glVertex3dv(&v[faces[i][1]][0]);
    glNormal3dv(&n[i][0]);
    glVertex3dv(&v[faces[i][2]][0]);
    glNormal3dv(&n[i][0]);
    glVertex3dv(&v[faces[i][3]][0]);
    glEnd();
    }
}

/*  Render wire frame or solid cube.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireCube (GLdouble size)
{
    GLdouble *sizeArray;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = size;
    displayList = findList (CUBEWIRE, sizeArray, 1);

    if (displayList == 0) {
    glNewList(makeModelPtr (CUBEWIRE, sizeArray, 1),
        GL_COMPILE_AND_EXECUTE);
        drawbox(-size/2., size/2., -size/2., size/2.,
        -size/2., size/2., GL_LINE_LOOP);
    glEndList();
    }
    else {
    glCallList(displayList);
    free (sizeArray);
    }
}

void auxSolidCube (GLdouble size)
{
    GLdouble *sizeArray;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = size;
    displayList = findList (CUBESOLID, sizeArray, 1);

    if (displayList == 0) {
    glNewList(makeModelPtr (CUBESOLID, sizeArray, 1),
        GL_COMPILE_AND_EXECUTE);
        drawbox(-size/2., size/2., -size/2., size/2.,
        -size/2., size/2., GL_QUADS);
    glEndList();
    }
    else {
    glCallList(displayList);
    free (sizeArray);
    }
}


static void
drawBox(GLfloat size, GLenum type)
{
  static GLfloat n[6][3] =
  {
    {-1.0, 0.0, 0.0},
    {0.0, 1.0, 0.0},
    {1.0, 0.0, 0.0},
    {0.0, -1.0, 0.0},
    {0.0, 0.0, 1.0},
    {0.0, 0.0, -1.0}
  };
  static GLint faces[6][4] =
  {
    {0, 1, 2, 3},
    {3, 2, 6, 7},
    {7, 6, 5, 4},
    {4, 5, 1, 0},
    {5, 6, 2, 1},
    {7, 4, 0, 3}
  };
  GLfloat v[8][3];
  GLint i;

  v[0][0] = v[1][0] = v[2][0] = v[3][0] = -size / 2;
  v[4][0] = v[5][0] = v[6][0] = v[7][0] = size / 2;
  v[0][1] = v[1][1] = v[4][1] = v[5][1] = -size / 2;
  v[2][1] = v[3][1] = v[6][1] = v[7][1] = size / 2;
  v[0][2] = v[3][2] = v[4][2] = v[7][2] = -size / 2;
  v[1][2] = v[2][2] = v[5][2] = v[6][2] = size / 2;

  for (i = 5; i >= 0; i--) {
    glBegin(type);
    glNormal3fv(&n[i][0]);
    glVertex3fv(&v[faces[i][0]][0]);
    glVertex3fv(&v[faces[i][1]][0]);
    glVertex3fv(&v[faces[i][2]][0]);
    glVertex3fv(&v[faces[i][3]][0]);
    glEnd();
  }
}

/* CENTRY */
void
glutWireCube(GLdouble size)
{
  drawBox(size, GL_LINE_LOOP);
}

void
glutSolidCube(GLdouble size)
{
  drawBox(size, GL_QUADS);
}


//------------------------------------------------------------------------------
//         QFitsWidget3D
//------------------------------------------------------------------------------
QFitsWidget3D::QFitsWidget3D(QFitsSingleBuffer *buffer) : QFitsBaseWidget(buffer) {
    cubeViewer = new QFitsView3D(this);

connect(fitsMainWindow->mytoolbar, SIGNAL(updateZRange3D(double, double)),
        cubeViewer, SLOT(updateZRange3D(double, double)));
connect(fitsMainWindow->viewingtools->total, SIGNAL(setSlowAndNice()),
        cubeViewer, SLOT(paintSlowAndNice()));
connect(fitsMainWindow->viewingtools->total, SIGNAL(setQuickAndDirty()),
        cubeViewer, SLOT(paintQuickAndDirty()));

connect(cubeViewer, SIGNAL(decRot()),
        buffer->getQFitsWidget2D(), SLOT(decRotation()));

    // Alex: kann das anders gelöst werden? wenn buffersClicked() aufgerufen wird
    // oder in view3D gewechselt wird erst updateZoom(int) aufrufen?
//    connect(buffer->getQFitsWidget2D()->viewer, SIGNAL(notify3DzoomChanged(int)),
//            cubeViewer, SLOT(updateZoom(int)));
}

QFitsWidget3D::~QFitsWidget3D() {
    if (cubeViewer != NULL) {
        delete cubeViewer;
        cubeViewer = NULL;
    }
}

void QFitsWidget3D::setZoom(int z) {
    cubeViewer->setZoom(z);
}

void QFitsWidget3D::updateScaling() {
    cubeViewer->updateScaling();
}

void QFitsWidget3D::reset() {
    cubeViewer->ResetCamera();
}

void QFitsWidget3D::newData3D() {
    cubeViewer->newData();
}

void QFitsWidget3D::setupColours()
{
    cubeViewer->updateColourtable();
}

void QFitsWidget3D::setImageCenter(int x, int y) {
    cubeViewer->setImageCenter(x, y);
}

void QFitsWidget3D::resizeEvent(QResizeEvent *r) {
    cubeViewer->setGeometry(0, 0, r->size().width(), r->size().height());
    QFitsBaseWidget::resizeEvent(r); // ???
}

// handled now by QFitsBaseWidget!
//void QFitsWidget3D::calculateScaling(Fits *fits) {
//    return myParent->getState()->calculateScaling(fits);
//}
//------------------------------------------------------------------------------
//         QFitsView3D
//------------------------------------------------------------------------------
QFitsView3D::QFitsView3D(QFitsWidget3D *parent) :
                                      QGLWidget(parent)
{
    xRot = yRot = zRot = 0.0;		// default object rotation
	xRot = yRot = 45.;
    scale = 1.25;			// default object scale
		
		setMouseTracking(FALSE);
     myParent = parent;
    setFocusPolicy(Qt::StrongFocus);
    initialized = false;
    scalingDirty = true;
    colourtableDirty = true;

    connect(this, SIGNAL(flipX()),
            fitsMainWindow->mytoolbar->buttonflip, SLOT(toggle()));
    connect(this, SIGNAL(flipY()),
            fitsMainWindow->mytoolbar->buttonflop, SLOT(toggle()));
}


QFitsView3D::~QFitsView3D()
{
	makeCurrent();
}

void QFitsView3D::updateScaling() {
    scalingDirty = true;
    if (this->isVisible()) {
        //updateToolbar(currentBuffer);
        // wenn mal average angezeigt wird -->uncomment
        //main_view->plotwidget->plotter->setYRange(myParent->getMyBuffer()->min, myParent->getMyBuffer()->max);

       newData();
    }
}

void QFitsView3D::paintQuickAndDirty() {
}

void QFitsView3D::paintSlowAndNice() {
}

void QFitsView3D::mousePressEvent(QMouseEvent* event)
{
    // middle button press resets camera
    switch(event->button())
    {
        case Qt::LeftButton:
            // change colormap
//            myParent->getMainView()->setCurrentBufferFromWidget(myParent->getMyBuffer());
            paintQuickAndDirty();
            break;
        case Qt::MidButton:
            ResetCamera();
            break;
        case Qt::RightButton:
            // rotate
                        mousex = event->x();
                        mousey = event->y();
           break;
        default:
            break;
    }
}

void QFitsView3D::mouseMoveEvent(QMouseEvent* event)
{
    if (!hasFocus()) {
        setFocus();
    }
    if (int(event->buttons()) == Qt::RightButton) {
        // moved + right button
        yRot = yRot + (mousex - event->x()) * 360. / (float)width();
        xRot = xRot - (mousey - event->y()) * 180. / (float)height();
        mousex = event->x();
        mousey = event->y();
		if (xRot < 0.) xRot += 360.;
		else if (xRot >= 360.) xRot -= 360.;
		if (yRot < 0.) yRot += 360.;
		else if (yRot >= 360.) yRot -= 360.;
		updateGL();
    } else if (int(event->buttons()) == Qt::LeftButton) {
        // moved + left button: change colormap
//        if (myParent->hasMouseTracking()) {
            myParent->getMyBuffer()->setBrightnessContrast((int)((float)(event->x()) / (float)width() * 1000.),
                                                                      (int)((float)(event->y()) / (float)height() * 1000.));
//        }
    } else if (int(event->buttons()) == Qt::MidButton) {
        scale = (float)event->y() / (float)height() * 10.;
		updateGL();
		}
}

void QFitsView3D::mouseReleaseEvent(QMouseEvent* event)
{
    // invoke appropriate vtk event (Alex: switched Left and Right event)
    switch(event->button())
    {
        case Qt::LeftButton:
            paintSlowAndNice();
            break;
        case Qt::MidButton:
            break;
        case Qt::RightButton:
            break;
        default:
            break;
    }
}

void QFitsView3D::calcZoomedRanges(int *x1, int *x2, int *y1, int *y2, int *z1, int *z2) {
    QFitsSingleBuffer *sb = myParent->getMyBuffer();
    if (sb->getFirstView3D()) {
        sb->setFirstView3D(false);

        if ((sb->getWidthVisible() == -1) && (sb->getHeightVisible() == -1)) {
            sb->setWidthVisible(sb->getNaxis1());
            sb->setHeightVisible(sb->getNaxis2());
        }

        // this buffer has been viewed the first time in 3D or has been loaded directly from command line
        // if the total spatial area is bigger than 10'000 pixels scale it down
        int area = sb->getWidthVisible() * sb->getHeightVisible();
        if (area > 10000) {
            // set area to max. 10000 pixels
            sb->setWidthVisible(sb->getWidthVisible() * 100 / sqrt(area));
            sb->setHeightVisible(sb->getHeightVisible() * 100 / sqrt(area));
        }

        if ((!sb->getWasCompletelyVisible()) || (area > 10000)) {
            // calculate appropriate zoom3d-factor here
            sb->setZoom3D(zoomIndex);
            int ww = sb->getWidthVisible();
            int hh = sb->getHeightVisible();
            while ((ww < sb->getNaxis1()) || (hh < sb->getNaxis2())) {
                sb->setZoom3D(sb->getZoom3D());
                ww *= 2;
                hh *= 2;
            }
        } else
            sb->setZoom3D(zoomIndex);

        // calc effective width and height based on zoom factor
        if ((sb->getWasCompletelyVisible()) || (area > 10000)) {
            calcZoomedVisibleArea();
        }
    }

    *x1 = sb->getXcenter() - sb->getWidthVisible() / 2;
    if (*x1 < 1) *x1 = 1;
    *x2 = *x1 + sb->getWidthVisible() - 1;
    if (*x2 > sb->getNaxis1()) {
        *x2 = sb->getNaxis1();
        *x1 = *x2 - sb->getWidthVisible() + 1;
        if (*x1 < 1) *x1 = 1;
    }

    *y1 = sb->getYcenter() - sb->getHeightVisible() / 2;
    if (*y1 < 1) *y1 = 1;
    *y2 = *y1 + sb->getHeightVisible() - 1;
    if (*y2 > sb->getNaxis2()) {
        *y2 = sb->getNaxis2();
        *y1 = *y2 - sb->getHeightVisible() + 1;
        if (*y1 < 1) *y1 = 1;
    }


    *z1 = sb->getCubeCenter() - sb->getLineWidth();
    *z2 = sb->getCubeCenter() + sb->getLineWidth();

    if (*z1 < 1)
        *z1 = 1;
    if (*z2 > sb->getNaxis3())
        *z2 = sb->getNaxis3();

    sb->setXcenter(*x1+(int)((*x2-*x1)/2.+.5));
    sb->setYcenter(*y1+(int)((*y2-*y1)/2.+.5));
    sb->setWidthVisible(*x2-*x1+1);
    sb->setHeightVisible(*y2-*y1+1);

    fitsMainWindow->setTotalCompletelyVisible(sb->getZoom3D() == zoomIndex);

    fitsMainWindow->updateTotalVisibleRect(sb->getXcenter(),
                                           sb->getYcenter(),
                                           sb->getWidthVisible(),
                                           sb->getHeightVisible());

    // wenn man in View3D-modus im spectrum-fenster "C" drückt ohne die Maus zu
    // bewegen, wird ein subspectrum von breite 3 ausgewählt, dies wird erst
    // angezeigt, wenn die maus bewegt wird... update() oder repaint() wird
    // nicht ausgeführt
    fitsMainWindow->spectrum->update();
//    fitsMainWindow->spectrum->repaint();
}

void QFitsView3D::newData() {
    myParent->getMyBuffer()->setDirty(true);

    updateGL();
}

void QFitsView3D::newData2() {
    myParent->getMyBuffer()->setDirty(true);
    scalingDirty = true;

    updateGL();
}

void QFitsView3D::updateZRange3D(double min, double max) {
    zmin = min;
    zmax = max;
}

void QFitsView3D::updateData() {
    QFitsSingleBuffer *sb = myParent->getMyBuffer();
    if (isVisible()) {
        QReadLocker locker(&buffersLock);
        // undo any possibly applied rotation and flipping
        if (sb->getFlipX()) {
            emit flipX();
        }
        if (sb->getFlipY()) {
            emit flipY();
        }
        while (sb->getRotation() != 0) {
            emit decRot();
        }

        // calculate dimensions of desired subcube
        int x1, x2, y1, y2, z1, z2;
        calcZoomedRanges(&x1, &x2, &y1, &y2, &z1, &z2);

        // update ranges of axes
        double ranges[6];
        ranges[0] = x1;
        ranges[1] = x2;
        ranges[2] = y1;
        ranges[3] = y2;
        // flip z-axis for correct display (longest wave length is furthest)
        ranges[4] = zmax;
        ranges[5] = zmin;

        // take into account WCS if available
        double crpix, crval, cdelt;

        if (sb->getFitsData()->GetFloatKey("CRPIX1", &crpix)) {
            if (sb->getFitsData()->GetFloatKey("CRVAL1", &crval)) {
                if (sb->getFitsData()->GetFloatKey("CDELT1", &cdelt)) {
                    ranges[0] = crval + cdelt*(ranges[0] - crpix);
                    ranges[1] = crval + cdelt*(ranges[1] - crpix);
                }
            }
        }

        if (sb->getFitsData()->GetFloatKey("CRPIX2", &crpix)) {
            if (sb->getFitsData()->GetFloatKey("CRVAL2", &crval)) {
                if (sb->getFitsData()->GetFloatKey("CDELT2", &cdelt)) {
                    ranges[2] = crval + cdelt*(ranges[2] - crpix);
                    ranges[3] = crval + cdelt*(ranges[3] - crpix);
                }
            }
        }

        // ranges[4] and ranges[5] don't have to be converted here, this is
        // already done in Toolbar.cpp

        dp_debug("RANGES: %d %d   %d %d   %d %d", ranges[0], ranges[1], ranges[2], ranges[3], ranges[4], ranges[5]);
//@        axes->SetRanges(ranges);

        if (scalingDirty) {
            // extract desired subcube
            QReadLocker locker(&buffersLock);
//            dpusermutex->lock();
            sb->getFitsData()->extractRange(subcube, x1, x2, y1, y2, z1, z2);
//            dpusermutex->unlock();

            // converts Fits from double to unsigned char (if it is double)
            myParent->calculateScaling(&subcube);

            fitsMainWindow->showMinMax(sb);
//            emit newMinMax(sb->min, sb->max);

            scalingDirty = false;
        } else {
            // extract desired subcube
            Fits tmp;
//            dpusermutex->lock();
            sb->getFitsData()->extractRange(tmp, x1, x2, y1, y2, z1, z2);
//            dpusermutex->unlock();

            // convert Fits eventually from double to unsigned char (if it is double)
            tmp.normub(subcube, sb->getMin(), sb->getMax());
        }

        newColourtable();

        // flip data in z-axis for correct display (longest wave length is furthest)
        subcube.flip(3);

        sb->setDirty(false);

        fitsMainWindow->setMagnifierCenterPos(0, 0);
    } else {
        sb->setDirty(true);
    }
}

void QFitsView3D::paintGL()
{qFitsDebug("\tQFitsView3D::paintEvent\t[%p]", this);
	if (isVisible()) {
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glLoadIdentity();
    glTranslatef( 0.0, 0.0, -10.0 );
    glScalef( scale, scale, scale );

    glRotatef( xRot, 1.0, 0.0, 0.0 ); 
    glRotatef( yRot, 0.0, 1.0, 0.0 ); 
		
    QFitsSingleBuffer *sb = myParent->getMyBuffer();
    if (sb->getFitsData()) {
        // if the dimensions of the subcube to be displayed changed,
        // extract the corresponding subcube and center it
        bool wasUpdated = false;
        if (sb->getDirty() || scalingDirty || colourtableDirty) {
            updateData();
            wasUpdated = true;
        }
		
		glEnable(GL_DEPTH_TEST);
		glCullFace(GL_BACK);
		glEnable(GL_CULL_FACE);
		
		int x, y, z;
		float sx, sy, sz;
		unsigned char value;
		
		sx = subcube.Naxis(1) / 2 * .02;
		sy = subcube.Naxis(2) / 2 * .02;
		sz = subcube.Naxis(3) / 2 * .02;

// This is ugly spaghetti-code. Take the 16 regions and draw the cubes
// from back to front
		if (yRot >= 0. && yRot < 90.) {
			if (xRot >= 0. && xRot < 90.) {
				glTranslatef(sx, -sy, -sz);
					
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., .02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 90. && xRot < 180.) {
				glTranslatef(-sx, -sy, sz);
				
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., .02, .02 * subcube.Naxis(3));
					}
					glTranslatef(.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 180. && xRot < 270.) {
				glTranslatef(-sx, sy, sz);
				
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., -.02, .02 * subcube.Naxis(3));
					}
					glTranslatef(.02, .02 * subcube.Naxis(2), 0.);
				}
			} else {
				glTranslatef(sx, sy, -sz);
				
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., -.02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, .02 * subcube.Naxis(2), 0.);
				}
			}
		} else if (yRot >= 90. && yRot < 180.) {
			if (xRot >= 0. && xRot < 90.) {
				glTranslatef(sx, -sy, sz);
					
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., .02, .02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 0. && xRot < 180.) {
				glTranslatef(-sx, -sy, -sz);
					
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., .02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 180. && xRot < 270.) {
				glTranslatef(-sx, sy, -sz);
					
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., -.02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(.02, .02 * subcube.Naxis(2), 0.);
				}
			} else {
				glTranslatef(sx, sy, sz);
					
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., -.02, .02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, .02 * subcube.Naxis(2), 0.);
				}
			}
		} else if (yRot >= 180. && yRot < 270.) {
			if (xRot >= 0. && xRot < 90.) {
				glTranslatef(-sx, -sy, sz);
				
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., .02, .02 * subcube.Naxis(3));
					}
					glTranslatef(.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 90. && xRot < 180.) {
				glTranslatef(sx, -sy, -sz);
					
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., .02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 180. && xRot < 270.) {
				glTranslatef(sx, sy, -sz);
				
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., -.02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, .02 * subcube.Naxis(2), 0.);
				}
			} else {
				glTranslatef(-sx, sy, sz);
				
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., -.02, .02 * subcube.Naxis(3));
					}
					glTranslatef(.02, .02 * subcube.Naxis(2), 0.);
				}
			}
		} else if (yRot >= 270. && yRot < 360.) {
			if (xRot >= 0. && xRot < 90.) {
				glTranslatef(-sx, -sy, -sz);
					
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., .02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 90. && xRot < 180.) {
				glTranslatef(sx, -sy, sz);
					
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = 0; y < subcube.Naxis(2); y++) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., .02, .02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, -.02 * subcube.Naxis(2), 0.);
				}
			} else if (xRot >= 180. && xRot < 270.) {
				glTranslatef(sx, sy, sz);
					
				for (x = subcube.Naxis(1) - 1; x >= 0; x--) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = subcube.Naxis(3) - 1; z >= 0; z--) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., -.02);
						}
						glTranslatef(0., -.02, .02 * subcube.Naxis(3));
					}
					glTranslatef(-.02, .02 * subcube.Naxis(2), 0.);
				}
			} else {
				glTranslatef(-sx, sy, -sz);
					
				for (x = 0; x < subcube.Naxis(1); x++) {
					for (y = subcube.Naxis(2) - 1; y >= 0; y--) {
						for (z = 0; z < subcube.Naxis(3); z++) {
							value = subcube.i1data[subcube.C_I(x, y, z)];
							glColor4ub(glcolors[value][0], glcolors[value][1], glcolors[value][2], value/10);
							glutSolidCube(0.02);
							glTranslatef(0., 0., .02);
						}
						glTranslatef(0., -.02, -.02 * subcube.Naxis(3));
					}
					glTranslatef(.02, .02 * subcube.Naxis(2), 0.);
				}
			}
		}
//		glDisable(GL_DEPTH_TEST);
		glDisable(GL_CULL_FACE);

		glLoadIdentity();
		glTranslatef( 0.0, 0.0, -10.0 );
		glScalef( scale, scale, scale );

		glRotatef( xRot, 1.0, 0.0, 0.0 ); 
		glRotatef( yRot, 0.0, 1.0, 0.0 ); 
		glBegin(GL_LINES);
		glColor3f(1., 1., 1.);
		glLineWidth(.05);
		glVertex3f(-sx, -sy, -sz);
		glVertex3f(sx, -sy, -sz);
		glVertex3f(-sx, -sy, -sz);
		glVertex3f(-sx, sy, -sz);
		glVertex3f(-sx, -sy, -sz);
		glVertex3f(-sx, -sy, sz);

		renderText(sx, -sy, -sz, "x->");
		glEnd();

    }
	}
}

void QFitsView3D::keyPressEvent( QKeyEvent *e ) {
    fitsMainWindow->main_view->keyPressEvent(e);
}

// this code was copied from vtkRenderer::ResetCamera()
// (a direct call to rendererer->ResetCamera() produced a segmentation fault...)
void QFitsView3D::ResetCamera()
{
	xRot = yRot = 45.;
	zRot = 0.;
	scale = 1.25;			// default object scale
}

// slot: called when colormap is inverted or changed
void QFitsView3D::updateColourtable() {
    colourtableDirty = true;
    newColourtable();
}

void QFitsView3D::newColourtable() {
	if (isVisible() && colourtableDirty) {
		QRgb color;
		int i;

		for (i = 0; i < 256; i++) {
            color = myParent->getMyBuffer()->getImage()->color(i);
			glcolors[i][0] = qRed(color);
			glcolors[i][1] = qGreen(color);
			glcolors[i][2] = qBlue(color);
		}
		colourtableDirty = false;
		updateGL();
	}
}

void QFitsView3D::enterEvent(QEvent *e)
{
    setFocus();
    myParent->enterBuffer();
}

void QFitsView3D::leaveEvent (QEvent *e)
{
    myParent->leaveBuffer();
}

// slot: called when rectangle in TotalView is moved
void QFitsView3D::setImageCenter(int x, int y) {
    QFitsSingleBuffer *sb = myParent->getMyBuffer();
    if (isVisible()) {
        sb->setXcenter(x);
        sb->setYcenter(sb->getNaxis2() - y);

        scalingDirty = true;
        newData();
    }
}


void QFitsView3D::calcZoomedVisibleArea() {
    QFitsSingleBuffer *sb = myParent->getMyBuffer();
    sb->setZoomChangedIn3D(true);

    // change "Fit window" to 100%
    if (sb->getZoom3D() < zoomIndex) {
        sb->setZoom3D(zoomIndex);
    }

//    emit zoomChanged(sb->zoom3d);
    fitsMainWindow->setZoomText(sb->getZoom3D());

    if (sb->getZoom3D() > zoomIndex) {
        sb->setWidthVisible(sb->getNaxis1() / (sb->getZoom3D() - (zoomIndex-1)));
        sb->setHeightVisible(sb->getNaxis2() / (sb->getZoom3D() - (zoomIndex-1)));
    } else if (sb->getZoom3D() == zoomIndex) {
        sb->setWidthVisible(sb->getNaxis1());
        sb->setHeightVisible(sb->getNaxis2());
    }
}

void QFitsView3D::updateZoom(int newZoom) {
    dp_debug("QFitsView3D::updateZoom: %i", newZoom);
    QFitsSingleBuffer *sb = myParent->getMyBuffer();
//    if (sb->viewMode == ViewWidget3D) {
        if (newZoom < zoomIndex) {
            sb->setZoom3D(zoomIndex);
        } else {
            sb->setZoom3D(newZoom);
        }

        calcZoomedVisibleArea();

        // calculate new size and recalculate distance of camera
        updateData();
//@        ResetCamera(false, false, false);

        newData2();
//    }
}

// Alex: added blindly without testing...
void QFitsView3D::setZoom(int z) {
//    if (myParent->getMyBuffer()->viewMode == View3D) {
        updateZoom(z);
//    }
}
/*!
  Set up the OpenGL rendering state, and define display list
*/

void QFitsView3D::initializeGL()
{
    qglClearColor( Qt::black ); 		// Let OpenGL clear to black
    glShadeModel( GL_FLAT );
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}



/*!
  Set up the OpenGL view port, matrix mode, etc.
*/

void QFitsView3D::resizeGL( int w, int h )
{
    GLfloat width = (float) w / (float) h;
    GLfloat height = 1.0;
    glViewport( 0, 0, (GLint)w, (GLint)h );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho( -width, width, -height, height, 5.0, 150.0 );
    glMatrixMode( GL_MODELVIEW );
}
