/*@@@**************************************************************************
 * \file  CDisplay
 * \author Hernan Badino
 * \date  Thu Feb 26 10:45:48 Local time zone must be set--see zic manual page 2009
 * \notes 
 *******************************************************************************
 ******************************************************************************/

/* INCLUDES */

#include <QtGui>
#include <QtOpenGL>

#include "display.h"
#include "displayTreeNode.h"

using namespace VIC;

CDisplay::CDisplay( CDisplayOpNode * const f_rootNode_p,
                    QWidget *              f_parent_p )
        : QGLWidget(                    f_parent_p, 0 ),
          m_rootNode_p (                 f_rootNode_p ),
          m_zoomTL (                              0,0 ),
          m_zoomFactor_f (                        1.f )
{
    /// Just now temporal.
    m_screenSize.width  = 800;
    m_screenSize.height = 600;

    m_screenCount.width  = 3;
    m_screenCount.height = 3;

    /// Enable focus on this widget.
    setFocusPolicy(Qt::StrongFocus);

    m_roiSize.width  = width();
    m_roiSize.height = height();
    m_roiPos.x = 0;
    m_roiPos.y = 0;
    
    setAcceptDrops(true);
    setMouseTracking (true);
}

CDisplay::~CDisplay()
{
}

QSize CDisplay::minimumSizeHint() const
{
    return QSize(400, 400);
}

QSize CDisplay::sizeHint() const
{
    return QSize(random()/(RAND_MAX/100)+100, 200);
}

void CDisplay::initializeGL()
{
    // First disable depth test. 
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
}

void CDisplay::paintGL()
{
    // Reset modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Clear screen using the current background color
    glClearColor( 0.f, 0.f, 0.f, 1.);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor4f( 1.f, 1.f, 1.f, 1.f);

    /// This line can help flip views or set aspect ratios.
    glScalef(2.f, -2.f, 1.f);
    // Move center (0,0) to upper left corner.
    glTranslatef( -.5, -.5, 0.0);

    // Scale to use absolut pixel coordinates considering current zoom factor.
    glScalef( m_zoomFactor_f / m_roiSize.width,
              m_zoomFactor_f / m_roiSize.height, 1.0);
    
    glTranslatef( m_roiPos.x-m_zoomTL.x,
                  m_roiPos.y-m_zoomTL.y, 0.0);

    //// PAINT HERE FIRST THE IMAGES

    // Enable transparency
    //glEnable(GL_BLEND);
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    ////// PAINT HERE ALL OTHER STUFF
    displayScreens ( m_rootNode_p );

    // Disable transparency again
    //glDisable(GL_BLEND);

    ////// PAINT FINALLY HERE THE SELECTION AND ZOOM RECTANGLES.


    //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //glLoadIdentity();


    //printf("paintGL called \n");
}

void CDisplay::resizeGL(const int f_width_i, const int f_height_i)
{
    glViewport( 0, 0, f_width_i, f_height_i );
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity();

    // Scale to use absolut pixel coordinates considering current zoom factor.
    const float totalWidth_f  = m_screenSize.width  * (float) m_screenCount.width;
    const float totalHeight_f = m_screenSize.height * (float) m_screenCount.height;

    // Centrate displays in window.
    float displayAspectRatio_f = totalWidth_f / (float)totalHeight_f;
    float widgetAspectRatio_f = f_width_i / (float)f_height_i;
    float aspectRel_f         = displayAspectRatio_f / widgetAspectRatio_f;
    
    if ( aspectRel_f < 1 )
    {
        float rel_f = totalHeight_f / f_height_i;
        
        m_roiSize.height = totalHeight_f;
        m_roiPos.y       = 0;
        
        m_roiSize.width  = rel_f * f_width_i;
        m_roiPos.x       = (m_roiSize.width - totalWidth_f)/2;
    }
    else
    {
        float rel_f = totalWidth_f / f_width_i;
        
        m_roiSize.width  = totalWidth_f;
        m_roiPos.x       = 0;
        
        m_roiSize.height = rel_f * f_height_i;
        m_roiPos.y       = (m_roiSize.height - totalHeight_f)/2;
    }

    if (isOneScreenMode())
        setZoomOnScreen ( m_fsScreen.x, m_fsScreen.y, false);
}

