#include <QMessageBox>
#include <QPainter>
#include <QScreen>
#include <QMouseEvent>
#include <QApplication>

#include "QFitsMainWindow.h"
#include "QFitsMainView.h"
#include "QFitsWidgetWiregrid.h"
#include "QFitsSingleBuffer.h"
#include "QFitsViewingTools.h"
#include "fits.h"
#include "qtdpuser.h"
#include <gsl/gsl_math.h>
#include "gsl/gsl_matrix.h"

#define LEVELS 10

QFitsWidgetWiregrid::QFitsWidgetWiregrid(QFitsBaseBuffer *bb) :
                                                         QFitsBaseWidget(bb)
{
    xRot    = 30.0;     // default object rotation
    yRot    = 95.0;
    zRot    = 0.0;
    scale   = 1.25;
    scale   = 5.;       // default object scale
    radius  = 50;
    nvalues = 0;
    image   = NULL;
    zvalues = NULL;
    indices = NULL;
    xv      = NULL;
    yv      = NULL;
    zv      = NULL;

    setMouseTracking(false);
    setFocusPolicy(Qt::StrongFocus);
    setMouseTracking(true);

    connect(this, SIGNAL(updateMagnifier(QPixmap&)),
            fitsMainWindow->viewingtools->magnifier, SLOT(setDirectPixmap(QPixmap&)));
}

/*!
  Release allocated resources
*/

QFitsWidgetWiregrid::~QFitsWidgetWiregrid()
{
    if (image != NULL) {
        delete image;
        image = NULL;
    }

    if (zvalues != NULL) {
        free(zvalues);
        zvalues = NULL;
    }

    if (indices != NULL) {
        free(indices);
        indices = NULL;
    }

    if (xv != NULL) {
        gsl_matrix_free(xv);
        xv = NULL;
    }

    if (yv != NULL) {
        gsl_matrix_free(yv);
        yv = NULL;
    }

    if (zv != NULL) {
        gsl_matrix_free(zv);
        zv = NULL;
    }
}

void QFitsWidgetWiregrid::setZoom(double zoomValue) {
    getMyBuffer()->setZoomFactor_3Dwire(zoomValue);
    update();
}

void QFitsWidgetWiregrid::mousePressEvent(QMouseEvent *m) {
    if (m->button() == Qt::RightButton) {
        mousex = m->x();
        mousey = m->y();
    }
    if (m->button() == Qt::LeftButton) {
        fitsMainWindow->main_view->setCurrentBufferFromWidget(getMyBuffer());
    }
}

void QFitsWidgetWiregrid::mouseMoveEvent(QMouseEvent *event) {
    if (event->buttons() == Qt::RightButton) {
        yRot = yRot - (mousex - event->x()) * 360. / (float)width();
        xRot = xRot - (mousey - event->y()) * 180. / (float)height();
        mousex = event->x();
        mousey = event->y();
        if (yRot < 0.) {
            yRot += 360.;
        } else if (yRot >= 360.) {
            yRot -= 360.;
        }
        if (xRot < -90.) {
            xRot = -90.;
        } else if (xRot > 90.) {
            xRot = 90.;
        }
        update();
    } else {
        QPoint epos = mapToGlobal(event->pos());
        QPixmap p = QApplication::screens().at(0)->grabWindow(0, epos.x() - 15, epos.y() - 15, 30, 30);
        emit updateMagnifier(p);
    }
}

void QFitsWidgetWiregrid::wheelEvent(QWheelEvent* event) {
    if (event->angleDelta().y() > 0) {
        // zoom in
        scale += (float)event->angleDelta().y()/120;
    } else {
        // zoom out
        scale -= -(float)event->angleDelta().y()/120;
    }
    update();
}

void QFitsWidgetWiregrid::setImageCenter(double xcenter, double ycenter) {
    myBuffer->setXcenter(xcenter);
    myBuffer->setYcenter(ycenter);
    update();
}

