/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of Qt 3D Studio.
**
** $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$
**
****************************************************************************/

#ifndef ACTIVATEDSURFACE_P_H
#define ACTIVATEDSURFACE_P_H

#include <private/dragonoptional_p.h>

#include <Qt3DRender/qabstracttexture.h>
#include <Qt3DRender/qmemorybarrier.h>

#include <private/dragonglbuffer_p.h>
#include <private/dragonglshader_p.h>
#include <private/dragonopenglvertexarrayobject_p.h>
#include <private/dragonrendercommand_p.h>
#include <private/dragonshader_p.h>
#include <private/dragonvaluecontainer_p.h>
#include <private/dragongraphicshelperinterface_p.h>
#include <private/dragonuniform_p.h>

#include <Qt3DRender/private/qgraphicsapifilter_p.h>
#include <private/dragonmutable_p.h>

#include <QColor>
#include <QOpenGLContext>
#include <QSharedPointer>

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists for the convenience
// of other Qt classes.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

QT_BEGIN_NAMESPACE

class QOpenGLContext;
class QSurface;

namespace Qt3DRender {

namespace Render {
class SurfaceLocker;
} // namespace Render

namespace Dragon {

struct ClearBackBufferInfo;
struct AttachmentPack;
struct GLTexture;
class Geometry;
class Attribute;
struct RenderView;
struct CameraMatrices;
struct BlitFramebufferInfo;

class DrawContext
{
public:
    ~DrawContext();
private:
    DrawContext(QOpenGLContext *openGLContext, QSurface *surface, bool autoSwapBuffers);
    QOpenGLContext *m_openGLContext = nullptr;
    QSurface *m_surface = nullptr;
    bool m_autoSwapBuffers = true;

    friend class ActivatedSurface;
};

class ActivatedSurface
{
public:
    ActivatedSurface(QSurface *surface, QOpenGLContext *glContext, Render::SurfaceLocker *lock);
    ActivatedSurface(const ActivatedSurface &) = delete;
    ActivatedSurface(ActivatedSurface &&) = default;
    ActivatedSurface &operator=(const ActivatedSurface &) = delete;
    ActivatedSurface &operator=(ActivatedSurface &&) = default;

    ~ActivatedSurface();

    bool isValid();
    void setGlHelper(QSharedPointer<Dragon::GraphicsHelperInterface> helper);
    // TODO this is not really const and needs a better solution
    Dragon::GraphicsHelperInterface *glHelper() const { return m_glHelper.data(); }
    QSharedPointer<Dragon::GraphicsHelperInterface> resolveHighestOpenGLFunctions() const;

    // commands
    void clearBackBuffer(const ClearBackBufferInfo &info);
    void memoryBarrier(const QMemoryBarrier::Operations &operations);
    void activateRenderTarget(GLuint fboId,
                              const AttachmentPack &attachments);
    void viewport(const QRectF &viewport, const QSize &surfaceSize);
    Immutable<GLVertexArrayObject> createVertexArrayObject(VAOIdentifier key,
        const Mutable<GLShader> &uploadedShader,
        const Immutable<Geometry> &geometry,
        const ValueContainer<Attribute> &attributes,
        const MutableContainer<GLBuffer> &uploadedBuffers);
    GraphicsApiFilterData contextInfo();

    bool activateShader(const Immutable<GLShader> &shader);
    bool bindVertexArrayObject(const Immutable<GLVertexArrayObject> &vao);

    GLBuffer createGLBuffer(const Immutable<LoadedBuffer> &buffer);
    bool bindGLBuffer(const Mutable<GLBuffer> &buffer, GLBuffer::Type type);
    bool bindParameters(const Immutable<RenderView> &renderView, const Immutable<CameraMatrices> &cameraMatrices, const MutableContainer<GLBuffer> &glBuffers,
                        const MutableContainer<GLTexture> &glTextures, int maxTextureImageUnits,
                        const Immutable<GLShader> &glShader, const Immutable<Matrix4x4> &worldTransform,
                        const ParameterNameToParameterMap &parameterMap);

//    bool performDraw(const RenderCommand &command,
//                     const Value<GLShader> &glShader,
//                     const ValueContainer<Attribute> &attributes, const Value<GLVertexArrayObject> &vao);
    QOpenGLContext *openGLContext() const;
    // TODO make private
    void applyState(const StateVariant &stateVariant);

