#include "QSofaViewer.h"
#include <QApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QMessageBox>
#include <QAction>
#include <sofa/helper/system/glut.h>
#include <SofaOpenglVisual/ClipPlane.h>
#include <iostream>
using std::cout;
using std::endl;
#include <SofaMiscCollision/RayTriangleVisitor.h>

typedef std::size_t Index;

GLfloat light_position[] = { 0.0, 0.0, 25.0, 0.0 };
GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 0.0 };
GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 0.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 0.0 };

//GLfloat cp[] = { 0.0, 0.0, 25.0, 0.0 };
GLfloat camera_target[] = { 22.0, 0.0, 0.0 };
GLfloat camera_angle = 55;
GLfloat znear = 15;
GLfloat zfar = 35;
GLfloat DegToRad = 3.1415927 / 180;


QSofaViewer::QSofaViewer(QSofaScene *sofaScene, QWidget *parent) :
    QGLWidget(parent), _sofaScene(sofaScene), _sofaGL(0)
{
    _drag = NULL;
    _showPointed = false;
    connect(sofaScene, SIGNAL(opened()), this, SLOT(reset()));
    connect(sofaScene, SIGNAL(stepEnd()), this, SLOT(draw()));

    {
        QAction* toggleFullScreenAct = new QAction( tr("&FullScreen"), this );
        toggleFullScreenAct->setShortcut(QKeySequence(Qt::CTRL+Qt::Key_F));
        toggleFullScreenAct->setShortcutContext(Qt::ApplicationShortcut);
        toggleFullScreenAct->setToolTip(tr("Show full screen"));
        connect(toggleFullScreenAct, SIGNAL(triggered()), this, SLOT(toggleFullScreen()));
        this->addAction(toggleFullScreenAct);
    }

}

void QSofaViewer::toggleShowPointed()
{
//    cerr<<"QSofaViewer::toggleShowPointed()" << endl;
    if( _showPointed )
    {
        _showPointed = false;
        setMouseTracking(false);
        setToolTip("");
    }
    else {
        _showPointed = true;
        setMouseTracking(true);
    }
}

void QSofaViewer::toggleFullScreen()
{
    std::cerr<<"QSofaViewer::toggleFullScreen()" << std::endl;
    if( this->isFullScreen() ){
        this->showNormal();
    }
    else {
        this->showFullScreen();
    }
}


void QSofaViewer::initializeGL()
{
    glClearColor (0.0, 0.0, 0.0, 0.0);

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glEnable(GL_DEPTH_TEST);


    _camera.setlookAt (
                0,0,25,
                camera_target[0],camera_target[1],camera_target[2],
            0.0, 1.0, 0.0 // up vector
            );


}

void QSofaViewer::paintGL()
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity ();
    _camera.lookAt();
    //    gluLookAt (
    //            cp[0],cp[1],cp[2],
    //            camera_target[0],camera_target[1],camera_target[2],
    //            0.0, 1.0, 0.0 // up vector
    //            );

    // we need to init sofaGL here in order to be able to call initTextures (needing an active opengl context) for each opened scene
    if(!_sofaGL)
    {
        _sofaGL = new SofaGL(_sofaScene);
        viewAll();


    }

    _sofaGL->draw();

    //    // display a box, for debug
    //    glColor3f (1.0, 0.0, 0.0);
    //    glutWireCube (1.0);

}

void QSofaViewer::reset()
{
    delete _sofaGL;
    _sofaGL = 0;
}

void QSofaViewer::draw()
{
    update();
}

void QSofaViewer::viewAll()
{
    if(!_sofaGL)
        return;

    //    SReal cp[3], ct[3], zn, zf;
    //    Camera::Vec3 eye = _camera.eye();
    //    for( int i=0; i<3; i++ ) cp[i] = eye[i];
    //    _sofaGL->viewAll(
    //            &cp[0],&cp[1],&cp[2],
    //            &ct[0],&ct[1],&ct[2],
    //            camera_angle * DegToRad, &zn, &zf
    //            );
    //    cout << "QSofaViewer::viewAll, camera eye before = " << _camera.eye().transpose() << endl << ", linear = " << endl << _camera.getTransform().linear().inverse() << endl;
    //    cout << " cp = " << cp[0] << " " << cp[1] << " " << cp[2] << ", ct = "<< ct[0] << " " << ct[1] << " " << ct[2]  << endl;
    //     _camera.setlookAt(cp[0],cp[1],cp[2],
    //            ct[0],ct[1],ct[2],
    //            _camera.getTransform().linear()(1,0), _camera.getTransform().linear()(1,1), _camera.getTransform().linear()(1,2)); // use current y direction as up axis
    //    cout << "QSofaViewer::viewAll, camera eye after = " << _camera.eye().transpose() << endl << ", linear = " << endl << _camera.getTransform().linear().inverse() << endl;
    //    znear = zn;
    //    zfar = zf;

    float xmin, xmax, ymin, ymax, zmin, zmax;
    _sofaGL->getSceneBBox(&xmin,&ymin,&zmin, &xmax,&ymax,&zmax);
    _camera.viewAll(xmin,ymin,zmin, xmax,ymax,zmax);

    update();
}