void QFitsWidgetWiregrid::paintEvent(QPaintEvent*) {
    QReadLocker locker(&buffersLock);
    if ((myBuffer->getDpData()->type == typeFits) && (myBuffer->getDpData()->fvalue != NULL)) {
        if (image == NULL) {
            // image hasn't been initialised yet, do this now
            image = new QImage(width(), height(), QImage::Format_RGB32);
        }

        if ((image->width() < width()) || (image->height() < height())) {
            // image has been allocated smaller than it is actually,
            // so resize it bigger
            delete image;
            image = new QImage(width(), height(), QImage::Format_RGB32);
        }
        int visW = myBuffer->getWidthVisible(),
            visH = myBuffer->getHeightVisible();
        visW = myBuffer->Naxis(2) / myBuffer->getZoomFactor_3Dwire();
        visH = myBuffer->Naxis(1) / myBuffer->getZoomFactor_3Dwire();
        if (zvalues == NULL) {
            zvalues = (double *)malloc(visW * visH * sizeof(double));
            indices = (long *)malloc(visW * visH * sizeof(long));
            nvalues = visW * visH;
            xv = gsl_matrix_alloc(visW+1, visH+1);
            yv = gsl_matrix_alloc(visW+1, visH+1);
            zv = gsl_matrix_alloc(visW+1, visH+1);

        }
        if (nvalues != visW * visH) {
            free(zvalues);
            free(indices);
            gsl_matrix_free(xv);
            gsl_matrix_free(yv);
            gsl_matrix_free(zv);
            zvalues = (double *)malloc(visW * visH * sizeof(double));
            indices = (long *)malloc(visW * visH * sizeof(long));
            xv = gsl_matrix_alloc(visW+1, visH+1);
            yv = gsl_matrix_alloc(visW+1, visH+1);
            zv = gsl_matrix_alloc(visW+1, visH+1);
            nvalues = visW * visH;
        }

        int x1 = 0,
            x2 = 0,
            y1 = 0,
            y2 = 0;

        double  xCen = myBuffer->getXcenter(),
                yCen = myBuffer->getYcenter();

//        myBuffer->transCoordImage2Widget(&xCen, &yCen, myBuffer->getZoomFactor_3Dwire());

        x1 = xCen - visW / 2;
        if (x1 < 1) {
            x1 = 1;
        }
        x2 = x1 + visW - 1;

        if (x2 > myBuffer->Naxis(1)) {
            x2 = myBuffer->Naxis(1);
            x1 = x2 - visW + 1;
            if (x1 < 1) {
                x1 = 1;
            }
        }

        y1 = yCen - visH / 2;
        if (y1 < 1) {
            y1 = 1;
        }
        y2 = y1 + visH - 1;
        if (y2 > myBuffer->Naxis(2)) {
            y2 = myBuffer->Naxis(2);
            y1 = y2 - visH + 1;
            if (y1 < 1) {
                y1 = 1;
            }
        }

        QPainter p;
        if (!p.begin(image)) {
            return;
        }
        p.save();
        p.fillRect(0, 0, width(), height(), QColor(255, 255, 255));
//        calcMesh(x1, x2, y1, y2, yRot * M_PI / 180., xRot * M_PI / 180.);
        QTransform m(-1, 0, 0, -1, width() / 2, (height()-20) - fabs(xRot) / 90. * ((height()-10) / 2));
//        m.setMatrix(-1, 0, 0, -1, width() / 2, (height()-20) - fabs(xRot) / 90. * ((height()-10) / 2));
//        m.setMatrix(-1, 0, 0, 0, -1, 0, width() / 2, (height()-20) - fabs(xRot) / 90. * ((height()-10) / 2), 0);
        p.setWorldTransform(m);
        QPen pen(QColor(0, 0, 0));
        pen.setWidth(0);
        p.setPen(pen);
        p.setBrush(QColor(255,255,255));
        p.scale(scale, scale);
//        p.setRenderHint(QPainter::Antialiasing);

//        for (int j = mesh.count() - 1; j >= 0; j--) {
//            if (zvalues[indices[j]] > -1e29) {
//                p.drawPolygon(mesh.at(indices[j]), Qt::WindingFill);
//            }
//        }
        drawMesh(x1, x2, y1, y2, yRot * M_PI / 180., xRot * M_PI / 180., p);

        p.restore();
        p.end();

        p.begin(this);
        p.drawImage(0, 0, *image, 0, 0, width(), height());
        p.end();

        getMyBuffer()->setWidthVisible(x2 - x1 + 1);
        getMyBuffer()->setHeightVisible(y2 - y1+ 1);
        fitsMainWindow->viewingtools->total->update();

    }
}

