/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit 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_TILE_DRAWABLE
#define OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_TILE_DRAWABLE 1

#include "Common"
#include "TileNode"
#include "TileNodeRegistry"
#include "RenderBindings"
#include "MPTexture"

#include <osg/Geometry>
#include <osg/buffered_value>
#include <osgEarth/Map>
#include <osgEarth/MapFrame>

using namespace osgEarth;

namespace osgEarth { namespace Drivers { namespace RexTerrainEngine
{
    /**
     * A Geometry that will draw its primitive sets multiple time,
     * once for each configured "layer". For rendering multipass
     * data from a single cull.
     */
    class TileDrawable : public osg::Drawable
    {
    public:
        // uniform name IDs.
        unsigned _uidUniformNameID;
        unsigned _orderUniformNameID;
        unsigned _opacityUniformNameID;
        unsigned _texMatrixUniformNameID;
        unsigned _texMatrixParentUniformNameID;
        unsigned _texParentExistsUniformNameID;
        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 _tileMorphCValue;
        mutable osg::Vec4f _tileGridDimsValue;
        mutable osg::Vec4f _tileExtentsValue;
        RenderBindings _bindings;

        bool _supportsGLSL;

        // underlying geometry, possible shared between this tile and other.
        osg::ref_ptr<osg::Geometry> _geom;

        // tile dimensions
        int _tileSize;

        bool _drawPatch;

        osg::ref_ptr<MPTexture> _mptex;

        int _textureImageUnit;
        int _textureParentImageUnit;

        const TileKey _key;

        osg::ref_ptr<const osg::Image> _elevationRaster;
        osg::Matrixf                   _elevationScaleBias;

        int _skirtSize;

        float* _heightCache;

    public:
        
        // construct a new TileDrawable that fronts an osg::Geometry
        TileDrawable(
            const TileKey&        key,
            const RenderBindings& bindings,
            osg::Geometry*        geometry,
            int                   tileSize,
            int                   skirtSize);

        // whether to draw this as a multi-image geometry or as a tesselation patch.
        void setDrawAsPatches(bool value) { _drawPatch = value; }

        /** Sets the multipass texture to use when rendering geometry. */
        void setMPTexture(MPTexture* tex) { _mptex = tex; }

    public:

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

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

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

        void drawSurface(osg::RenderInfo& renderInfo, bool renderColor) const;

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

        // Sets the elevation raster for this tile
        void setElevationRaster(const osg::Image* image, const osg::Matrixf& scaleBias);
        const osg::Image* getElevationRaster() const;
        const osg::Matrixf& getElevationMatrix() const;

    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;

    public: // osg::Drawable overrides

        // These methods defer functors (like stats collection) to the underlying
        // (possibly shared) geometry instance.
        bool supports(const osg::Drawable::AttributeFunctor& f) const { return true; }
        void accept(osg::Drawable::AttributeFunctor& f) { if ( _geom.valid() ) _geom->accept(f); }

        bool supports(const osg::Drawable::ConstAttributeFunctor& f) const { return true; }
        void accept(osg::Drawable::ConstAttributeFunctor& f) const { if ( _geom.valid() ) _geom->accept(f); }

        bool supports(const osg::PrimitiveFunctor& f) const { return true; }
        void accept(osg::PrimitiveFunctor& f) const;
        
        /** indexed functor is NOT supported since we need to apply elevation dynamically */
        bool supports(const osg::PrimitiveIndexFunctor& f) const { return false; }

    public:
        META_Object(osgEarth, TileDrawable);
        TileDrawable() : osg::Drawable(){}
        TileDrawable(const TileDrawable& rhs, const osg::CopyOp& cop) : osg::Drawable(rhs, cop) {}

        virtual ~TileDrawable();
    };

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

#endif // OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_RexGEOMETRY
