/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2015 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_TERRAIN_ENGINE_NODE_H
#define OSGEARTH_TERRAIN_ENGINE_NODE_H 1

#include <osgEarth/Map>
#include <osgEarth/MapFrame>
#include <osgEarth/Terrain>
#include <osgEarth/TerrainEffect>
#include <osgEarth/TerrainTileNode>
#include <osgEarth/TextureCompositor>
#include <osgEarth/ShaderUtils>
#include <osg/CoordinateSystemNode>
#include <osg/Geode>
#include <osg/NodeCallback>

#define OSGEARTH_ENV_TERRAIN_ENGINE_DRIVER "OSGEARTH_TERRAIN_ENGINE"

namespace osgEarth
{
    class TerrainEffect;

    /**
     * Interface for querying terrain engine feature requirements.
     */
    class TerrainEngineRequirements
    {
    public:
        virtual bool elevationTexturesRequired() const =0;
        virtual bool normalTexturesRequired() const =0;
        virtual bool parentTexturesRequired() const =0;

    public:
        virtual ~TerrainEngineRequirements() { }
    };

    /**
     * TerrainEngineNode is the base class and interface for map engine implementations.
     *
     * A map engine lives under a MapNode and is responsible for generating the
     * actual geometry representing the Earth.
     */
    class OSGEARTH_EXPORT TerrainEngineNode : public osg::CoordinateSystemNode,
                                              public TerrainTileNodeBroker,
                                              public TerrainEngineRequirements
    {
    public:
        /** Gets the map that this engine is rendering. */
        const Map* getMap() const { return _map.get(); }

        /** Gets the Terrain interface for interacting with the scene graph */
        Terrain* getTerrain() { return _terrainInterface.get(); }
        const Terrain* getTerrain() const { return _terrainInterface.get(); }

        /** Gets the property set in use by this map engine. */
        virtual const TerrainOptions& getTerrainOptions() const =0;

        /** Accesses the object that keeps track of GPU resources in use */
        TextureCompositor* getResources() const;

        /** Adds a terrain effect */
        void addEffect( TerrainEffect* effect );

        /** Removes a terrain effect */
        void removeEffect( TerrainEffect* effect );

        /**
         * Marks the terrain tiles intersecting the provied extent as invalid.
         * If the terrain engine supports incremental update, it will reload
         * invalid tiles. If not, it will simple regenerate all tiles in the 
         * terrain (which might be slow).
         */
        virtual void invalidateRegion(
            const GeoExtent& extent,
            unsigned         minLevel,
            unsigned         maxLevel) { }

        // See invalidateRegion() above.
        void invalidateRegion(const GeoExtent& extent) {
            invalidateRegion(extent, 0u, INT_MAX);
        }

        /** Whether the implementation should generate normal map rasters. */
        void requireNormalTextures();
        
        /** Whether the implementation should generate elevation map rasters. */
        void requireElevationTextures();

        /** Whether the implementation should generate parent color textures. */
        void requireParentTextures();

        /** Access the stateset used to render the terrain. */
        virtual osg::StateSet* getTerrainStateSet() { return getOrCreateStateSet(); }

        /** Access the stateset used to render payload data. */
        virtual osg::StateSet* getPayloadStateSet() { return getOrCreateStateSet(); }

    public: // TerrainEngineRequirements

        bool normalTexturesRequired() const { return _requireNormalTextures; }
        bool elevationTexturesRequired() const { return _requireElevationTextures; }
        bool parentTexturesRequired() const { return _requireParentTextures; }

    public:
        // adds a callback that will be fired while the terrain engine is building
        // a tile node in a background thread.
        void addTileNodeCallback(TerrainTileNodeCallback* cb);
        void removeTileNodeCallback(TerrainTileNodeCallback* cb);
            

    public: // Runtime properties

        /** Sets the scale factor to apply to elevation height values. Default is 1.0
          * @deprecated */
        void setVerticalScale( float value );

        /** Gets the scale factor to apply to elevation height values.
          * #deprecated */
        float getVerticalScale() const { return _verticalScale; }

    public: // deprecated
        
        /** @derepcated Use getResources() instead. */
        TextureCompositor* getTextureCompositor() const { return getResources(); }

    protected:
        TerrainEngineNode();

        //TerrainEngineNode( const TerrainEngineNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL );

        virtual ~TerrainEngineNode();

    public: // osg::Node overrides
        virtual osg::BoundingSphere computeBound() const;
        virtual void traverse( osg::NodeVisitor& );

    protected: // implementation events
        virtual void onVerticalScaleChanged() { }
        virtual void onElevationSamplingRatioChanged() { }

    protected:
        friend class MapNode;
        friend class TerrainEngineNodeFactory;

        /** Attaches a map to the terrain engine and initialized it.*/
        virtual void preInitialize( const Map* map, const TerrainOptions& options );
        virtual void postInitialize( const Map* map, const TerrainOptions& options );

        // signals that a redraw is needed because something changed.
        virtual void requestRedraw();

        // Request that the terrain tiles be rebuilt.
        virtual void dirtyTerrain();

        // allow subclasses direct access for convenience.
        osg::ref_ptr<TextureCompositor> _texCompositor;

        bool _requireElevationTextures;
        bool _requireNormalTextures;
        bool _requireParentTextures;

        // called by addTileNodeCallback to notify any already-existing nodes
        // of the new callback.
        virtual void notifyExistingNodes(TerrainTileNodeCallback* cb) { }

    public: // TerrainTileNodeBroker

        void notifyOfTerrainTileNodeCreation(const TileKey& key, osg::Node* node);

    public: // utility

        /**
         * Utility function that will return an osg::Node representing the geometry
         * for a tile key. The node is standalone; it has no ability to load children
         * or receive updates.
         */
        virtual osg::Node* createTile( const TileKey& key ) =0;

    private:
        friend struct TerrainEngineNodeCallbackProxy;
        friend struct MapNodeMapLayerController;

        void ctor();
        void onMapInfoEstablished( const MapInfo& mapInfo ); // not virtual!
        void onMapModelChanged( const MapModelChange& change );
        virtual void updateTextureCombining() { }

    private:
        
        struct ImageLayerController : public ImageLayerCallback
        {
            ImageLayerController( const Map* map, TerrainEngineNode* engine );
            void onColorFiltersChanged( ImageLayer* layer ); 

        private:
            MapFrame           _mapf;
            TerrainEngineNode* _engine;
            friend class TerrainEngineNode;
        };

        osg::ref_ptr<ImageLayerController> _imageLayerController;
        osg::ref_ptr<const Map>            _map;
        bool                               _redrawRequired;
        float                              _verticalScale;
        osg::ref_ptr<Terrain>              _terrainInterface;
        unsigned                           _dirtyCount;
        
        enum InitStage {
            INIT_NONE,
            INIT_PREINIT_COMPLETE,
            INIT_POSTINIT_COMPLETE
        };
        InitStage _initStage;

        typedef std::vector<osg::ref_ptr<TerrainEffect> > TerrainEffectVector;
        TerrainEffectVector effects_;

        typedef std::vector<osg::ref_ptr<TerrainTileNodeCallback> > TerrainTileNodeCallbackVector;
        TerrainTileNodeCallbackVector _tileNodeCallbacks;
        mutable Threading::Mutex _tileNodeCallbacksMutex;

    public:

        /** Access a typed effect. */
        template<typename T>
        T* getEffect() {
            for(TerrainEffectVector::iterator i = effects_.begin(); i != effects_.end(); ++i ) {
                T* e = dynamic_cast<T*>(i->get());
                if ( e ) return e;
            }
            return 0L;
        }
    };

    /**
     * Factory class for creating terrain engine instances.
     */
    class TerrainEngineNodeFactory
    {
    public:
        static TerrainEngineNode* create( Map* map, const TerrainOptions& options );
    };

    /**
     * Base class for a node that "decorates" the terrain engine node within a map node.
     */
    class TerrainDecorator : public osg::Group
    {
    public:
        virtual void onInstall( TerrainEngineNode* engine );
        virtual void onUninstall( TerrainEngineNode* engine );

    protected:
        virtual ~TerrainDecorator();
    };

} // namespace osgEarth

#endif // OSGEARTH_TERRAIN_ENGINE_NODE_H
