/* osgEarth
 * Copyright 2008-2014 Pelican Mapping
 * MIT License
 */
#ifndef OSGEARTH_REX_TERRAIN_LAYER_DRAWABLE_H
#define OSGEARTH_REX_TERRAIN_LAYER_DRAWABLE_H 1

#include "DrawTileCommand"
#include "DrawState"
#include "TileRenderModel"

#include <osgEarth/ImageLayer>
#include <osgEarth/GLUtils>
#include <osgEarth/TextureArena>

#include <vector>

using namespace osgEarth;

namespace osgEarth { namespace REX
{
    class TerrainRenderData;

    /**
     * Drawable for single "Layer" i.e. rendering pass. 
     * It is important that LayerDrawables be rendered in the order in which
     * they appear. Since all LayerDrawables share a common bounds, this 
     * should happen automatically, but let's keep an eye out for trouble.
     */
    class LayerDrawable : public osg::Drawable
    {
    public:
        LayerDrawable();

        // The colletion of tiles to render for this layer
        DrawTileCommands _tiles;

        // Determines whether to use the default surface shader program
        Layer::RenderType _renderType = Layer::RENDERTYPE_TERRAIN_SURFACE;

        // Pointer back to the actual Map layer, if there is one
        const Layer* _layer = nullptr;

        // If _layer is a VisibleLayer, this will be set as well, otherwise nullptr
        const VisibleLayer* _visibleLayer = nullptr;

        // If _layer is an ImageLayer, this will be set as well, otherwise nullptr
        const ImageLayer* _imageLayer = nullptr;

        // If _layer is a PatchLayer, this will be set, otherwise nullptr
        const PatchLayer* _patchLayer = nullptr;

        // Layer render order, which is pushed into a Uniform at render time.
        // This value is assigned at cull time by RexTerrainEngineNode.
        int _drawOrder = 0;

        // Layer render order (only terrain surface layers)
        int _surfaceDrawOrder = 0;

        // The last layer to render will have this flag set, which will
        // prompt the render to dirty the osg::State to prevent corruption.
        // This flag is set at cull time by RexTerrainEngineNode.
        bool _clearOsgState = false;

        // Reference the terrain-wide state
        DrawState::Ptr _drawState;

        // Whether to render this layer.
        bool _draw = true;

        // shared context
        class EngineContext* _context = nullptr;

    public: // osg::Drawable
        
        // All LayerDrawables share the common terrain bounds.
        osg::BoundingSphere computeBound() const override { return _drawState->_bs; }
        osg::BoundingBox computeBoundingBox() const override { return _drawState->_box; }
        bool supports(const osg::PrimitiveFunctor&) const override { return true; }
        void accept(osg::PrimitiveFunctor& functor) const override;
        bool supports(const osg::PrimitiveIndexFunctor&) const override { return true; }
        void accept(osg::PrimitiveIndexFunctor&) const override;

        void accept(osg::NodeVisitor& nv) override {
            osg::Drawable::accept(nv);
        }

    protected:

        // overriden to prevent OSG from releasing GL objects on an attached stateset.
        virtual ~LayerDrawable();

    private:
    };


    /**
     * Drawable for single "Layer" i.e. rendering pass.
     * It is important that LayerDrawables be rendered in the order in which
     * they appear. Since all LayerDrawables share a common bounds, this
     * should happen automatically, but let's keep an eye out for trouble.
     */
    class LayerDrawableGL3 : public LayerDrawable
    {
    public:
        LayerDrawableGL3();

    public: // osg::Drawable

        void drawImplementation(osg::RenderInfo& ri) const override;

    protected:

        // overriden to prevent OSG from releasing GL objects on an attached stateset.
        virtual ~LayerDrawableGL3();
    };


    /**
     * Drawable for single "Layer" i.e. rendering pass.
     * It is important that LayerDrawables be rendered in the order in which
     * they appear. Since all LayerDrawables share a common bounds, this
     * should happen automatically, but let's keep an eye out for trouble.
     */
    class LayerDrawableNVGL : public LayerDrawable
    {
    public:
        LayerDrawableNVGL();

    public: // osg::Drawable

        void drawImplementation(osg::RenderInfo& ri) const override;
        void releaseGLObjects(osg::State*) const override;
        void resizeGLObjectBuffers(unsigned size) override;
        void accept(osg::NodeVisitor& nv) override;

    protected:

        virtual ~LayerDrawableNVGL();

    private:

        // GL objects maintained entirely on a per-State/GC basis.
        // Since this is a Drawable, the data here will differ per view
        // and per camera, and therefore cannot be shared across graphics
        // contexts no matter what.
        struct GLObjects : public PerStateGLObjects
        {
            // ssbo containing globally shared constant data
            GLBuffer::Ptr shared;

            // ssbo containing per-tile render info
            GLBuffer::Ptr tiles;

            // VAO for rendering with bindless buffers
            GLVAO::Ptr vao;

            // bindless draw commands
            GLBuffer::Ptr commands;

            osg::GLExtensions* ext;

            void(GL_APIENTRY * glMultiDrawElementsIndirectBindlessNV)
                (GLenum, GLenum, const GLvoid*, GLsizei, GLsizei, GLint);

            void(GL_APIENTRY * glVertexAttribFormat)
                (GLuint, GLint, GLenum, GLboolean, GLuint);
        };

        struct RenderState
        {
            RenderState();

            bool dirty;
            int revision = -1;

            DrawTileCommands tiles;
            std::vector<GL4Tile> tilebuf;

            std::vector<DrawElementsIndirectBindlessCommandNV> commands;
            osg::buffered_object<GLObjects> globjects;
        };
        mutable RenderState _rs;

        void refreshRenderState();
    };


    // Straight list of LayerDrawables.
    using LayerDrawableList = std::vector<osg::ref_ptr<LayerDrawable>>;
    using LayerDrawableMap = std::unordered_map<UID, osg::ref_ptr<LayerDrawable>>;

} } // namespace 

#endif // OSGEARTH_REX_TERRAIN_LAYER_DRAWABLE_H