    // TODO consider moving where they are used
    GLuint createRenderTarget(Qt3DCore::QNodeId renderTargetNodeId,
                              const AttachmentPack &attachments,
                              const MutableContainer<GLTexture> &glTextures);
    GLuint updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId,
                              const AttachmentPack &attachments,
                              bool isActiveRenderTarget,
                              const MutableContainer<GLTexture> &glTextures);
    QSize bindFrameBufferAttachmentHelper(GLuint fboId,
                                         const AttachmentPack &attachments,
                                         const MutableContainer<GLTexture> &glTextures);
    void activateDrawBuffers(const AttachmentPack &attachments);
    // Parameter/uniform functions
    void applyUniform(const ShaderUniform &description, const UniformValue &v);

    DrawContext beginDrawing(bool autoSwapBuffers);

    void blitFramebuffer(const BlitFramebufferInfo &blitFramebufferInfo, uint defaultFboId, const MutableContainer<GLTexture> &glTextures);
    RenderStateSet applyStateSet(const RenderStateSet &previous, const RenderStateSet &next);
private:
    // Cached clear data
    Optional<QColor> m_clearColor;
    Optional<float> m_clearDepth;
    Optional<int> m_clearStencil;
    // Cached shader data
    ProgramDNA m_activeShaderDNA = 0;
    // TODO difference between currentSurface and surface is what?
    QSurface *m_surface = nullptr;
    bool m_valid = false;
    QSharedPointer<Dragon::GraphicsHelperInterface> m_glHelper;

    // Frame buffer
    GLuint m_activeFBO = 0;

    // Render targets
    //    QHash<Qt3DCore::QNodeId, HGLBuffer> m_renderBufferHash;
    QHash<Qt3DCore::QNodeId, GLuint> m_renderTargets;
    QHash<GLuint, QSize> m_renderTargetsSize;
    QAbstractTexture::TextureFormat m_renderTargetFormat;

    // Context
    bool m_supportsVAO = false;
    GLuint m_defaultFBO = 0;


    // TODO consider removing
    QSize m_surfaceSize;
    QRectF m_viewport;

    // TODO reconsider active textures strategy
    Immutable<RenderStateSet> m_currentStateSet;

    // Set to be mutable because some functions don't change the state, but still
    // need to access the context. Note that mutable is strictly not necessary
    // because it's a pointer.
    mutable QOpenGLContext *m_glContext = nullptr;
    QSize renderTargetSize(const QSize &surfaceSize) const;

    template<UniformType>
    void applyUniformHelper(const ShaderUniform &, const UniformValue &) const
    {
        Q_ASSERT_X(false, Q_FUNC_INFO, "Uniform: Didn't provide specialized apply() implementation");
    }
    void resetMasked(qint64 maskOfStatesToReset);
};

// TODO consider a template-only (macro-free) implementation
#define QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformTypeEnum, BaseType, Func) \
template<> \
void ActivatedSurface::applyUniformHelper<UniformTypeEnum>(const ShaderUniform &description, const UniformValue &value) const;

QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Float, float, glUniform1fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Vec2, float, glUniform2fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Vec3, float, glUniform3fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Vec4, float, glUniform4fv)

// OpenGL expects int* as values for booleans
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Bool, int, glUniform1iv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::BVec2, int, glUniform2iv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::BVec3, int, glUniform3iv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::BVec4, int, glUniform4iv)

QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Int, int, glUniform1iv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::IVec2, int, glUniform2iv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::IVec3, int, glUniform3iv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::IVec4, int, glUniform4iv)

QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::UInt, uint, glUniform1uiv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::UIVec2, uint, glUniform2uiv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::UIVec3, uint, glUniform3uiv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::UIVec4, uint, glUniform4uiv)

QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat2, float, glUniformMatrix2fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat3, float, glUniformMatrix3fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat4, float, glUniformMatrix4fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat2x3, float, glUniformMatrix2x3fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat3x2, float, glUniformMatrix3x2fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat2x4, float, glUniformMatrix2x4fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat4x2, float, glUniformMatrix4x2fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat3x4, float, glUniformMatrix3x4fv)
QT3D_DRAGON_UNIFORM_TYPE_PROTO(UniformType::Mat4x3, float, glUniformMatrix4x3fv)

} // namespace Dragon
} // namespace Qt3DRender

QT_END_NAMESPACE

#endif // ACTIVATEDSURFACE_P_H
