/* -*-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 OSGTERRAIN_EXPORT TileID
{
    public:

        TileID();

        TileID(int in_level, int in_x, int 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(int dirtyMask, bool assumeMultiThreaded);

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

        template<class T> void setTerrainTechnique(const osg::ref_ptr<T>& terrainTechnique) { setTerrainTechnique(terrainTechnique.get()); }

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

        template<class T> void setLocator(const osg::ref_ptr<T>& locator) { setLocator(locator.get()); }

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

        /** Get the const 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);

        template<class T> void setElevationLayer(const osg::ref_ptr<T>& layer) { setElevationLayer(layer.get()); }

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

        template<class T> void setColorLayer(unsigned int i, const osg::ref_ptr<T>& layer) { setColorLayer(i, layer.get()); }

        /** 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 neighbours to valid entries with the default value.*/
        void setTreatBoundariesToValidDataAsDefaultValue(bool flag) { _treatBoundariesToValidDataAsDefaultValue = flag; }

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


        enum BlendingPolicy
        {
            INHERIT, /** Default - check for the any BlendingPolicy set on the enclosing osgTerrain::Terrain node, and if it's also INHERIT then assume ENABLE_BLENDING_WHEN_ALPHA_PRESENT. */
            DO_NOT_SET_BLENDING,
            ENABLE_BLENDING,
            ENABLE_BLENDING_WHEN_ALPHA_PRESENT /** check colour layers for alpha value and if present enable blending. */
        };

        /** Set the policy to use when deciding whether to enable/disable blending and use of transparent bin.*/
        void setBlendingPolicy(BlendingPolicy policy) { _blendingPolicy = policy; }

        /** Get the policy to use when deciding whether to enable/disable blending and use of transparent bin.*/
        BlendingPolicy getBlendingPolicy() const { return _blendingPolicy; }


        enum DirtyMask
        {
            NOT_DIRTY = 0,
            IMAGERY_DIRTY = 1<<0,
            ELEVATION_DIRTY = 1<<1,
            LEFT_EDGE_DIRTY = 1<<2,
            RIGHT_EDGE_DIRTY = 1<<3,
            TOP_EDGE_DIRTY = 1<<4,
            TOP_LEFT_CORNER_DIRTY = 1<<5,
            TOP_RIGHT_CORNER_DIRTY = 1<<6,
            BOTTOM_EDGE_DIRTY = 1<<7,
            BOTTOM_LEFT_CORNER_DIRTY = 1<<8,
            BOTTOM_RIGHT_CORNER_DIRTY = 1<<9,
            EDGES_DIRTY = LEFT_EDGE_DIRTY | RIGHT_EDGE_DIRTY | TOP_EDGE_DIRTY | BOTTOM_EDGE_DIRTY |
                          TOP_LEFT_CORNER_DIRTY | TOP_RIGHT_CORNER_DIRTY | BOTTOM_LEFT_CORNER_DIRTY | BOTTOM_RIGHT_CORNER_DIRTY,
            ALL_DIRTY = IMAGERY_DIRTY | ELEVATION_DIRTY | EDGES_DIRTY
        };

        /** Set the dirty flag on/off.*/
        void setDirty(bool dirty) { setDirtyMask(dirty ? ALL_DIRTY : NOT_DIRTY); }

        /** return true if the any of the DirtyMask are set.*/
        int getDirty() const { return _dirtyMask!=NOT_DIRTY; }


        /** Set the dirty flag on/off.*/
        void setDirtyMask(int dirtyMask);

        /** return true if the tile is dirty and needs to be updated,*/
        int getDirtyMask() const { return _dirtyMask; }


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

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


    protected:

        virtual ~TerrainTile();

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

        friend class Terrain;

        Terrain*                            _terrain;

        int                                 _dirtyMask;
        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;
        BlendingPolicy                      _blendingPolicy;
};

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