/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSGTERRAIN_TERRAINTILE
#define OSGTERRAIN_TERRAINTILE 1

#include <osg/Group>
#include <osg/CoordinateSystemNode>

#include <osgDB/ReaderWriter>

#include <osgTerrain/TerrainTechnique>
#include <osgTerrain/Layer>
#include <osgTerrain/Locator>

namespace osgTerrain {

class Terrain;

class TileID
{
    public:
    
        TileID():
            level(-1),
            x(-1),
            y(-1) {}

        TileID(int in_level, int in_x, int in_y):
            level(in_level),
            x(in_x),
            y(in_y) {}
            
        bool operator == (const TileID& rhs) const        
        {
            return (level==rhs.level) && (x==rhs.x) && (y==rhs.y);
        }

        bool operator != (const TileID& rhs) const        
        {
            return (level!=rhs.level) || (x!=rhs.x) || (y!=rhs.y);
        }

        bool operator < (const TileID& rhs) const
        {
            if (level<rhs.level) return true;
            if (level>rhs.level) return false;
            if (x<rhs.x) return true;
            if (x>rhs.x) return false;
            return y<rhs.y;
        }
        
        bool valid() const { return level>=0; }

        int level;
        int x;
        int y;
};


/** Terrain provides a framework for loosely coupling height field data with height rendering algorithms.
  * This allows TerrainTechnique's to be plugged in at runtime.*/
class OSGTERRAIN_EXPORT TerrainTile : public osg::Group
{
    public:

        TerrainTile();
        
        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        TerrainTile(const TerrainTile&,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Node(osgTerrain, TerrainTile);

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

        /** Call init on any attached TerrainTechnique.*/
        void init();


        /** Set the Terrain that this Terrain tile is a member of.*/
        void setTerrain(Terrain* ts);

        /** Get the Terrain that this Terrain tile is a member of.*/
        Terrain* getTerrain() { return _terrain; }

        /** Get the const Terrain that this Terrain tile is a member of.*/
        const Terrain* getTerrain() const { return _terrain; }

        
        /** Set the TileID (layer, x,y) of the TerrainTile.
          * The TileID is used so it can be located by its neighbours 
          * via the enclosing Terrain node that manages a map of TileID to TerraiTiles.*/
        void setTileID(const TileID& tileID);
        
        /** Get the TileID (layer, x,y) of the TerrainTile.*/
        const TileID& getTileID() const { return _tileID; }
        

        /** Set the TerrainTechnique*/
        void setTerrainTechnique(TerrainTechnique* TerrainTechnique);

        /** Get the TerrainTechnique*/
        TerrainTechnique* getTerrainTechnique() { return _terrainTechnique.get(); }
        
        /** Get the const TerrainTechnique*/
        const TerrainTechnique* getTerrainTechnique() const { return _terrainTechnique.get(); }


        /** Set the coordinate frame locator of the terrain node.
          * The locator takes non-dimensional s,t coordinates into the X,Y,Z world coords and back.*/
        void setLocator(Locator* locator) { _locator = locator; }

        /** Get the coordinate frame locator of the terrain node.*/
        Locator* getLocator() { return _locator.get(); }

        /** Get the coordinate frame locator of the terrain node.*/
        const Locator* getLocator() const { return _locator.get(); }

        /** Set the layer to use to define the elevations of the terrain.*/
        void setElevationLayer(Layer* layer);

        /** Get the layer to use to define the elevations of the terrain.*/
        Layer* getElevationLayer() { return _elevationLayer.get(); }

        /** Get the const layer to use to define the elevations of the terrain.*/
        const Layer* getElevationLayer() const { return _elevationLayer.get(); }


        /** Set a color layer with specified layer number.*/
        void setColorLayer(unsigned int i, Layer* layer);

        /** Get color layer with specified layer number.*/
        Layer* getColorLayer(unsigned int i) { return i<_colorLayers.size() ? _colorLayers[i].get() : 0; }

        /** Set const color layer with specified layer number.*/
        const Layer* getColorLayer(unsigned int i) const { return i<_colorLayers.size() ? _colorLayers[i].get() : 0; }

        /** Get the number of colour layers.*/
        unsigned int getNumColorLayers() const { return _colorLayers.size(); }
        
        
        /** Set hint to whether the TerrainTechnique should create per vertex normals for lighting purposes.*/
        void setRequiresNormals(bool flag) { _requiresNormals = flag; }

        /** Get whether the TerrainTechnique should create per vertex normals for lighting purposes.*/
        bool getRequiresNormals() const { return _requiresNormals; }


        /** Set the hint to whether the TerrainTechnique should treat the invalid Layer entries that at are neigbours to valid entries with the default value.*/
        void setTreatBoundariesToValidDataAsDefaultValue(bool flag) { _treatBoundariesToValidDataAsDefaultValue = flag; }

        /** Get whether the TeatBoundariesToValidDataAsDefaultValue hint.*/
        bool getTreatBoundariesToValidDataAsDefaultValue() const { return _treatBoundariesToValidDataAsDefaultValue; }

        /** Set the dirty flag on/off.*/
        void setDirty(bool dirty);

        /** return true if the tile is dirty and needs to be updated,*/
        bool getDirty() const { return _dirty; }

        /** Compute the bounding volume of the terrain by computing the union of the bounding volumes of all layers.*/
        virtual osg::BoundingSphere computeBound() const;

        /** Callback for post processing loaded TerrainTile, and for filling in missing elements such as external external imagery.*/
        struct TileLoadedCallback : public osg::Referenced
        {
            virtual bool deferExternalLayerLoading() const = 0;
            virtual void loaded(osgTerrain::TerrainTile* tile, const osgDB::ReaderWriter::Options* options) const = 0;
        };
        
        static void setTileLoadedCallback(TileLoadedCallback* lc);
        static osg::ref_ptr<TileLoadedCallback>& getTileLoadedCallback();

    protected:

        virtual ~TerrainTile();


        typedef std::vector< osg::ref_ptr<Layer> > Layers;

        friend class Terrain;

        Terrain*                            _terrain;
        
        bool                                _dirty;
        bool                                _hasBeenTraversal;
        
        TileID                              _tileID;

        osg::ref_ptr<TerrainTechnique>      _terrainTechnique;
        osg::ref_ptr<Locator>               _locator;
        
        osg::ref_ptr<Layer>                 _elevationLayer;

        Layers                              _colorLayers;
        
        bool                                _requiresNormals;
        bool                                _treatBoundariesToValidDataAsDefaultValue;
};

/** Helper callback for managing optional sets of layers, that loading of is deffered to this callback,
  * with this callback working out which layers to load, and how to create fallback versions of the layers.
*/
class OSGTERRAIN_EXPORT WhiteListTileLoadedCallback : public TerrainTile::TileLoadedCallback
{
    public:

        WhiteListTileLoadedCallback();

        void allow(const std::string& setname) { _setWhiteList.insert(setname); }
        
        void setMinimumNumOfLayers(unsigned int numLayers) { _minumumNumberOfLayers = numLayers; }
        unsigned int getMinimumNumOfLayers() const { return _minumumNumberOfLayers; }

        void setReplaceSwitchLayer(bool replaceSwitchLayer) { _replaceSwitchLayer = replaceSwitchLayer; }
        bool getReplaceSwitchLayer() const { return _replaceSwitchLayer; }

        void setAllowAll(bool allowAll) { _allowAll = allowAll; }
        bool getAllowAll() const { return _allowAll; }

        bool layerAcceptable(const std::string& setname) const;
        bool readImageLayer(osgTerrain::ImageLayer* imageLayer, const osgDB::ReaderWriter::Options* options) const;

        virtual bool deferExternalLayerLoading() const;

        virtual void loaded(osgTerrain::TerrainTile* tile, const osgDB::ReaderWriter::Options* options) const;
    
    protected:

        virtual ~WhiteListTileLoadedCallback();
    
        typedef std::set<std::string> SetWhiteList;
        SetWhiteList    _setWhiteList;
        unsigned int    _minumumNumberOfLayers;
        bool            _replaceSwitchLayer;
        bool            _allowAll;

};

}

#endif
