/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Data Visualization module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "glstatestore_p.h"
#include <QDebug>
#include <QColor>
#include <QFile>

#ifdef VERBOSE_STATE_STORE
static QFile *beforeFile = 0;
static QFile *afterFile = 0;
#endif

GLStateStore::GLStateStore(QOpenGLContext *context, QObject *parent) :
    QObject(parent),
    QOpenGLFunctions(context)
  #ifdef VERBOSE_STATE_STORE
  , m_map(EnumToStringMap::newInstance())
  #endif
{
    GLint maxVertexAttribs;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);

#ifdef VERBOSE_STATE_STORE
    qDebug() << "GL_MAX_VERTEX_ATTRIBS: " << maxVertexAttribs;
    if (!beforeFile) {
        beforeFile = new QFile(QStringLiteral("state_before.txt"));
        afterFile = new QFile(QStringLiteral("state_after.txt"));
        beforeFile->open(QIODevice::WriteOnly);
        afterFile->open(QIODevice::WriteOnly);
        QDebug beforeInit(beforeFile);
        QDebug afterInit(afterFile);
        beforeInit << "GL states before 'context switch'" << endl;
        afterInit << "GL states after 'context switch'" << endl;
    }
#endif

    m_maxVertexAttribs = qMin(maxVertexAttribs, 2); // Datavis only uses 2 attribs max
    m_vertexAttribArrayEnabledStates.reset(new GLint[maxVertexAttribs]);
    m_vertexAttribArrayBoundBuffers.reset(new GLint[maxVertexAttribs]);
    m_vertexAttribArraySizes.reset(new GLint[maxVertexAttribs]);
    m_vertexAttribArrayTypes.reset(new GLint[maxVertexAttribs]);
    m_vertexAttribArrayNormalized.reset(new GLint[maxVertexAttribs]);
    m_vertexAttribArrayStrides.reset(new GLint[maxVertexAttribs]);
    m_vertexAttribArrayOffsets.reset(new void *[maxVertexAttribs]);

    initGLDefaultState();
}

GLStateStore::~GLStateStore()
{
#ifdef VERBOSE_STATE_STORE
    EnumToStringMap::deleteInstance();
    m_map = 0;
#endif
}

void GLStateStore::storeGLState()
{
#ifdef VERBOSE_STATE_STORE
    printCurrentState(true);
#endif

#if !QT_CONFIG(opengles2)
    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &m_drawFramebuffer);
    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &m_readFramebuffer);
    glGetIntegerv(GL_RENDERBUFFER_BINDING, &m_renderbuffer);
#endif
    glGetFloatv(GL_COLOR_CLEAR_VALUE, m_clearColor);
    m_isBlendingEnabled = glIsEnabled(GL_BLEND);
    m_isDepthTestEnabled = glIsEnabled(GL_DEPTH_TEST);
    glGetBooleanv(GL_DEPTH_WRITEMASK, &m_isDepthWriteEnabled);
    glGetFloatv(GL_DEPTH_CLEAR_VALUE, &m_clearDepth);
    glGetIntegerv(GL_DEPTH_FUNC, &m_depthFunc);
    glGetBooleanv(GL_POLYGON_OFFSET_FILL, &m_polygonOffsetFillEnabled);
    glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &m_polygonOffsetFactor);
    glGetFloatv(GL_POLYGON_OFFSET_UNITS, &m_polygonOffsetUnits);

    glGetIntegerv(GL_CURRENT_PROGRAM, &m_currentProgram);
    glGetIntegerv(GL_ACTIVE_TEXTURE, &m_activeTexture);
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &m_texBinding2D);
    glGetIntegerv(GL_FRONT_FACE, &m_frontFace);
    m_isCullFaceEnabled = glIsEnabled(GL_CULL_FACE);
    glGetIntegerv(GL_CULL_FACE_MODE, &m_cullFaceMode);
    glGetIntegerv(GL_BLEND_EQUATION_RGB, &m_blendEquationRGB);
    glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &m_blendEquationAlpha);
    glGetIntegerv(GL_BLEND_DST_ALPHA, &m_blendDestAlpha);
    glGetIntegerv(GL_BLEND_DST_RGB, &m_blendDestRGB);
    glGetIntegerv(GL_BLEND_SRC_ALPHA, &m_blendSrcAlpha);
    glGetIntegerv(GL_BLEND_SRC_RGB, &m_blendSrcRGB);
    glGetIntegerv(GL_SCISSOR_BOX, m_scissorBox);
    m_isScissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST);

    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &m_boundArrayBuffer);
    glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &m_boundElementArrayBuffer);

    for (int i = 0; i < m_maxVertexAttribs;i++) {
        glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED,
                            &m_vertexAttribArrayEnabledStates[i]);
        glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
                            &m_vertexAttribArrayBoundBuffers[i]);
        glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE, &m_vertexAttribArraySizes[i]);
        glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE, &m_vertexAttribArrayTypes[i]);
        glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
                            &m_vertexAttribArrayNormalized[i]);
        glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &m_vertexAttribArrayStrides[i]);
    }
}