void QFitsWidgetWiregrid::resizeEvent(QResizeEvent *e) {
//    usermem = (unsigned char *)realloc(usermem, e->size().width() * e->size().height() * 3);
//    delete im;
//    im = new QImage(e->size().width(), e->size().height(), QImage::Format_RGB32);
}

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

void QFitsWidgetWiregrid::enterEvent(QEvent *e) {
    setFocus();
    enterBuffer();
}

void QFitsWidgetWiregrid::leaveEvent (QEvent *e) {
    leaveBuffer();
}

/*!
  Set the rotation angle of the object to \e degrees around the X axis.
*/

void QFitsWidgetWiregrid::setXRotation( int degrees ) {
    xRot = (float)(degrees % 360);
    update();
}


/*!
  Set the rotation angle of the object to \e degrees around the Y axis.
*/

void QFitsWidgetWiregrid::setYRotation(int degrees) {
    yRot = (float)(degrees % 360);
    update();
}


/*!
  Set the rotation angle of the object to \e degrees around the Z axis.
*/

void QFitsWidgetWiregrid::setZRotation(int degrees) {
    zRot = (float)(degrees % 360);
    update();
}

void QFitsWidgetWiregrid::setScrollerImageCenter(double x, double y) {
    if (isVisible()) {
        myBuffer->setXcenter(x);
        myBuffer->setYcenter(myBuffer->Naxis(2) - y);

        fitsMainWindow->updateTotalVisibleRect();

        update();
    }
}

///*
//Comparison function for double;
//This function uses the global variable double *_compareDouble
//*/
//double *_compareDouble;

//int doubleCompare(const void *a, const void *b)
//{
//        if (_compareDouble[*(long *)a] == _compareDouble[*(long *)b]) return 0;
//        else if (_compareDouble[*(long *)a] < _compareDouble[*(long *)b]) return -1;
//        else return 1;
//}

///*!
//Create an index table of a double array in ascending order.
//The memory for the indices must have been allocated and initialized.
//*/
//void createDoubleIndex(double *array, long *indices, long n) {
//        _compareDouble = array;
//        qsort(indices, n, sizeof(long), doubleCompare);
//}


//void QFitsWidgetWiregrid::calcMesh(int x1, int x2, int y1, int y2, double alpha, double beta) {
//    double sin_alpha = sin(alpha);
//    double cos_alpha = cos(alpha);
//    double sin_beta = sin(beta);
//    double cos_beta = cos(beta);

//    double z, xc, yc, xp, yp, maxvalue = 0.0;

//    xc = x1 + (x2 - x1) / 2.;
//    yc = y1 + (y2 - y1) / 2.;
//    int x, y, n = 0;
//    int quadrant;

//    if (alpha < M_PI / 2.) quadrant = 0;
//    else if (alpha < M_PI) quadrant = 1;
//    else if (alpha < 3. * M_PI / 2.) quadrant = 2;
//    else quadrant = 3;

