/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2014 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/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;

    /**
     * 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:
        /** 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 compositor that controls the rendering of image layers */
        TextureCompositor* getTextureCompositor() 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);
        }
            

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


    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 dirty();

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

    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 onVisibleChanged( TerrainLayer* layer );
            void onOpacityChanged( ImageLayer* layer );
            void onColorFiltersChanged( ImageLayer* layer );      
            void onVisibleRangeChanged( 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_;

    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