#ifdef VERBOSE_STATE_STORE
void GLStateStore::printCurrentState(bool in)
{
    QFile *file;
    if (in)
        file = beforeFile;
    else
        file = afterFile;

    if (file->isOpen()) {
        QDebug msg(file);
#if !QT_CONFIG(opengles2)
        GLint drawFramebuffer;
        GLint readFramebuffer;
        GLint renderbuffer;
#endif
        GLfloat clearColor[4];
        GLfloat clearDepth;
        GLboolean isBlendingEnabled = glIsEnabled(GL_BLEND);
        GLboolean isDepthTestEnabled = glIsEnabled(GL_DEPTH_TEST);
        GLint depthFunc;
        GLboolean isDepthWriteEnabled;
        GLint currentProgram;
        GLint *vertexAttribArrayEnabledStates = new GLint[m_maxVertexAttribs];
        GLint *vertexAttribArrayBoundBuffers = new GLint[m_maxVertexAttribs];
        GLint *vertexAttribArraySizes = new GLint[m_maxVertexAttribs];
        GLint *vertexAttribArrayTypes = new GLint[m_maxVertexAttribs];
        GLint *vertexAttribArrayNormalized = new GLint[m_maxVertexAttribs];
        GLint *vertexAttribArrayStrides = new GLint[m_maxVertexAttribs];
        GLint activeTexture;
        GLint texBinding2D;
        GLint arrayBufferBinding;
        GLint frontFace;
        GLboolean isCullFaceEnabled = glIsEnabled(GL_CULL_FACE);
        GLint cullFaceMode;
        GLint blendEquationRGB;
        GLint blendEquationAlpha;

        GLint blendDestAlpha;
        GLint blendDestRGB;
        GLint blendSrcAlpha;
        GLint blendSrcRGB;
        GLint scissorBox[4];
        GLboolean isScissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST);
        GLint boundElementArrayBuffer;
        GLboolean polygonOffsetFillEnabled;
        GLfloat polygonOffsetFactor;
        GLfloat polygonOffsetUnits;

        glGetBooleanv(GL_DEPTH_WRITEMASK, &isDepthWriteEnabled);
#if !QT_CONFIG(opengles2)
        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFramebuffer);
        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFramebuffer);
        glGetIntegerv(GL_RENDERBUFFER_BINDING, &renderbuffer);
#endif
        glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor);
        glGetFloatv(GL_DEPTH_CLEAR_VALUE, &clearDepth);
        glGetIntegerv(GL_DEPTH_FUNC, &depthFunc);
        glGetBooleanv(GL_POLYGON_OFFSET_FILL, &polygonOffsetFillEnabled);
        glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &polygonOffsetFactor);
        glGetFloatv(GL_POLYGON_OFFSET_UNITS, &polygonOffsetUnits);

        glGetIntegerv(GL_CURRENT_PROGRAM, &currentProgram);
        glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTexture);
        glGetIntegerv(GL_TEXTURE_BINDING_2D, &texBinding2D );
        glGetIntegerv(GL_FRONT_FACE, &frontFace);
        glGetIntegerv(GL_CULL_FACE_MODE, &cullFaceMode);
        glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB);
        glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha);
        glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDestAlpha);
        glGetIntegerv(GL_BLEND_DST_RGB, &blendDestRGB);
        glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha);
        glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB);
        glGetIntegerv(GL_SCISSOR_BOX, scissorBox);
        glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &boundElementArrayBuffer);
        glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &arrayBufferBinding);

        for (int i = 0; i < m_maxVertexAttribs;i++) {
            glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED,
                                &vertexAttribArrayEnabledStates[i]);
            glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
                                &vertexAttribArrayBoundBuffers[i]);
            glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE, &vertexAttribArraySizes[i]);
            glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE, &vertexAttribArrayTypes[i]);
            glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
                                &vertexAttribArrayNormalized[i]);
            glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &vertexAttribArrayStrides[i]);
        }

        QColor color;
        color.setRgbF(clearColor[0], clearColor[1], clearColor[2]);
        color.setAlphaF(clearColor[3]);