QImage
CDisplay::renderGL ()
{
    printf("Rendering pixmap\n");
    
    QPixmap pixmap = QGLWidget::renderPixmap(0, 0, true);
    QImage fullImage = pixmap.toImage();
    QImage saveImage;
    
    int sx = -1;
    int sy = 0;
    
    if (sx == -1)
        saveImage = fullImage;
    else
        saveImage = fullImage.copy(int(sx*800), int(sy*600), 800, 600);
    
    return saveImage;    
}

void CDisplay::mousePressEvent( QMouseEvent * f_event_p )
{
    m_prevZoomTL = m_zoomTL;
    m_prevMousePos.x = f_event_p -> x();
    m_prevMousePos.y = f_event_p -> y();

    emit mousePressed ( getMouseEventData(f_event_p) );
}

CMouseEvent *CDisplay::getMouseEventData( QMouseEvent * f_event_p )
{
    static CMouseEvent me;
    me.qtMouseEvent_p = f_event_p;
    
    S2D<float> mousePos;
    /// normalize position.
    mousePos.x = m_zoomTL.x - m_roiPos.x + 
        (f_event_p->x() / (float)width()  * m_roiSize.width) / m_zoomFactor_f;
    
    mousePos.y = m_zoomTL.y - m_roiPos.y + 
        f_event_p->y() / (float)height() * m_roiSize.height / m_zoomFactor_f;
    
    me.posInDisplay = mousePos;
    
    S2D<int> screen ( (int)(mousePos.x / m_screenSize.width),
                      (int)(mousePos.y / m_screenSize.height) );

    if (mousePos.x < 0) --screen.x;
    if (mousePos.y < 0) --screen.y;
    
    me.displayScreen = screen;
    me.posInScreen = S2D<float>( mousePos.x - 
                                 screen.x * m_screenSize.width,
                                 mousePos.y - 
                                 screen.y * m_screenSize.height );
    return &me;
}



void CDisplay::mouseMoveEvent( QMouseEvent * f_event_p )
{
    // Check if mouse clicked.
    if ( m_prevMousePos.isValid() && 
         (f_event_p -> buttons() & Qt::LeftButton) &&
         m_zoomFactor_f > 1 &&
         f_event_p->modifiers() & Qt::ControlModifier )
    {
        S2D<float> deltaPos ( m_prevMousePos.x - f_event_p -> x(),
                              m_prevMousePos.y - f_event_p -> y() );    
        
        // Scale to use absolut pixel coordinates.
        //const float totalWidth_f  = m_screenSize.width  * (float) m_screenCount.width;
        //const float totalHeight_f = m_screenSize.height * (float) m_screenCount.height;

        deltaPos.x *= m_roiSize.width  / width();
        deltaPos.y *= m_roiSize.height / height();
        
        m_zoomTL.x = m_prevZoomTL.x + deltaPos.x / (m_zoomFactor_f);
        m_zoomTL.y = m_prevZoomTL.y + deltaPos.y / (m_zoomFactor_f);

        //printf("zoomFactor: %f pos x: %f pos y: %f\n",
        //       m_zoomFactor_f, m_zoomTL.x, m_zoomTL.y );

        exitOneScreenMode();

        updateGL();
    }

    emit mouseMoved ( getMouseEventData(f_event_p) );
}

void CDisplay::mouseReleaseEvent(QMouseEvent * f_event_p )
{
    // Invalidate mouse pos.
    m_prevMousePos = S2D<float>();
    
    emit clicked();
    emit mouseReleased( getMouseEventData(f_event_p) );
}

void CDisplay::wheelEvent ( QWheelEvent *f_event_p )
{
    if (f_event_p->orientation() == Qt::Vertical)
    {
        int numDegrees_i = f_event_p->delta() / 8;
        int numSteps_i   = numDegrees_i / 15;
        
        float newZoomFactor_f = m_zoomFactor_f;
        
        if ( f_event_p -> modifiers() & Qt::ControlModifier )
        {
            if (numSteps_i > 0)
                newZoomFactor_f *= (abs(numSteps_i)+1);
            else
                newZoomFactor_f /= (abs(numSteps_i)+1);
        }
        else if ( f_event_p -> modifiers() & Qt::ShiftModifier )
        {
            newZoomFactor_f += numSteps_i/1000.f;
        }
        else
            newZoomFactor_f += numSteps_i;
        
        newZoomFactor_f = std::max(1.f, newZoomFactor_f);
        
        exitOneScreenMode();
        
        zoom ( f_event_p -> x(),
               f_event_p -> y(),
               newZoomFactor_f,
               true );

    }

    f_event_p->accept();
    emit wheelTurned ( f_event_p );
}


