/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2016 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_LAYER_H
#define OSGEARTH_LAYER_H 1

#include <osgEarth/Common>
#include <osgEarth/Config>
#include <osgEarth/Status>
#include <osgEarth/TileKey>
#include <osgEarth/PluginLoader>
#include <osgEarth/CachePolicy>
#include <osgEarth/TerrainResources>
#include <osgEarth/Cache>
#include <osg/BoundingBox>
#include <osg/StateSet>
#include <osgDB/Options>
#include <vector>

namespace osgUtil {
    class CullVisitor;
}

namespace osgEarth
{
    class SequenceControl;

    /**
     * Base serializable options class for configuring a Layer.
     */
    class OSGEARTH_EXPORT LayerOptions : public ConfigOptions
    {
    public:
        /** Name of the layer */
        optional<std::string>& name() { return _name; }
        const optional<std::string>& name() const { return _name; }

        /**
         * Whether to open and use this layer in the map. When a layer
         * is not enabled, osgEarth components will ignore it altogether.
         * Layers are enabled by default.
         */
        optional<bool>& enabled() { return _enabled; }
        const optional<bool>& enabled() const { return _enabled; }

        /**
         * Uniquely identifies the caching location to use for this
         * layer if a cache is enabled and if this layer does any caching.
         * By default, the cache system will automatically generate a
         * cache ID for each Layer, but you can set one manually here.
         */
        optional<std::string>& cacheId() { return _cacheId; }
        const optional<std::string>& cacheId() const { return _cacheId; }

        /**
         * The caching policy directs how to use caching for this layer
         * if a cache is available.
         */
        optional<CachePolicy>& cachePolicy() { return _cachePolicy; }
        const optional<CachePolicy>& cachePolicy() const { return _cachePolicy; }

        /**
         * If set, calls setDefine() on the layer's stateset to make the
         * #define available in shader code.
         */
        optional<std::string>& shaderDefine() { return _shaderDefine; }
        const optional<std::string>& shaderDefine() const { return _shaderDefine; }

        /**
         * Shader snippet(s) to inject into this layer's stateset.
         */
        optional<std::string>& shader() { return _shader; }
        const optional<std::string>& shader() const { return _shader; }

    public:
        LayerOptions();
        LayerOptions(const ConfigOptions& configOptions);

        virtual Config getConfig() const;
        void fromConfig(const Config& conf);
        virtual void mergeConfig(const Config& conf);

    protected:
        void setDefaults();

        optional<std::string> _name;
        optional<bool> _enabled;
        optional<std::string> _cacheId;
        optional<CachePolicy> _cachePolicy;
        optional<std::string> _shaderDefine;
        optional<std::string> _shader;
    };


    /**
     * Base callback for all Layer types
     */
    class LayerCallback : public osg::Referenced
    {
        //NOP
    };


#define META_Layer(LIBRARY, TYPE, OPTIONS) \
    private: \
        OPTIONS * _options; \
        OPTIONS   _optionsConcrete; \
        TYPE ( const TYPE& rhs, const osg::CopyOp& op ) { } \
    public: \
        META_Object(LIBRARY, TYPE); \
        OPTIONS& options() { return *_options; } \
        const OPTIONS& options() const { return *_options; }


    /**
     * Base class for all Map layers. 
     */
    class OSGEARTH_EXPORT Layer : public osg::Object
    {
    public:
        META_Layer(osgEarth, Layer, LayerOptions);

        /**
         * Constructs a map layer
         */
        Layer();

        /**
         * Constructs a map layer by deserializing options. 
         */
        Layer(LayerOptions* options);

        /**
         * This layer's unique ID.
         * This value is generated automatically at runtime and is not
         * guaranteed to be the same from one run to the next.
         */
        UID getUID() const { return _uid; }

        /**
         * Sets the osgDB read options for this Layer.
         */
        virtual void setReadOptions(const osgDB::Options* options);
        const osgDB::Options* getReadOptions() const { return _readOptions.get(); }

        /**
         * Open a layer and return the status.
         * The layer options are applied in open().
         */
        virtual const Status& open();

        /**
         * Gets the status of this layer.
         */
        const Status& getStatus() const { return _status; }

        /**
         * @deprecated
         * Sequence controller if the layer has one.
         */
        virtual SequenceControl* getSequenceControl() { return 0L; }

        /**
         * Returns a Config object serializing this layer, if applicable.
         */
        virtual Config getConfig() const;