#if !QT_CONFIG(opengles2)
        msg << "---" << endl;
        msg << "    GL_DRAW_FRAMEBUFFER_BINDING "<< drawFramebuffer << endl;
        msg << "    GL_READ_FRAMEBUFFER_BINDING "<< readFramebuffer << endl;
        msg << "    GL_RENDERBUFFER_BINDING " << renderbuffer << endl;
#endif
        msg << "    GL_SCISSOR_TEST " << bool(isScissorTestEnabled) << endl;
        msg << "    GL_SCISSOR_BOX " << m_scissorBox[0] << m_scissorBox[1] << m_scissorBox[2]
            << m_scissorBox[3] << endl;
        msg << "    GL_COLOR_CLEAR_VALUE "<< color << endl;
        msg << "    GL_DEPTH_CLEAR_VALUE "<< clearDepth << endl;
        msg << "    GL_BLEND "<< bool(isBlendingEnabled) << endl;
        msg << "    GL_BLEND_EQUATION_RGB" << m_map->lookUp(blendEquationRGB) << endl;
        msg << "    GL_BLEND_EQUATION_ALPHA" << m_map->lookUp(blendEquationAlpha) << endl;
        msg << "    GL_BLEND_DST_ALPHA" << m_map->lookUp(blendDestAlpha) << endl;
        msg << "    GL_BLEND_DST_RGB" << m_map->lookUp(blendDestRGB) << endl;
        msg << "    GL_BLEND_SRC_ALPHA" << m_map->lookUp(blendSrcAlpha) << endl;
        msg << "    GL_BLEND_SRC_RGB" << m_map->lookUp(blendSrcRGB) << endl;
        msg << "    GL_DEPTH_TEST "<< bool(isDepthTestEnabled) << endl;
        msg << "    GL_DEPTH_WRITEMASK "<< bool(isDepthWriteEnabled) << endl;
        msg << "    GL_POLYGON_OFFSET_FILL" << bool(polygonOffsetFillEnabled) << endl;
        msg << "    GL_POLYGON_OFFSET_FACTOR "<< polygonOffsetFactor << endl;
        msg << "    GL_POLYGON_OFFSET_UNITS "<< polygonOffsetUnits << endl;
        msg << "    GL_CULL_FACE "<< bool(isCullFaceEnabled) << endl;
        msg << "    GL_CULL_FACE_MODE "<< m_map->lookUp(cullFaceMode) << endl;
        msg << "    GL_DEPTH_FUNC "<< m_map->lookUp(depthFunc) << endl;
        msg << "    GL_FRONT_FACE "<< m_map->lookUp(frontFace) << endl;
        msg << "    GL_CURRENT_PROGRAM "<< currentProgram << endl;
        msg << "    GL_ACTIVE_TEXTURE "<< QString("0x%1").arg(activeTexture, 0, 16) << endl;
        msg << "    GL_TEXTURE_BINDING_2D "<< texBinding2D << endl;
        msg << "    GL_ELEMENT_ARRAY_BUFFER_BINDING "<< boundElementArrayBuffer << endl;
        msg << "    GL_ARRAY_BUFFER_BINDING "<< arrayBufferBinding << endl;
        for (int i = 0; i < m_maxVertexAttribs;i++) {
            msg << "    GL_VERTEX_ATTRIB_ARRAY_ENABLED "<< i << "       = "
                <<  bool(vertexAttribArrayEnabledStates[i]) << endl;
            msg << "    GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING"<< i << " = "
                <<  vertexAttribArrayBoundBuffers[i] << endl;
            msg << "    GL_VERTEX_ATTRIB_ARRAY_SIZE"<< i << " = "
                <<  vertexAttribArraySizes[i] << endl;
            msg << "    GL_VERTEX_ATTRIB_ARRAY_TYPE"<< i << " = "
                <<  vertexAttribArrayTypes[i] << endl;
            msg << "    GL_VERTEX_ATTRIB_ARRAY_NORMALIZED"<< i << " = "
                <<  vertexAttribArrayNormalized[i] << endl;
            msg << "    GL_VERTEX_ATTRIB_ARRAY_STRIDE"<< i << " = "
                <<  vertexAttribArrayStrides[i] << endl;
        }
    }
}
#endif

void GLStateStore::restoreGLState()
{
#if !QT_CONFIG(opengles2)
    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_readFramebuffer);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_drawFramebuffer);
    glBindRenderbuffer(GL_RENDERBUFFER_BINDING, m_renderbuffer);
