/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2008-2013 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_ENGINE_OSGTERRAIN_SINGLE_PASS_TERRAIN_TECHNIQUE
#define OSGEARTH_ENGINE_OSGTERRAIN_SINGLE_PASS_TERRAIN_TECHNIQUE 1

#include "CustomTerrainTechnique"
#include "TransparentLayer"

#include <OpenThreads/Atomic>
#include <osg/MatrixTransform>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/StateSet>
#include <osg/Texture2DArray>
#include <osg/Version>
#include <osg/Uniform>

#include <osgTerrain/Locator>
#include <osgEarth/Export>
#include <osgEarth/Progress>
#include <osgEarth/TextureCompositor>
#include <osgEarth/ThreadingUtils>
#include <osgEarthSymbology/Geometry>

#include <queue>

namespace osgEarth_engine_osgterrain
{
    class Tile;
    class TileFrame;

    // --------------------------------------------------------------------------

    /**
     * A terrain technique that uses a single texture unit by compositing image layer textures
     * on the GPU.
     * 
     * This technique works by creating a single "mosaic" texture and copying each image layer's 
     * texture into that mosaic. It then creates a uniform array that conveys the relative offset
     * and scale information of each sub-texture to a shader. The shader then composites the
     * approprate mosaic texels on the GPU.
     *
     * Limitations:
     *
     * This technique is limited by the maximum texture size your GPU will support, since it 
     * creates a single mosaic texture. For example, if your GPU's max texture size is 2048,
     * this technique can support 64 256-pixel layers.
     */
    class SinglePassTerrainTechnique : public CustomTerrainTechnique
    {
    public:
        SinglePassTerrainTechnique( TextureCompositor* compositor =0L ); //osgTerrain::Locator* locator =0L );

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

        META_Object( osgEarth, SinglePassTerrainTechnique );

        /** dtor */
        virtual ~SinglePassTerrainTechnique();

    public: /* overrides */

        virtual void init();

        void setParentTile( Tile* tile ) { _parentTile = tile; }

        void compile( const TileUpdate& updateSpec, ProgressCallback* progress );

        // returns TRUE if a swap occurred and a new subgraph is now in place.
        bool applyTileUpdates();

        /** Traverse the terrain subgraph.*/
        virtual void traverse( osg::NodeVisitor& nv );

    public:

        /**
        * Sets a factor by which to scale elevation height values. By default, this object
        * will get the vertical scale from the osgTerrain::Terrain with which the tile is
        * associated. Setting this value overrides that (or sets it if there is no terrain).
        */
        void setVerticalScaleOverride( float value );

        /**
        * Gets the overriden vertical scale value.
        */
        float getVerticalScaleOverride() const;

        /**
         * Sets whether to try to optimize the triangle orientation based on the elevation values.
         *  If false, 
         */
        void setOptimizeTriangleOrientation(bool optimizeTriangleOrientation);
        bool getOptimizeTriangleOrientation() const;

        /** If State is non-zero, this function releases any associated OpenGL objects for
        * the specified graphics context. Otherwise, releases OpenGL objects
        * for all graphics contexts. */
        virtual void releaseGLObjects(osg::State* = 0) const;

        osg::StateSet* getActiveStateSet() const;

        /** Gets to the underlying transform that parents the actual constructed geometry. */
        osg::Transform* getTransform() const { return _transform.get(); }
        osg::Transform* takeTransform() { return _transform.release(); }

        bool getClearDataAfterCompile() const { return _clearDataAfterCompile;}
        void setClearDataAfterCompile( bool value) { _clearDataAfterCompile = value;}

    protected:

        void calculateSampling( unsigned int& out_rows, unsigned int& out_cols, double& out_i, double& out_j );

    private:
        bool _debug;

        OpenThreads::Mutex _compileMutex;
        //OpenThreads::Mutex                  _writeBufferMutex;
        osg::ref_ptr<osg::MatrixTransform> _transform;
        osg::ref_ptr<osg::Group> _backNode;
        osg::ref_ptr<osg::Uniform> _imageLayerStampUniform;
        osg::Vec3d _centerModel;
        float _verticalScaleOverride;
        osg::ref_ptr<GeoLocator> _masterLocator;

        int  _initCount;
        bool _pendingFullUpdate;    
        bool _pendingGeometryUpdate;
        bool _clearDataAfterCompile;

        struct ImageLayerUpdate {
            GeoImage _image;
            UID      _layerUID;
            bool     _isRealData; // versus fallback data
        };
        typedef std::queue<ImageLayerUpdate> ImageLayerUpdates;
        ImageLayerUpdates _pendingImageLayerUpdates;

        // associates each texture index with a layer UID.
        typedef std::map< UID, int > LayerUIDtoIndexMap;
        LayerUIDtoIndexMap _layerUIDtoIndexMap;

        // XXX Are these both necessary?
        GeoExtent _tileExtent;
        TileKey _tileKey;

        bool _optimizeTriangleOrientation;

        osg::ref_ptr<const TextureCompositor> _texCompositor;
        bool _frontGeodeInstalled;

        OpenThreads::Atomic _atomicCallOnce;

    private:

        osg::Vec3d computeCenterModel();
        bool createGeoImage( const CustomColorLayer& layer, GeoImage& image ) const; //const osgTerrain::Layer* imageLayer ) const;
        osg::Group* createGeometry( const TileFrame& tilef );
        osg::StateSet* createStateSet( const TileFrame& tilef );
        void prepareImageLayerUpdate( int layerIndex, const TileFrame& tilef );
        //Threading::ReadWriteMutex& getMutex();
        inline osg::Group* getFrontNode() const {
            if (_transform.valid() && _transform->getNumChildren() > 0)
                return static_cast<osg::Group*>(_transform->getChild(0));
            return NULL;
        }
        osg::StateSet* getParentStateSet() const;
        //const CustomColorLayer* getLayerByUID( UID uid ) const;
        int getIndexOfColorLayerWithUID( UID uid ) const;

        
        osg::observer_ptr<Tile> _parentTile;
    };

} // namespace osgEarth_engine_osgterrain

#endif // OSGEARTH_ENGINE_OSGTERRAIN_SINGLE_PASS_TERRAIN_TECHNIQUE