//    QReadLocker locker(&buffersLock);
////    dpusermutex->lock();
//    n = 0;
//    for (x = x1; x <= x2; x++) {
//        for (y = y1; y <= y2; y++) {
//            z = myBuffer->getFitsData()->ValueAt(myBuffer->getFitsData()->F_I(x, y));
//            if (z > maxvalue) maxvalue = z;
//            zvalues[n] = z;
//            n++;
//        }
//    }
////    dpusermutex->unlock();
//    maxvalue = (x2 - x1 + 1) / maxvalue;
//    n = 0;
//    for (x = x1; x <= x2; x++) {
//        for (y = y1; y <= y2; y++) {
//            z = zvalues[n] * maxvalue;
//            xp = (x-xc) * cos_alpha + (y-yc) * sin_alpha;
//            yp = -(x-xc) * sin_alpha + (y-yc) * cos_alpha;
//            gsl_matrix_set(xv, x-x1, y-y1, xp * cos_beta - z * sin_beta);
//            gsl_matrix_set(yv, x-x1, y-y1, yp);
//            gsl_matrix_set(zv, x-x1, y-y1, xp * sin_beta + z * cos_beta);
//            indices[n] = n;
//            n++;
//        }
//    }
//    QPolygonF plane;
//    n = 0;
//    mesh.clear();
//    for (x = 1; x <= x2-x1+1; x++) {
//        for (y = 1; y <= y2-y1+1; y++) {
//            plane.clear();
//            if (x < x2-x1+1 && y < y2-y1+1) {
//                plane << QPointF(gsl_matrix_get(yv, x-1, y-1), gsl_matrix_get(zv, x-1, y-1)) << QPointF(gsl_matrix_get(yv, x, y-1), gsl_matrix_get(zv, x, y-1)) << QPointF(gsl_matrix_get(yv, x, y), gsl_matrix_get(zv, x, y)) << QPointF(gsl_matrix_get(yv, x-1, y), gsl_matrix_get(zv, x-1, y));
//                mesh.append(plane);
//                switch (quadrant) {
//                case 0:
//                    zvalues[n] = gsl_matrix_get(xv, x, y);
//                    break;
//                case 1:
//                    zvalues[n] = gsl_matrix_get(xv, x-1, y);
//                    break;
//                case 2:
//                    zvalues[n] = gsl_matrix_get(xv, x-1, y-1);
//                    break;
//                case 3:
//                    zvalues[n] = gsl_matrix_get(xv, x, y-1);
//                    break;
//                default:
//                    break;
//                }
//                n++;
//            }
//        }
//    }
//    createDoubleIndex(zvalues, indices, mesh.count());
//}

bool allfinite(const QPointF *d) {
    return gsl_finite(d[0].y()) &&
           gsl_finite(d[1].y()) &&
           gsl_finite(d[2].y()) &&
           gsl_finite(d[3].y());
}