#endif

    if (m_isScissorTestEnabled)
        glEnable(GL_SCISSOR_TEST);
    else
        glDisable(GL_SCISSOR_TEST);

    glScissor(m_scissorBox[0], m_scissorBox[1], m_scissorBox[2], m_scissorBox[3]);
    glClearColor(m_clearColor[0], m_clearColor[1], m_clearColor[2], m_clearColor[3]);
    glClearDepthf(m_clearDepth);
    if (m_isBlendingEnabled)
        glEnable(GL_BLEND);
    else
        glDisable(GL_BLEND);

    if (m_isDepthTestEnabled)
        glEnable(GL_DEPTH_TEST);
    else
        glDisable(GL_DEPTH_TEST);

    if (m_isCullFaceEnabled)
        glEnable(GL_CULL_FACE);
    else
        glDisable(GL_CULL_FACE);

    glCullFace(m_cullFaceMode);

    glBlendEquationSeparate(m_blendEquationRGB, m_blendEquationAlpha);
    glBlendFuncSeparate(m_blendSrcRGB, m_blendDestRGB, m_blendSrcAlpha, m_blendDestAlpha);

    glDepthMask(m_isDepthWriteEnabled);
    glDepthFunc(m_depthFunc);
    glFrontFace(m_frontFace);

    if (m_polygonOffsetFillEnabled)
        glEnable(GL_POLYGON_OFFSET_FILL);
    else
        glDisable(GL_POLYGON_OFFSET_FILL);

    glPolygonOffset(m_polygonOffsetFactor, m_polygonOffsetUnits);

    glUseProgram(m_currentProgram);

    glActiveTexture(m_activeTexture);
    glBindTexture(GL_TEXTURE_2D, m_texBinding2D);

    // Restore bound element array buffer and array buffers
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_boundElementArrayBuffer);
    for (int i = 0; i < m_maxVertexAttribs; i++) {
        if (m_vertexAttribArrayEnabledStates[i])
            glEnableVertexAttribArray(i);
        else
            glDisableVertexAttribArray(i);

        glBindBuffer(GL_ARRAY_BUFFER, m_vertexAttribArrayBoundBuffers[i]);
        glVertexAttribPointer(i, m_vertexAttribArraySizes[i],
                              m_vertexAttribArrayTypes[i],
                              m_vertexAttribArrayNormalized[i],
                              m_vertexAttribArrayStrides[i],
                              m_vertexAttribArrayOffsets[i]);
    }

    glBindBuffer(GL_ARRAY_BUFFER, m_boundArrayBuffer);

#ifdef VERBOSE_STATE_STORE
    printCurrentState(false);
#endif
}

void GLStateStore::initGLDefaultState()
{
#if !QT_CONFIG(opengles2)
    m_drawFramebuffer = 0;
    m_readFramebuffer = 0;
    m_renderbuffer = 0;
#endif
    m_clearColor[0] = m_clearColor[1] = m_clearColor[2] = m_clearColor[3] = 1.0f;
    m_clearDepth = 1.0f;
    m_isBlendingEnabled = GL_FALSE;
    m_isDepthTestEnabled = GL_FALSE;
    m_depthFunc = GL_LESS;
    m_isDepthWriteEnabled = GL_TRUE;
    m_currentProgram = 0;
    m_texBinding2D = 0;
    for (int i = 0; i < m_maxVertexAttribs;i++) {
        m_vertexAttribArrayEnabledStates[i] = GL_FALSE;
        m_vertexAttribArrayBoundBuffers[i] = 0;
        m_vertexAttribArraySizes[i] = 4;
        m_vertexAttribArrayTypes[i] = GL_FLOAT;
        m_vertexAttribArrayNormalized[i] = GL_FALSE;
        m_vertexAttribArrayStrides[i] = 0;
        m_vertexAttribArrayOffsets[i] = 0;
    }
    m_activeTexture = GL_TEXTURE0;
    m_frontFace = GL_CCW;
    m_isCullFaceEnabled = false;
    m_cullFaceMode = GL_BACK;
    m_blendEquationRGB = GL_FUNC_ADD;
    m_blendEquationAlpha = GL_FUNC_ADD;
    m_scissorBox[0] = 0;
    m_scissorBox[1] = 0;
    m_scissorBox[2] = 0;
    m_scissorBox[3] = 0;
    m_isScissorTestEnabled = GL_FALSE;

    m_polygonOffsetFillEnabled = GL_FALSE;
    m_polygonOffsetFactor = 0.0;
    m_polygonOffsetUnits = 0.0;
}