void QSofaViewer::resizeGL(int w, int h)
{
    glViewport (0, 0, (GLsizei) w, (GLsizei) h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    _camera.perspective(camera_angle, (GLfloat) w/(GLfloat) h, 0.01, 100);
    glMatrixMode (GL_MODELVIEW);
}

void QSofaViewer::keyPressEvent ( QKeyEvent * /*event*/ )
{
    //    if( event->key() == Qt::Key_Shift ) cout << "Shift ";
    //    else if( event->key() == Qt::Key_Control ) cout << "Control ";
    //    else if( event->key() == Qt::Key_Alt ) cout << "Alt ";
    //    cout << event->text().toStdString();
}

void QSofaViewer::keyReleaseEvent ( QKeyEvent * /*event*/ )
{
    //    cout << endl;
}

void QSofaViewer::mousePressEvent ( QMouseEvent * event )
{
    if(!_sofaGL)
        return;

    if( isShiftPressed() )
    {
        if( isControlPressed() ) // pick an existing interactor
        {
            _drag = _sofaGL->pickInteractor(_camera.eye()[0], _camera.eye()[1], _camera.eye()[2], event->x(), event->y());
        }
        else if( sofa::simplegui::PickedPoint glpicked = _sofaGL->pick(
                     _camera.eye()[0], _camera.eye()[1], _camera.eye()[2],
                     event->x(),event->y() )  ) // create new interactor
        {
            //            cout << "Picked: " << glpicked <<  endl;
            _drag = _sofaGL->getInteractor(glpicked);
            if( _drag == NULL )
            {
                cout << "create new interactor" << endl;
                _drag = new sofa::simplegui::SpringInteractor(glpicked,10000);
                _sofaGL->attach(_drag);
            }
            else cout << "reuse interactor" << endl;
        }
        else {
            cout << "no particle glpicked" << endl;
        }
    }
    else if(isControlPressed()){
        _sofaGL->glPick(event->x(), event->y());
        update();
    }
    else {
        if( _camera.handleMouseButton(
                    event->button()==Qt::LeftButton ? Camera::ButtonLeft : event->button()==Qt::MiddleButton ? Camera::ButtonMiddle : Camera::ButtonRight ,
                    Camera::ButtonDown,
                    event->x(), event->y()
                    ))
        {
            return;
        }

    }

}

void QSofaViewer::mouseMoveEvent ( QMouseEvent * event )
{
    if(!_sofaGL)
        return;

    if( _showPointed ){ // cast a ray to figure out what is under the pointer

        sofa::component::collision::RayTriangleVisitor rtv;
        // Set the direction. Note that any direction would work. Setting it helps debugging.
        _sofaGL->getPickDirection(&rtv.direction[0],&rtv.direction[1],&rtv.direction[2],event->x(),event->y());

        Vec3 origin(_camera.eye()[0],_camera.eye()[1],_camera.eye()[2]);
        rtv.origin = origin;

        // If clipping plane (assuming there can not be more than one) change the origin of the ray
        // TODO: make this more general, by handling the clipping planes in the visitor
        using sofa::component::visualmodel::ClipPlane;
        ClipPlane* clipPlane = _sofaScene->groot()->get<ClipPlane>(sofa::core::objectmodel::BaseContext::SearchDown);
        if( clipPlane != NULL )
        {
            // find the intersection of the ray with the plane
            Vec3 N =clipPlane->normal.getValue();

            // find intersection point
            SReal NdotRayDir = N*rtv.direction;
            SReal eps = std::numeric_limits<SReal>::epsilon() * 100;
            if( fabs(NdotRayDir)>eps) {// ray not parallel to plane.
                SReal d= N*clipPlane->position.getValue();
                SReal t= -(N*origin + d)/NdotRayDir;
                if( NdotRayDir < 0 ) { // the triangle is forward
                    rtv.origin = origin + t * rtv.direction;
//                    cerr<<"origin moved to the clipping plane"<< endl;
                }
            }
        }

        // go !
        _sofaScene->groot()->executeVisitor(&rtv);
        sofa::core::objectmodel::BaseObject* cm = rtv.embeddingModel();
        if( cm!=NULL ){
//            cerr<<"Inside " << cm->getName() << endl;
            setToolTip(cm->getName().c_str());
        }
        else {
//            cerr<<"Outside all" << endl;
            setToolTip("Outside all");
        }

//        return;
    }

    if( _drag != NULL )
    {
        _sofaGL->move(_drag, event->x(), event->y());
    }
    else if( _camera.handleMouseMotion(event->x(), event->y()) )
    {
        update();
        return;
    }

}

void QSofaViewer::mouseReleaseEvent ( QMouseEvent * event )
{
    if(!_sofaGL)
        return;

    if( _drag != NULL )
    {
        if(QApplication::keyboardModifiers() & Qt::ShiftModifier )
        {
            _sofaGL->detach(_drag);
            delete _drag;
            //                cout << "delete interactor " << endl;
        }
        _drag = NULL;
    }
    else
    {
        if( _camera.handleMouseButton(
                    event->button()==Qt::LeftButton ? Camera::ButtonLeft : event->button()==Qt::MiddleButton ? Camera::ButtonMiddle : Camera::ButtonRight ,
                    Camera::ButtonUp,
                    event->x(), event->y()
                    ))
        {
            return;
        }

    }
}