void QFitsWidgetWiregrid::drawMesh(int x1, int x2, int y1, int y2, double alpha, double beta, QPainter &p) {
    if ((myBuffer->getDpData()->type == typeFits) && (myBuffer->getDpData()->fvalue != NULL)) {
        QReadLocker locker(&buffersLock);
        double sin_alpha = sin(alpha);
        double cos_alpha = cos(alpha);
        double sin_beta = sin(beta);
        double cos_beta = cos(beta);

        double z, xc, yc, xp, yp, maxvalue = 0.0;

        xc = x1 + (x2 - x1) / 2.;
        yc = y1 + (y2 - y1) / 2.;
        int quadrant = 0;
        if (alpha < M_PI / 2.) {
            quadrant = 0;
        } else if (alpha < M_PI) {
            quadrant = 1;
        } else if (alpha < 3. * M_PI / 2.) {
            quadrant = 2;
        } else {
            quadrant = 3;
        }

    //    dpusermutex->lock();
        int n = 0;
        for (int x = x1; x <= x2; x++) {
            for (int y = y1; y <= y2; y++) {
                z = myBuffer->getDpData()->fvalue->ValueAt(myBuffer->getDpData()->fvalue->F_I(x, y));
                if (gsl_finite(z)) {
                    if (z > maxvalue) {
                        maxvalue = z;
                    }
                }
                zvalues[n] = z;
                n++;
            }
        }
    //    dpusermutex->unlock();
        maxvalue = (x2 - x1 + 1) / maxvalue;
        n = 0;
        for (int x = x1; x <= x2; x++) {
            for (int y = y1; y <= y2; y++) {
                z = zvalues[n] * maxvalue;
                xp = (x-xc) * cos_alpha + (y-yc) * sin_alpha;
                yp = -(x-xc) * sin_alpha + (y-yc) * cos_alpha;
    //            gsl_matrix_set(xv, x-x1, y-y1, xp * cos_beta - z * sin_beta);
                gsl_matrix_set(yv, x-x1, y-y1, yp);
                gsl_matrix_set(zv, x-x1, y-y1, xp * sin_beta + z * cos_beta);
    //            indices[n] = n;
                n++;
            }
        }
        QPointF plane[4];
        n = 0;

        // we intend to draw from back to front
//        int x = 0, y= 0;
        switch (quadrant) {
        case 0:
            for (int x = x2 - x1 - 1; x >= 0; x--) {
                for (int y = y2-y1-1; y >= 0; y--) {
                    plane[0].setX(gsl_matrix_get(yv, x, y));
                    plane[0].setY(gsl_matrix_get(zv, x, y));
                    plane[1].setX(gsl_matrix_get(yv, x+1, y));
                    plane[1].setY(gsl_matrix_get(zv, x+1, y));
                    plane[2].setX(gsl_matrix_get(yv, x+1, y+1));
                    plane[2].setY(gsl_matrix_get(zv, x+1, y+1));
                    plane[3].setX(gsl_matrix_get(yv, x, y+1));
                    plane[3].setY(gsl_matrix_get(zv, x, y+1));
                    if (allfinite(plane)) {
                        p.drawPolygon(plane, 4);
                    }
                }
            }
            break;
        case 1:
            for (int x = 0; x < x2-x1; x++) {
                for (int y = y2-y1-1; y >= 0; y--) {
                    plane[0].setX(gsl_matrix_get(yv, x, y));
                    plane[0].setY(gsl_matrix_get(zv, x, y));
                    plane[1].setX(gsl_matrix_get(yv, x+1, y));
                    plane[1].setY(gsl_matrix_get(zv, x+1, y));
                    plane[2].setX(gsl_matrix_get(yv, x+1, y+1));
                    plane[2].setY(gsl_matrix_get(zv, x+1, y+1));
                    plane[3].setX(gsl_matrix_get(yv, x, y+1));
                    plane[3].setY(gsl_matrix_get(zv, x, y+1));
                    if (allfinite(plane)) {
                        p.drawPolygon(plane, 4);
                    }
                }
            }
            break;
        case 2:
            for (int x = 0; x < x2-x1; x++) {
                for (int y = 0; y < y2-y1; y++) {
                    plane[0].setX(gsl_matrix_get(yv, x, y));
                    plane[0].setY(gsl_matrix_get(zv, x, y));
                    plane[1].setX(gsl_matrix_get(yv, x+1, y));
                    plane[1].setY(gsl_matrix_get(zv, x+1, y));
                    plane[2].setX(gsl_matrix_get(yv, x+1, y+1));
                    plane[2].setY(gsl_matrix_get(zv, x+1, y+1));
                    plane[3].setX(gsl_matrix_get(yv, x, y+1));
                    plane[3].setY(gsl_matrix_get(zv, x, y+1));
                    if (allfinite(plane)) {
                        p.drawPolygon(plane, 4);
                    }
                }
            }
            break;
        case 3:
            for (int x = x2 - x1 - 1; x >= 0; x--) {
                for (int y = 0; y < y2-y1; y++) {
                    plane[0].setX(gsl_matrix_get(yv, x, y));
                    plane[0].setY(gsl_matrix_get(zv, x, y));
                    plane[1].setX(gsl_matrix_get(yv, x+1, y));
                    plane[1].setY(gsl_matrix_get(zv, x+1, y));
                    plane[2].setX(gsl_matrix_get(yv, x+1, y+1));
                    plane[2].setY(gsl_matrix_get(zv, x+1, y+1));
                    plane[3].setX(gsl_matrix_get(yv, x, y+1));
                    plane[3].setY(gsl_matrix_get(zv, x, y+1));
                    if (allfinite(plane)) {
                        p.drawPolygon(plane, 4);
                    }
                }
            }
        default:
            break;
        }
    }
}