/// Progressive zoom.
void 
CDisplay::zoom ( float f_x_f, 
                 float f_y_f, 
                 float f_newZoomFactor_f,
                 bool f_updateGL_b )
{
    if (f_newZoomFactor_f == 1.f)
    {
        m_zoomFactor_f = f_newZoomFactor_f;
        m_zoomTL.x = 0;
        m_zoomTL.y = 0;
    }
    else
    {
        // Scale to use absolut pixel coordinates.
        S2D<float> mousePos;

        /// normalize position.
        mousePos.x = f_x_f / (float)width()  * m_roiSize.width; 
        mousePos.y = f_y_f / (float)height() * m_roiSize.height;
    
        /// Compute position of mouse in the scaled coordinate.
        float x_f = m_zoomTL.x + mousePos.x / m_zoomFactor_f;
        float y_f = m_zoomTL.y + mousePos.y / m_zoomFactor_f;

        m_zoomFactor_f = f_newZoomFactor_f;

        /// Compute position top/left corner in the new zoomed
        /// coordinate.
        m_zoomTL.x = x_f - ( mousePos.x ) / m_zoomFactor_f;
        m_zoomTL.y = y_f - ( mousePos.y ) / m_zoomFactor_f;
    
        //printf("zoomFactor: %f pos x: %f pos y: %f\n",
        //       m_zoomFactor_f, m_zoomTL.x, m_zoomTL.y );    
    }

    if ( f_updateGL_b )
        updateGL();
}

void CDisplay::keyPressEvent ( QKeyEvent *   f_event_p )
{
    if (f_event_p -> key() == Qt::Key_F )
    {
        emit fullScreenSwitched();
    }
    else if ( f_event_p -> key() == Qt::Key_Escape )
    {
        emit exitFullScreen();
    }
    else if ( f_event_p -> key() >= Qt::Key_F1 && 
              f_event_p -> key() <= Qt::Key_F35 )
    {
        int key_i = f_event_p -> key() - Qt::Key_F1;
        unsigned int sy_i = (int)(key_i / m_screenCount.width);
        unsigned int sx_i = (int)(key_i % m_screenCount.width);
        setZoomOnScreen ( sx_i, sy_i );
    }
    else if ( f_event_p -> key() == Qt::Key_Less )
    {
        zoomIn();
    }
    else if ( f_event_p -> key() == Qt::Key_Greater )
    {
        zoomOut();
    }
    else if ( f_event_p ->key() == Qt::Key_G )
    {
        static int imgNr_i = 0;
        if (0)
        {
            QImage saveImage = renderGL();
            
            char fileName_p[256];
            
            sprintf(fileName_p, "grabbedDisplayImg_%05i.png", imgNr_i );
         
            printf("Saving image %s\n", fileName_p );
            
            saveImage.save( fileName_p );
            
        }
        ++imgNr_i;
    }
    
    static CKeyEvent ke;
    ke.qtKeyEvent_p = f_event_p;

    emit keyPressed ( &ke );

}

void CDisplay::displayScreens ( CDisplayOpNode * const f_parent_p )
{
    for (uint32_t i = 0; i < f_parent_p -> getDisplayCount(); ++i)
    {
        CDisplayNode *  dispNode_p = f_parent_p-> getDisplayChild (i);
        CDrawingList *  list_p     = dispNode_p -> getDrawingList();

        if ( list_p -> VIC::CDrawingList::isVisible() )
        {
            S2D<int> pos = list_p -> getPosition ( );

            if ( !isOneScreenMode() ||
                 ( pos.x == m_fsScreen.x &&
                   pos.y == m_fsScreen.y ) )
            {   
                glTranslatef( pos.x * m_screenSize.width, 
                              pos.y * m_screenSize.height, 
                              0.0 );
                
                list_p -> show();
                
                glTranslatef( -pos.x * m_screenSize.width, 
                              -pos.y * m_screenSize.height, 
                              0.0 );
            }
        }
    }

    for (unsigned int i = 0; i < f_parent_p -> getOpCount(); ++i)
    {
        displayScreens ( f_parent_p-> getOpChild (i) );
    }
}

bool CDisplay::setZoomOnScreen ( const S2D<float>  screen,
                                 const bool f_update_b )
{
    return setZoomOnScreen ( screen.x, screen.y, f_update_b );
}

