/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2008-2014 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*/
#ifndef OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_GEOMETRY_POOL
#define OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_GEOMETRY_POOL 1

#include "Common"
#include "MaskGenerator"
#include "RexTerrainEngineOptions"
#include <osgEarth/MapInfo>
#include <osgEarth/TileKey>
#include <osgEarth/ThreadingUtils>
#include <osgEarth/ResourceReleaser>
#include <osg/Geometry>

#if OSG_MIN_VERSION_REQUIRED(3,5,9)
#define SUPPORTS_VAO 1
#endif

namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
{
    using namespace osgEarth;

    // Adapted from osgTerrain shared geometry class.
    class /*internal*/ SharedGeometry : public osg::Drawable
    {
    public:
        SharedGeometry();

        SharedGeometry(const SharedGeometry&, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);

        META_Node(osgEarthRex, SharedGeometry);

        void setVertexArray(osg::Array* array) { _vertexArray = array; }
        osg::Array* getVertexArray() { return _vertexArray.get(); }
        const osg::Array* getVertexArray() const  { return _vertexArray.get(); }

        void setNormalArray(osg::Array* array) { _normalArray = array; }
        osg::Array* getNormalArray() { return _normalArray.get(); }
        const osg::Array* getNormalArray() const { return _normalArray.get(); }

        void setTexCoordArray(osg::Array* array) { _texcoordArray = array; }
        osg::Array* getTexCoordArray() { return _texcoordArray.get(); }
        const osg::Array* getTexCoordArray() const { return _texcoordArray.get(); }

        void setNeighborArray(osg::Array* array) { _neighborArray = array; }
        osg::Array* getNeighborArray() { return _neighborArray.get(); }
        const osg::Array* getNeighborArray() const { return _neighborArray.get(); }

        void setNeighborNormalArray(osg::Array* array) { _neighborNormalArray = array; }
        osg::Array* getNeighborNormalArray() { return _neighborNormalArray.get(); }
        const osg::Array* getNeighborNormalArray() const { return _neighborNormalArray.get(); }

        void setDrawElements(osg::DrawElements* array) { _drawElements = array; }
        osg::DrawElements* getDrawElements() { return _drawElements.get(); }
        const osg::DrawElements* getDrawElements() const { return _drawElements.get(); }

        void setMaskElements(osg::DrawElements* array) { _maskElements = array; }
        osg::DrawElements* getMaskElements() { return _maskElements.get(); }
        const osg::DrawElements* getMaskElements() const { return _maskElements.get(); }

        // convert to a "real" geometry object
        osg::Geometry* makeOsgGeometry();

        // whether this geometry contains anything
        bool empty() const;

    public: // osg::Drawable

#ifdef SUPPORTS_VAO
        osg::VertexArrayState* createVertexArrayStateImplementation(osg::RenderInfo& renderInfo) const;
#endif

        void drawImplementation(osg::RenderInfo& renderInfo) const;

        void resizeGLObjectBuffers(unsigned int maxSize);
        void releaseGLObjects(osg::State* state) const;
        
        bool supports(const osg::Drawable::AttributeFunctor&) const { return true; }
        void accept(osg::Drawable::AttributeFunctor&);

        bool supports(const osg::Drawable::ConstAttributeFunctor&) const { return true; }
        void accept(osg::Drawable::ConstAttributeFunctor&) const;

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

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

    protected:

        virtual ~SharedGeometry();

        osg::ref_ptr<osg::Array>        _vertexArray;
        osg::ref_ptr<osg::Array>        _normalArray;
        osg::ref_ptr<osg::Array>        _colorArray;
        osg::ref_ptr<osg::Array>        _texcoordArray;
        osg::ref_ptr<osg::Array>        _neighborArray;
        osg::ref_ptr<osg::Array>        _neighborNormalArray;
        osg::ref_ptr<osg::DrawElements> _drawElements;
        osg::ref_ptr<osg::DrawElements> _maskElements;

    private:

        friend struct DrawTileCommand;
        mutable osg::buffered_object<GLenum> _ptype;
    };

    /**
     * Pool of terrain tile geometries.
     *
     * In a geocentric map, every tile at a particular LOD and a particular latitudinal
     * (north-south) extent shares exactly the same geometry; each tile is just shifted
     * and rotated differently. Therefore we can use the same Geometry for all tiles that
     * share the same LOD and same min/max latitude in a geocentric map. In a projected
     * map, all tiles at a given LOD share the same geometry regardless of extent, so eve
     * more sharing is possible.
     *
     * This object creates and returns geometries based on TileKeys, sharing instances
     * whenever possible. Concept adapted from OSG's osgTerrain::GeometryPool.
     */
    class GeometryPool : public osg::Group
    {
    public:
        /** Construct the geometry pool */
        GeometryPool(const RexTerrainEngineOptions& options);

        /** Sets an object to release unused GL resources */
        void setReleaser(ResourceReleaser* releaser);

    public:
        /**
         * Hashtable key for unique (and therefore shareable) geometries.
         */
        struct GeometryKey
        {
            GeometryKey() :
                lod(-1),
                tileY(0),
                patch(false),
                size(0u)
                {
                }

            bool operator < (const GeometryKey& rhs) const
            {
                if (lod < rhs.lod) return true;
                if (lod > rhs.lod) return false;
                if (tileY < rhs.tileY) return true;
                if (tileY > rhs.tileY) return false;
                if (size < rhs.size) return true;
                if (size > rhs.size) return false;
                if (patch == false && rhs.patch == true) return true;
                return false;
            }

            int      lod;
            int      tileY;
            bool     patch;
            unsigned size;
        };

        typedef std::map<GeometryKey, osg::ref_ptr<SharedGeometry> > GeometryMap;

        /**
         * Gets the Geometry associated with a tile key, creating a new one if
         * necessary and storing it in the pool.
         */
        void getPooledGeometry(
            const TileKey&               tileKey,
            const MapInfo&               mapInfo,
            unsigned                     tileSize,
            MaskGenerator*               maskSet,
            osg::ref_ptr<SharedGeometry>& out);

        /**
         * The number of elements (incides) in the terrain skirt, if applicable
         */
        int getNumSkirtElements(unsigned tileSize) const;

        /**
         * Are we doing pooling?
         */
        bool isEnabled() const { return _enabled; }

        /**
         * Clear and reset the pool.
         */
        void clear();


    public: // osg::Node

        /** Perform an update traversal to check for unused resources. */
        void traverse(osg::NodeVisitor& nv);

    protected:
        virtual ~GeometryPool() { }

        mutable Threading::Mutex       _geometryMapMutex;
        GeometryMap                    _geometryMap;
        //unsigned                       _tileSize;
        const RexTerrainEngineOptions& _options; 
        osg::ref_ptr<ResourceReleaser> _releaser;

        mutable osg::ref_ptr<osg::Vec3Array> _sharedTexCoords;
        
        void createKeyForTileKey(
            const TileKey& tileKey, 
            unsigned       size,
            const MapInfo& mapInfo,
            GeometryKey&   out) const;

        SharedGeometry* createGeometry(
            const TileKey& tileKey,
            const MapInfo& mapInfo,
            unsigned       tileSize,
            MaskGenerator* maskSet ) const;

        bool _enabled;
        bool _debug;
    };

} } } // namespace osgEarth::Drivers::RexTerrainEngine

#endif // OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_GEOMETRY_POOL
