#include "MatrixGLWidget.hpp"

#include "MatrixWindow.hpp"

#define BACKGROUND_COLOR_R      0.9f
#define BACKGROUND_COLOR_G      0.9f
#define BACKGROUND_COLOR_B      0.9f

/* Helpers */
static void drawSquare(GLfloat x, GLfloat y, GLfloat dx, GLfloat dy, GLfloat r, GLfloat g, GLfloat b)
{
    glBegin(GL_QUADS);

    // Setup color
    glColor3f(r, g, b);

    // Setup square corners
    glVertex2f(x,      y);
    glVertex2f(x + dx, y);
    glVertex2f(x + dx, y + dy);
    glVertex2f(x,      y + dy);

    glEnd();
}

static void drawTemporarySelection(int x, int y, int dx, int dy)
{
    glBegin(GL_QUADS);

    // Setup color
    glColor4f(1.f, 1.f, 1.f, .5f);

    // Setup vertex
    glVertex2f(x,      y);
    glVertex2f(x + dx, y);
    glVertex2f(x + dx, y + dy);
    glVertex2f(x,      y + dy);

    glEnd();
}

MatrixGLWidget::MatrixGLWidget(QWidget* parent, symbol_matrix_t* matrix, QLabel* label)
    :
#ifdef USE_QT5
    QOpenGLWidget(parent)
#else
    QGLWidget(parent)
#endif
    , m_frameCount(0)
    , m_label(label)
{
    this->setFixedSize(MATRIX_WINDOW_LENGTH, MATRIX_WINDOW_HEIGHT);

    m_zooming = new Zooming( matrix );

    m_qtToGLWidthCoeff  = 1.f;
    m_qtToGLHeightCoeff = 1.f;

    m_camera.m_cameraX  = 0.f;
    m_camera.m_cameraY  = 0.f;
    m_camera.m_cameraDx = 1.f;
    m_camera.m_cameraDy = 1.f;

    m_mouseXClicked = -1;
    m_mouseYClicked = -1;
}

MatrixGLWidget::~MatrixGLWidget()
{
    delete m_zooming;
}

void MatrixGLWidget::initializeGL()
{
#ifdef USE_QT5
    initializeOpenGLFunctions();
#endif
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    glClearColor(BACKGROUND_COLOR_R, BACKGROUND_COLOR_G, BACKGROUND_COLOR_B, 1.f);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, Zooming::DEFAULT_LEVEL_POWER_2, Zooming::DEFAULT_LEVEL_POWER_2, 0, -1, 1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0, 2, 0);
}

void MatrixGLWidget::resizeGL(int w, int h)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, Zooming::DEFAULT_LEVEL_POWER_2, Zooming::DEFAULT_LEVEL_POWER_2, 0, -1, 1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0, 2, 0);

    m_qtToGLWidthCoeff  = (double)Zooming::DEFAULT_LEVEL_POWER_2 / (double)w;
    m_qtToGLHeightCoeff = (double)Zooming::DEFAULT_LEVEL_POWER_2 / (double)h;
}

void MatrixGLWidget::paintGL()
{
    int i, j;

#ifdef USE_QT5
    initializeOpenGLFunctions();
#endif

    if (m_frameCount == 0) {
        m_time.start();
    }
    else
    {
        sprintf(m_fpsString, "FPS: %f ms", m_time.elapsed() / float(m_frameCount));
        m_label->setText(m_fpsString);
    }

    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor( BACKGROUND_COLOR_R,
                  BACKGROUND_COLOR_G,
                  BACKGROUND_COLOR_B,
                  0.f );

    for (i = 0; i < Zooming::DEFAULT_LEVEL_POWER_2; ++i)
    {
        for (j = 0; j < Zooming::DEFAULT_LEVEL_POWER_2; ++j)
        {
            GLfloat grey = m_zooming->getColor(i, j);

            if (grey != 1.f) {
                if (grey != 0.f) {
                    drawSquare(i, j, 1, 1, 0., 0., grey);
                }
                else {
                    drawSquare(i, j, 1, 1, grey, grey, grey);
                }
            }
        }
    }

    if (m_drawTempSelection == 1) {
        drawTemporarySelection(m_tempSelectionX, m_tempSelectionY, m_tempSelectionDx, m_tempSelectionDy);
    }

    m_frameCount++;
}

