/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2018 Pelican Mapping
* MIT License
*/
#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_MPGEOMETRY
#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_MPGEOMETRY 1

#include "Common"
#include "TileNode"
#include "TileNodeRegistry"
#include <osg/Geometry>
#include <osg/buffered_value>
#include <osgEarth/Map>
#include "MapFrame"

using namespace osgEarth;

namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
{
    /**
     * A Geometry that will draw its primitive sets multiple time,
     * once for each configured "layer". For rendering multipass
     * data from a single cull.
     */
    class MPGeometry : public osg::Geometry
    {
    public:
        /**
         * Per-tile attribution for use with the MPGeometry.
         */
        struct Layer
        {
            Layer()
            {
                _texMatUniformID = ~0;
                _layerID = 0u;
                _opaque = true;
            }

            osgEarth::UID                  _layerID;
            osg::ref_ptr<const ImageLayer> _imageLayer;
            osg::ref_ptr<osg::Texture>     _tex;
            osg::ref_ptr<osg::Vec2Array>   _texCoords;
            osg::ref_ptr<osg::Texture>     _texParent;
            osg::Matrixf                   _texMatParent; // yes, must be a float matrix
            bool                           _opaque;

            // for shared layers only:
            osg::Matrixf                   _texMat;          // yes, must be a float matrix
            unsigned                       _texMatUniformID; // uniform location ID

            // in support of std::find
            inline bool operator == (const osgEarth::UID& rhs) const {
                return _layerID == rhs;
            }
        };

    public:
        mutable MapFrame           _frame;
        mutable std::vector<Layer> _layers;
        mutable Threading::Mutex   _frameSyncMutex;

        // uniform name IDs.
        unsigned _uidUniformNameID;
        unsigned _birthTimeUniformNameID;
        unsigned _orderUniformNameID;
        unsigned _opacityUniformNameID;
        unsigned _texMatParentUniformNameID;
        unsigned _tileKeyUniformNameID;
        unsigned _minRangeUniformNameID;
        unsigned _maxRangeUniformNameID;

        // Data stored for each graphics context:
        struct PerContextData {
            PerContextData() : birthTime(-1.0f), lastFrame(0) { }
            float    birthTime;
            unsigned lastFrame;
        };
        mutable osg::buffered_object<PerContextData> _pcd;


        mutable osg::Vec4f _tileKeyValue;

        osg::ref_ptr<osg::Vec2Array> _tileCoords; // [0..1] unified tile coordinates

        int _imageUnit;          // image unit for primary texture
        int _imageUnitParent;    // image unit for secondary (parent) texture
        int _elevUnit;           // image unit for elevation texture

        bool _supportsGLSL;

        osg::ref_ptr<osg::Texture> _elevTex;

        mutable osg::ref_ptr<osg::PrimitiveSet> _patchTriangles;

    public:
        
        // construct a new MPGeometry.
        MPGeometry(const TileKey& key, const MapFrame& frame, int primaryImageUnit);

        // sets an image unit to use for parent texture blending.
        void setParentImageUnit(int value) { _imageUnitParent = value; }

        // render all passes of the geometry.
        void renderPrimitiveSets(osg::State& state, bool renderColor, bool usingVBOs) const;

        // validate the geometry is OK.
        void validate();

    public: // osg::Geometry overrides

        // override so we can properly release the GL buffer objects
        // that are not tracked by the Geometry itself but rather are
        // stored in the LayerRenderData.
        void releaseGLObjects(osg::State* state) const;
        void resizeGLObjectBuffers(unsigned maxSize);
        void compileGLObjects( osg::RenderInfo& renderInfo ) const;

        // this is copied mostly from osg::Geometry, but we've removed 
        // all the code that handles non-fastPath (don't need it) and
        // called into our custom renderPrimitiveSets method.
        void drawImplementation(osg::RenderInfo& renderInfo) const;
        
        // override to correctly process GL_PATCHES if necessary
        bool supports(const osg::PrimitiveFunctor&) const { return true; }
        void accept(osg::PrimitiveFunctor& functor) const;

        bool supports(const osg::PrimitiveIndexFunctor&) const { return true; }
        void accept(osg::PrimitiveIndexFunctor& functor) const;

        // recalculate the bound for the tile key uniform.
#if OSG_VERSION_GREATER_THAN(3,3,1)
        osg::BoundingBox computeBoundingBox() const;
#else
        osg::BoundingBox computeBound() const;
#endif

    protected:
#if OSG_MIN_VERSION_REQUIRED(3,5,9)
        virtual osg::VertexArrayState* createVertexArrayStateImplementation(osg::RenderInfo& renderInfo) const;
#elif OSG_MIN_VERSION_REQUIRED(3,5,6)
        virtual osg::VertexArrayState* createVertexArrayState(osg::RenderInfo& renderInfo) const;
#endif

    public:
        META_Object(osgEarth, MPGeometry);
        MPGeometry();
        MPGeometry(const MPGeometry& rhs, const osg::CopyOp& cop);
        virtual ~MPGeometry() { }
    };

} } } // namespace osgEarth::Drivers::MPTerrainEngine

#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_MPGEOMETRY
