/* -*-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_TERRAIN
#define OSGTERRAIN_TERRAIN 1

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

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

namespace osgTerrain {

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

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

        META_Node(osgTerrain, Terrain);

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

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

        /** Set the TerrainTechnique*/
        void setTerrainTechnique(osgTerrain::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, osgTerrain::Layer* layer);

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

        /** Set const color layer with specified layer number.*/
        const Layer* getColorLayer(unsigned int i) const { return i<_colorLayers.size() ? _colorLayers[i].layer.get() : 0; }
        
        /** Set a color transfer function with specified layer number.*/
        void setColorTransferFunction(unsigned int i, osg::TransferFunction* tf);

        /** Get color transfer function with specified layer number.*/
        osg::TransferFunction* getColorTransferFunction(unsigned int i) { return i<_colorLayers.size() ? _colorLayers[i].transferFunction.get() : 0; }

        /** Get const color transfer function with specified layer number.*/
        const osg::TransferFunction* getColorTransferFunction(unsigned int i) const { return i<_colorLayers.size() ? _colorLayers[i].transferFunction.get() : 0; }

        enum Filter
        {
            NEAREST,
            LINEAR
        };

        /** Set a color filter with specified layer number.*/
        void setColorFilter(unsigned int i, Filter filter);

        /** Set const color filter with specified layer number.*/
        Filter getColorFilter(unsigned int i) const { return i<_colorLayers.size() ? _colorLayers[i].filter : LINEAR; }


        /** 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 an OperationQueue to do an data initialization and update work.*/
        void setOperationQueue(osg::OperationQueue* operations) { _operationQueue = operations; }

        /** Get the OperationsQueue if one is attached, return NULL otherwise.*/
        osg::OperationQueue* getOperationQueue() { return _operationQueue.get(); }

        /** Get the const OperationsQueue if one is attached, return NULL otherwise.*/
        const osg::OperationQueue* getOperationsQueue() const { return _operationQueue.get(); }

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

    protected:

        virtual ~Terrain();

        struct LayerData
        {
            LayerData():
                filter(LINEAR) {}
        
            LayerData(const LayerData& rhs):
                filter(rhs.filter),
                layer(rhs.layer),
                transferFunction(rhs.transferFunction) {}

            LayerData& operator = (const LayerData& rhs)
            {
                filter = rhs.filter;
                layer = rhs.layer;
                transferFunction = rhs.transferFunction;
                return *this;
            }

            Filter                              filter;
            osg::ref_ptr<Layer>                 layer;
            osg::ref_ptr<osg::TransferFunction> transferFunction;
        };

        typedef std::vector<LayerData> Layers;

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

        Layers                              _colorLayers;
        
        bool                                _requiresNormals;
        bool                                _treatBoundariesToValidDataAsDefaultValue;

        osg::ref_ptr<osg::OperationQueue>   _operationQueue;
};

}

#endif
