/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2014 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library 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
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSGTERRAIN_GEOMETRYPOOL
#define OSGTERRAIN_GEOMETRYPOOL 1

#include <osg/Geometry>
#include <osg/MatrixTransform>
#include <osg/Program>

#include <OpenThreads/Mutex>

#include <osgTerrain/TerrainTile>


namespace osgTerrain {

extern OSGTERRAIN_EXPORT const osgTerrain::Locator* computeMasterLocator(const osgTerrain::TerrainTile* tile);


class OSGTERRAIN_EXPORT SharedGeometry : public osg::Drawable
{
    public:
        SharedGeometry();

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

        META_Node(osgTerrain, 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 setColorArray(osg::Array* array) { _colorArray = array; }
        osg::Array* getColorArray() { return _colorArray.get(); }
        const osg::Array* getColorArray() const { return _colorArray.get(); }

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

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


        typedef std::vector<unsigned int> VertexToHeightFieldMapping;

        void setVertexToHeightFieldMapping(const VertexToHeightFieldMapping& vthfm) { _vertexToHeightFieldMapping = vthfm; }

        VertexToHeightFieldMapping& getVertexToHeightFieldMapping() { return _vertexToHeightFieldMapping; }
        const VertexToHeightFieldMapping& getVertexToHeightFieldMapping() const { return _vertexToHeightFieldMapping; }


        osg::VertexArrayState* createVertexArrayStateImplementation(osg::RenderInfo& renderInfo) const;

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

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

        void resizeGLObjectBuffers(unsigned int maxSize);
        void releaseGLObjects(osg::State* state) const;

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

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

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

        virtual bool supports(const osg::PrimitiveIndexFunctor&) const { return true; }
        virtual 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::DrawElements> _drawElements;

        VertexToHeightFieldMapping      _vertexToHeightFieldMapping;
};

class OSGTERRAIN_EXPORT GeometryPool : public osg::Referenced
{
    public:
        GeometryPool();

        struct GeometryKey
        {
            GeometryKey(): sx(0.0), sy(0.0), y(0.0), nx(0), ny(0) {}

            bool operator < (const GeometryKey& rhs) const
            {
                if (sx<rhs.sx) return true;
                if (sx>rhs.sx) return false;

                if (sx<rhs.sx) return true;
                if (sx>rhs.sx) return false;

                if (y<rhs.y) return true;
                if (y>rhs.y) return false;

                if (nx<rhs.nx) return true;
                if (nx>rhs.nx) return false;

                return (ny<rhs.ny);
            }

            double sx;
            double sy;
            double y;

            int nx;
            int ny;
        };

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

        virtual bool createKeyForTile(TerrainTile* tile, GeometryKey& key);

        enum LayerType
        {
            HEIGHTFIELD_LAYER,
            COLOR_LAYER,
            CONTOUR_LAYER
        };

        typedef std::vector<LayerType> LayerTypes;
        typedef std::map<LayerTypes, osg::ref_ptr<osg::Program> > ProgramMap;

        osg::StateSet* getRootStateSetForTerrain(Terrain* terrain);

        virtual osg::ref_ptr<osg::Program> getOrCreateProgram(LayerTypes& layerTypes);

        virtual osg::ref_ptr<SharedGeometry> getOrCreateGeometry(osgTerrain::TerrainTile* tile);

        virtual osg::ref_ptr<osg::MatrixTransform> getTileSubgraph(osgTerrain::TerrainTile* tile);

        virtual void applyLayers(osgTerrain::TerrainTile* tile, osg::StateSet* stateset);

    protected:
        virtual ~GeometryPool();

        OpenThreads::Mutex      _geometryMapMutex;
        GeometryMap             _geometryMap;

        OpenThreads::Mutex      _programMapMutex;
        ProgramMap              _programMap;

        osg::ref_ptr<osg::StateSet>     _rootStateSet;
        bool                            _rootStateSetAssigned;
};


class OSGTERRAIN_EXPORT HeightFieldDrawable : public osg::Drawable
{
    public:
        HeightFieldDrawable();

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

        META_Node(osgTerrain, HeightFieldDrawable);

        void setHeightField(osg::HeightField* hf) { _heightField = hf; }
        osg::HeightField* getHeightField() { return _heightField.get(); }
        const osg::HeightField* getHeightField() const { return _heightField.get(); }

        void setGeometry(SharedGeometry* geom) { _geometry = geom; }
        SharedGeometry* getGeometry() { return _geometry.get(); }
        const SharedGeometry* getGeometry() const { return _geometry.get(); }

        void setVertices(osg::Vec3Array* vertices) { _vertices = vertices; }
        osg::Vec3Array* getVertices() { return _vertices.get(); }
        const osg::Vec3Array* getVertices() const { return _vertices.get(); }

        virtual void drawImplementation(osg::RenderInfo& renderInfo) const;
        virtual void compileGLObjects(osg::RenderInfo& renderInfo) const;
        virtual void resizeGLObjectBuffers(unsigned int maxSize);
        virtual void releaseGLObjects(osg::State* state=0) const;


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

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

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

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

protected:

        virtual ~HeightFieldDrawable();

        osg::ref_ptr<osg::HeightField>  _heightField;
        osg::ref_ptr<SharedGeometry>    _geometry;
        osg::ref_ptr<osg::Vec3Array>    _vertices;
};



}

#endif