        /**
         * Whether the layer is enabled (used in the map). This will return
         * false if the layer is set to disabled OR if getStatus returns an error.
         */
        virtual bool getEnabled() const;

        /**
         * Gets a optional scene graph provided by the layer (default is NULL).
         * When this layer is added to a Map, the MapNode will call this method and
         * add the return value to its scene graph; and remove it when the Layer
         * is removed from the Map.
         */
        virtual osg::Node* getOrCreateNode() { return 0L; }

        /**
         * Extent of this layer's data. 
         * This method may return GeoExtent::INVALID which just means that the
         * information is unavailable.
         */
        virtual const GeoExtent& getExtent() const;

        /**
         * Returns or generates a caching ID for this layer
         */
        virtual std::string getCacheID() const;

    public:

        /** Adds a property notification callback to this layer */
        void addCallback(LayerCallback* cb);

        /** Removes a property notification callback from this layer */
        void removeCallback(LayerCallback* cb);


    public:

        /**
         * Access the layer's stateset, creating it is necessary
         */
        osg::StateSet* getOrCreateStateSet();

        /**
         * Layer's stateset, or NULL if none exists
         */
        osg::StateSet* getStateSet() const {
            return _stateSet.get();
        }

        /**
         * How (and if) to use this layer when rendering terrain tiles.
         */
        enum RenderType
        {
            // Layer does not require its own terrain rendering pass (model layers, elevation layers)
            RENDERTYPE_NONE, 

            // Layer requires a terrain rendering pass that draws terrain tile geometry
            RENDERTYPE_TILE,

            // Layer requires a terrain rendering pass that emits terrian patches or
            // invokes a custom drawing function
            RENDERTYPE_PATCH
        };

        RenderType getRenderType() const { return _renderType; }

        void setRenderType(RenderType value) { _renderType = value; }

        virtual void modifyTileBoundingBox(const TileKey& key, osg::BoundingBox& box) const { }

        /**
         * Return the class type name without namespace. For example if the leaf class type
         * is osgEarth::ImageLayer, this method returns "ImageLayer".
         */
        const char* getTypeName() const;

        //! Map will call this function when this Layer is added to a Map.
        virtual void addedToMap(const class Map*) { }

        //! Map will call this function when this Layer is removed from a Map.
        virtual void removedFromMap(const class Map*) { }

        //! MapNode will call this function when terrain resources are available.
        virtual void setTerrainResources(TerrainResources*) { }


    public: // Experimental

        //! Called before culling this layer - return false to reject.
        virtual bool cull(const osgUtil::CullVisitor*, osg::State::StateSetStack& stateSetStack) const;


    public: // osg::Object

        virtual void setName(const std::string& name);


    public: // Loading a layer from a plugin
        
        static Layer* create(const ConfigOptions& options);
        static Layer* create(const std::string& driver, const ConfigOptions& options);

        static const ConfigOptions& getConfigOptions(const osgDB::Options*);

    protected:

        /** dtor */
        virtual ~Layer();

        // post-ctor initialization, chain to subclasses.
        virtual void init();

        const Status& setStatus(const Status& status) { _status = status; return _status; }
        const Status& setStatus(const Status::Code& statusCode, const std::string& message) { return setStatus(Status(statusCode, message)); }

    private:
        UID _uid;
        osg::ref_ptr<osg::StateSet> _stateSet;
        RenderType _renderType;
        Status _status;

    protected:
        typedef std::vector<osg::ref_ptr<LayerCallback> > CallbackVector;
        CallbackVector _callbacks;
        osg::ref_ptr<osgDB::Options> _readOptions;
        osg::ref_ptr<CacheSettings> _cacheSettings;

        // allow the map access to the addedToMap/removedFromMap methods
        friend class Map;
    };

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

    
#define REGISTER_OSGEARTH_LAYER(NAME,CLASS) \
    extern "C" void osgdb_##NAME(void) {} \
    static osgEarth::RegisterPluginLoader< osgEarth::PluginLoader<CLASS, osgEarth::Layer> > g_proxy_##CLASS_##NAME( #NAME );
    
#define USE_OSGEARTH_LAYER(NAME) \
    extern "C" void osgdb_##NAME(void); \
    static osgDB::PluginFunctionProxy proxy_osgearth_layer_##NAME(osgdb_##NAME);

} // namespace osgEarth

#endif // OSGEARTH_IMAGE_TERRAIN_LAYER_H
