/* -*-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_CUSTOM_TILE
#define OSGEARTH_ENGINE_OSGTERRAIN_CUSTOM_TILE 1

#include "Common"
#include "OSGTileFactory"
#include "TransparentLayer"
#include <osgEarth/TaskService>
#include <osgEarth/Locators>
#include <osgEarth/Profile>
#include <osgEarth/TerrainOptions>
#include <osgEarth/Map>
#include <osgEarth/ThreadingUtils>
#include <osgTerrain/Terrain>
#include <osgTerrain/TerrainTile>
#include <list>
#include <queue>

class TileFactory; //MapEngine;
class CustomTerrain;

using namespace osgEarth;

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

// Records a tile change request so that we can update tiles piecemeal
struct TileUpdate
{
    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;
};

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

// Please refer the refreshFamily() method for more info on this structure.
struct Relative
{
    int getImageLOD(unsigned int layerID)
    {
        LayerIDtoLODMap::iterator itr = imageLODs.find(layerID);
        if (itr != imageLODs.end()) return itr->second;
        return -1;
    }

    bool expected;
    int elevLOD;
    typedef std::map<unsigned int, int> LayerIDtoLODMap;
    LayerIDtoLODMap imageLODs;
    osgTerrain::TileID tileID;
    //TileKey key;

    enum Direction {
        PARENT =0,
        WEST   =1,
        NORTH  =2,
        EAST   =3,
        SOUTH  =4
    };

    Relative() { } // : key(0,0,0,0L) { }
};

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

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

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

class CustomTile : public osgTerrain::TerrainTile
{
public:
    CustomTile( const TileKey& key, GeoLocator* keyLocator, bool quickReleaseGLObjects );

    virtual const char* libraryName() const { return "osgEarth"; }
    virtual const char* className() const { return "CustomTile"; }


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

    /** Gets the locator associated with this tile. */

    /** Sets whether to use the TaskService to schedule per-layer background data loading. */
    void setUseLayerRequests( bool );
    bool getUseLayerRequests() const { return _useLayerRequests; }

    /** Sets whether to update the tile layer by layer vs. rebuidling the entire tile. */
    void setUsePerLayerUpdates( bool );

    /**
     * Gets the terrain revision that this tile is in sync with.
     */
    int getTerrainRevision() const;

    /**
     * Sets the terrain revision that this tile is in sync with.
     */
    void setTerrainRevision( int revision );

    /**
     * Returns true if this tile is in sync with its parent Terrain (i.e., their
     * version numbers are equal).
     */
    bool isInSyncWithTerrain() const;

    /**
     * Bumps this tile's revision number.
     */
    void incrementTileRevision();

    /**
     * Gets this tile's revision number.
     */
    int getTileRevision() const;

    /** Updates and services this tile's service pending requests. */
    void servicePendingImageRequests( const MapFrame& mapf, int stamp );

    void servicePendingElevationRequests( const MapFrame& mapf, int stamp, bool tileTableLocked );

    // returns TRUE if the tile was modified as a result of a completed request.
    bool serviceCompletedRequests( const MapFrame& mapf, bool tileTableLocked );

    /** Setting this hint tells the tile whether it should bother trying to load elevation data. */
    void setHasElevationHint( bool hasElevation );

    /** Gets whether the tile's real (not placeholder) elevation data has been loaded. */
    bool isElevationLayerUpToDate() const;

    /** Gets or sets the LOD of this tile's current heightfield data. */
    int getElevationLOD() const;
    void setElevationLOD( int lod );

    /** Gets the terrain object to which this tile belongs. */
    class CustomTerrain* getCustomTerrain();
    const class CustomTerrain* getCustomTerrain() const;

    bool getHasBeenTraversed() const;

    Threading::ReadWriteMutex& getTileLayersMutex();

    bool cancelRequests();

    void updateImagery( ImageLayer* layer, const MapFrame& mapf, OSGTileFactory* factory );

    bool getUseTileGenRequest() const;

    Relative* getFamily() { return _family; }

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

    void resetElevationRequests( const MapFrame& mapf );

    float getVerticalScale() const;
    void setVerticalScale(float verticalScale);

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


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:

    ~CustomTile();


private:

    int  _terrainRevision;
    int  _tileRevision;
    bool _useLayerRequests;
    bool _requestsInstalled;
    bool _usePerLayerUpdates;
    bool _hasElevation;
    bool _elevationLayerDirty;
    bool _colorLayersDirty;
    bool _elevationLayerRequested;
    bool _elevationLayerUpToDate;
    int  _elevationLOD;
    bool _hasBeenTraversed;
    bool _useTileGenRequest;
    bool _sequentialImagery;
    bool _quickReleaseGLObjects;

    typedef std::queue<TileUpdate> TileUpdateQueue;
    TileUpdateQueue _tileUpdates;

    TileKey _key;
    osg::ref_ptr<GeoLocator> _keyLocator;
    TaskRequestList _requests;
    osg::ref_ptr<TaskRequest> _elevRequest;
    osg::ref_ptr<TaskRequest> _elevPlaceholderRequest;
    osg::ref_ptr<TaskRequest> _tileGenRequest;

    osg::observer_ptr<osgTerrain::Terrain> _CustomTerrain;

    Relative _family[5];
    Threading::ReadWriteMutex _tileLayersMutex;

    /** Deals with completed requests during the UPDATE traversal. */
    void installRequests( const MapFrame& mapf, int stamp );
    bool readyForNewElevation();
    bool readyForNewImagery(osgEarth::ImageLayer* layer, int currentLOD);

    float _verticalScale;

    ColorLayersByUID _colorLayers;

public:
    friend class CustomTileFrame;
};

class CustomTileVector : public std::vector< osg::ref_ptr<CustomTile> > { };

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

class CustomTileFrame
{
public:
    CustomTileFrame( CustomTile* tile )
    {
        Threading::ScopedReadLock sharedLock( tile->_tileLayersMutex );
        _colorLayers = tile->_colorLayers;
        _elevationLayer = tile->getElevationLayer();
        _locator = tile->getLocator();
        _sampleRatio = tile->getTerrain()->getSampleRatio();
    }

    ColorLayersByUID _colorLayers;
    osg::ref_ptr< osgTerrain::Layer > _elevationLayer;
    osg::ref_ptr< osgTerrain::Locator > _locator;
    float _sampleRatio;

    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
