/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2008-2010 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 "Terrain"
#include "Tile"
#include <list>
#include <queue>
#include <iterator>

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 Terrain* getTerrain() { return _terrain.get(); }
    const class Terrain* getTerrain() const { return _terrain.get(); }

    // attaches this tile to a terrain and registers it.
    void attachToTerrain( Terrain* 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 );

    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<Terrain>     _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;
    }
};

#endif // OSGEARTH_ENGINE_OSGTERRAIN_CUSTOM_TILE