void MatrixGLWidget::keyPressEvent(QKeyEvent *keyEvent)
{
    switch (keyEvent->key())
    {
    default:
        break;
    }
}

void MatrixGLWidget::mousePressEvent(QMouseEvent* mouseEvent)
{
    if (mouseEvent->button() == Qt::RightButton)
    {
        if (m_savedPositions.size() > 0)
        {
            CameraPosition newCamera = m_savedPositions.top();
            m_savedPositions.pop();

            m_camera = newCamera;

            refreshCamera();
        }
    }
    else if (mouseEvent->button() == Qt::LeftButton)
    {
        m_mouseXClicked = mouseEvent->x() * m_qtToGLWidthCoeff;
        m_mouseYClicked = mouseEvent->y() * m_qtToGLHeightCoeff;
    }
}

void MatrixGLWidget::mouseMoveEvent(QMouseEvent* mouseEvent)
{
    if ( (m_mouseXClicked != -1) &&
         (m_mouseYClicked != -1) )
    {
        int glPosX = mouseEvent->x() * m_qtToGLWidthCoeff;
        int glPosY = mouseEvent->y() * m_qtToGLHeightCoeff;

        m_drawTempSelection = 1;

        m_tempSelectionX  = m_mouseXClicked;
        m_tempSelectionY  = m_mouseYClicked;
        m_tempSelectionDx = glPosX - m_mouseXClicked;
        m_tempSelectionDy = glPosY - m_mouseYClicked;

        update();
    }
}

void MatrixGLWidget::mouseReleaseEvent(QMouseEvent* mouseEvent)
{
    if ( (m_mouseXClicked != -1) &&
         (m_mouseYClicked != -1) )
    {
        int xmin, xmax, ymin, ymax;
        int glPosX = mouseEvent->x() * m_qtToGLWidthCoeff;
        int glPosY = mouseEvent->y() * m_qtToGLHeightCoeff;

        if ( glPosX > m_mouseXClicked )
        {
            xmin = m_mouseXClicked;
            xmax = glPosX;
        }
        else
        {
            xmin = glPosX;
            xmax = m_mouseXClicked;
        }

        if ( glPosY > m_mouseYClicked )
        {
            ymin = m_mouseYClicked;
            ymax = glPosY;
        }
        else
        {
            ymin = glPosY;
            ymax = m_mouseYClicked;
        }

        // Compute positions/dimensions in ratio of the current window
        double X  = (double)(xmin) / (double)Zooming::DEFAULT_LEVEL_POWER_2;
        double Y  = (double)(ymin) / (double)Zooming::DEFAULT_LEVEL_POWER_2;
        double dX = (double)(xmax - xmin) / (double)Zooming::DEFAULT_LEVEL_POWER_2;
        double dY = (double)(ymax - ymin) / (double)Zooming::DEFAULT_LEVEL_POWER_2;

        // Check for out of bounds
        X  = ( X  < 0. ) ? 0. : X;
        Y  = ( Y  < 0. ) ? 0. : Y;
        dX = ( (X+dX) > 1. ) ? 1. - X : dX;
        dY = ( (Y+dY) > 1. ) ? 1. - Y : dY;

        // Translate to the full display
        X  = X  * m_camera.m_cameraDx + m_camera.m_cameraX;
        Y  = Y  * m_camera.m_cameraDy + m_camera.m_cameraY;
        dX = dX * m_camera.m_cameraDx;
        dY = dY * m_camera.m_cameraDy;

        m_drawTempSelection = 0;

        // Return if in the upper triangular part
        if ( (Y + dY) < X )
        {
            update();
            return;
        }

        // Position is valid, we save it
        m_savedPositions.push(m_camera);

        m_camera.m_cameraX  = X;
        m_camera.m_cameraY  = Y;
        m_camera.m_cameraDx = dX;
        m_camera.m_cameraDy = dY;

        refreshCamera();

        m_mouseXClicked = -1;
        m_mouseYClicked = -1;
    }
}

void MatrixGLWidget::refreshCamera()
{
    m_zooming->move( m_camera.m_cameraX, m_camera.m_cameraX + m_camera.m_cameraDx,
                     m_camera.m_cameraY, m_camera.m_cameraY + m_camera.m_cameraDy );
    update();
}
