/* -*-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_TILE
#define OSGEARTH_ENGINE_OSGTERRAIN_TILE 1

#include "Common"
#include "OSGTileFactory"
#include "TransparentLayer"
#include "CustomTerrainTechnique"
#include <osgEarth/Locators>
#include <osgEarth/Profile>
#include <osgEarth/TerrainOptions>
#include <osgEarth/Map>
#include <osgEarth/ThreadingUtils>
#include "TerrainNode"
#include "Tile"
#include <list>
#include <queue>
#include <iterator>

namespace osgEarth_engine_osgterrain
{
    class TileFactory;
    class TerrainTechnique;

    using namespace osgEarth;

    typedef std::map<UID, CustomColorLayer> ColorLayersByUID;

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

    // Records a tile change request so that we can update tiles piecemeal
    class TileUpdate
    {
    public:
        enum Action {
            ADD_IMAGE_LAYER,
            REMOVE_IMAGE_LAYER,
            MOVE_IMAGE_LAYER,
            UPDATE_IMAGE_LAYER,
            UPDATE_ALL_IMAGE_LAYERS,
            UPDATE_ELEVATION,
            UPDATE_ALL
        };

        TileUpdate( Action action, UID layerUID =(UID)-1 ) 
            : _action(action), _layerUID(layerUID) { }

        Action getAction() const { return _action; }

        UID getLayerUID() const { return _layerUID; }

    private:
        Action _action;
        UID _layerUID;
    };

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

    class Tile : public osg::Node
    {
    public:
        Tile( const TileKey& key, GeoLocator* keyLocator, bool quickReleaseGLObjects );

        /** Gets the tilekey associated with this tile. */
        const TileKey& getKey() const { return _key; }

        /** Gets the terrain object to which this tile belongs. */
        class TerrainNode* getTerrain() { return _terrain.get(); }
        const class TerrainNode* getTerrain() const { return _terrain.get(); }

        // attaches this tile to a terrain and registers it.
        void attachToTerrain( TerrainNode* terrain );

        /** intializes the tile and clears its dirty flag */
        void init();

        /** Gets or sets the terrain mask geometry. */
        const MaskLayerVector& getTerrainMasks() { return _masks; }
        void setTerrainMasks(const MaskLayerVector& terrainMask) { _masks.clear(); std::copy( terrainMask.begin(), terrainMask.end(), std::back_inserter(_masks) ); }

        /** Whether OSG has traversed the tile at least once. */
        bool getHasBeenTraversed() const { return _hasBeenTraversed; }

        /** Mutex that protects access to the tile contents. */
        Threading::ReadWriteMutex& getTileLayersMutex() { return _tileLayersMutex; }

        // marks a request to regenerate the tile based on the specified change(s).
        virtual void queueTileUpdate( TileUpdate::Action action, int index =-1 );
        
        void applyImmediateTileUpdate( TileUpdate::Action action, int index =-1 );

        virtual bool cancelActiveTasks() { return true; }

        /** The scale factor for elevation heights */
        float getVerticalScale() const { return _verticalScale; }
        void setVerticalScale( float verticalScale );

        osgTerrain::Locator* getLocator() const { return _locator.get(); }

        const osgTerrain::TileID& getTileId() const { return _tileId; }

        bool getDirty() const { return _dirty; }
        void setDirty( bool value ) { _dirty = value; }

        void setTerrainTechnique( TerrainTechnique* value );
        TerrainTechnique* getTerrainTechnique() const { return _tech.get(); }

        /** Tile contents. We don't use the TerrainTile color layer list, we use our own */
        void removeCustomColorLayer( UID layerUID, bool writeLock =true );
        bool getCustomColorLayer( UID layerUID, CustomColorLayer& output, bool readLock =true ) const;
        void getCustomColorLayers( ColorLayersByUID& out, bool readLock =true ) const;
        void setCustomColorLayers( const ColorLayersByUID& in, bool writeLock =true );
        void setCustomColorLayer( const CustomColorLayer& colorLayer, bool writeLock =true );

        void clear();

        osgTerrain::HeightFieldLayer* getElevationLayer() const { return _elevationLayer.get(); }
        void setElevationLayer( osgTerrain::HeightFieldLayer* value ) { _elevationLayer = value; }

    public: // OVERRIDES

        virtual void traverse( class osg::NodeVisitor& nv );

        /** 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;

        virtual osg::BoundingSphere computeBound() const;

    protected:

        virtual ~Tile();

        bool _hasBeenTraversed;
        bool _quickReleaseGLObjects;
        bool _parentTileSet;
        bool _dirty;

        TileKey                        _key;
        osgTerrain::TileID             _tileId;
        osg::ref_ptr<GeoLocator>       _locator;
        osg::observer_ptr<TerrainNode> _terrain;
        MaskLayerVector                _masks;

        Threading::ReadWriteMutex _tileLayersMutex;
        ColorLayersByUID          _colorLayers;
        float                     _verticalScale;

        osg::ref_ptr<osgTerrain::HeightFieldLayer> _elevationLayer;
        osg::ref_ptr<TerrainTechnique>             _tech;


    public:
        friend class TileFrame;
    };

    class TileVector : public std::vector< osg::ref_ptr<Tile> > { };

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

    /**
     * Thread-safe working copy of Tile contents.
     */
    class TileFrame
    {
    public:
        TileFrame( Tile* tile );

        TileKey                                      _tileKey;
        ColorLayersByUID                             _colorLayers;
        osg::ref_ptr< osgTerrain::HeightFieldLayer > _elevationLayer;
        osg::ref_ptr< osgTerrain::Locator >          _locator;
        float                                        _sampleRatio;
        MaskLayerVector                              _masks;

        // convenience funciton to pull out a layer by its UID.
        bool getCustomColorLayer( UID layerUID, CustomColorLayer& out ) const {
            ColorLayersByUID::const_iterator i = _colorLayers.find( layerUID );
            if ( i != _colorLayers.end() ) {
                out = i->second;
                return true;
            }
            return false;
        }
    };

} // namespace osgEarth_engine_osgterrain

#endif // OSGEARTH_ENGINE_OSGTERRAIN_CUSTOM_TILE