bool CDisplay::setZoomOnScreen ( const unsigned int f_screenX_ui, 
                                 const unsigned int f_screenY_ui,
                                 const bool         f_update_b )
{
    if ( f_screenX_ui < (unsigned) m_screenCount.width && 
         f_screenY_ui < (unsigned) m_screenCount.height )
    {
        m_fsScreen.x = f_screenX_ui;
        m_fsScreen.y = f_screenY_ui;

        S2D<float> aspect( m_roiSize.width  /  m_screenSize.width,
                           m_roiSize.height /  m_screenSize.height );

        if ( aspect.width < aspect.height )
        {
            m_zoomFactor_f = aspect.width;
            m_zoomTL.x = 0;
            float val_f = (m_screenSize.width ) / (float)width();
            m_zoomTL.y = -(height() * val_f - m_screenSize.height)/2;
        }
        else
        {
            m_zoomFactor_f = aspect.height;
            float val_f = (m_screenSize.height ) / (float)height();
            m_zoomTL.x = -(width() * val_f - m_screenSize.width)/2;
            m_zoomTL.y = 0;
        }

        m_zoomTL.x += f_screenX_ui * m_screenSize.width  + m_roiPos.x;
        m_zoomTL.y += f_screenY_ui * m_screenSize.height + m_roiPos.y;

        //printf("m_zoomFactor_f = %f\n", m_zoomFactor_f);

        if (f_update_b)
            updateGL();

        return true;        
    }

    return false;
}

void CDisplay::zoomIn ( )
{
    m_zoomFactor_f/=2;

    if ( m_zoomFactor_f < 1) 
        m_zoomFactor_f = 1;

    updateGL();
}

void CDisplay::zoomOut ( )
{
    m_zoomFactor_f*=2;
    updateGL();
}

/// DRAG/DROP OPERATIONS
void CDisplay::dragEnterEvent(QDragEnterEvent *event)
{
    //printf("Drag Enter\n");
    
    if (event->mimeData()->hasFormat("display/gl-display-list"))
        event->accept();
    else
        event->ignore();
}

void CDisplay::dragMoveEvent(QDragMoveEvent *event)
{
    // Some cool display should be made here.
    //printf("Drag Move\n");
 
   if (event->mimeData()->hasFormat("display/gl-display-list")) 
   {
       event->setDropAction(Qt::MoveAction);
       event->accept();
   } 
   else
       event->ignore();
}

void CDisplay::dropEvent(QDropEvent *f_event_p)
{
    //printf("Drop F_Event_P\n");
    if (f_event_p->mimeData()->hasFormat("display/gl-display-list")) 
    {
        QByteArray pieceData = f_event_p->mimeData()->data("display/gl-display-list");
        QDataStream dataStream(&pieceData, QIODevice::ReadOnly);
        //QPixmap pixmap;
        //QPoint location;
        QModelIndexList items;
        
        qint32 size_i;
        quint64 ptr_ui;
        
        dataStream >> size_i;
        //printf("Size is %i\n", size_i);
        
        for (int i = 0; i < size_i; ++i)
        {
            dataStream >> ptr_ui;

            CDisplayNode *  node_p = static_cast<CDisplayNode *>((void*)ptr_ui);
            
            /// normalize position.
            QPoint pos = f_event_p -> pos ();
            S2D<float> mousePos;
            mousePos.x = pos.x() / (float)width()  * m_roiSize.width  - m_roiPos.x;
            mousePos.y = pos.y() / (float)height() * m_roiSize.height - m_roiPos.y;
            
            S2D<int> newPos;
            newPos.x =  std::min( std::max( 0,
                                            (int)(mousePos.x / m_screenSize.width) ),
                                  (int)(m_screenCount.width  - 1 ) );
            newPos.y =  std::min( std::max( 0, 
                                            (int)(mousePos.y / m_screenSize.height) ), 
                                  (int)(m_screenCount.height - 1 ) );
            
            node_p -> getDrawingList() -> setPosition(newPos);
            node_p -> setVisibility(true);
        }
        
        f_event_p->setDropAction(Qt::MoveAction);
        f_event_p->accept();
        
        updateGL();
    } 
    else
        f_event_p->ignore();
}


/* ////////////  Version History ///////////////
 *  $Log: display.cpp,v $
 *  Revision 1.2  2009/11/18 15:50:15  badino
 *  badino: global changes.
 *
 *//////////////////////////////////////////////
