/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2020 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/>
 */
#pragma once

#include <osgEarth/Common>
#include <osgEarth/Config>
#include <osgEarth/Status>
#include <osgEarth/PluginLoader>
#include <osgEarth/Cache>
#include <osgEarth/SceneGraphCallback>
#include <osgEarth/LayerShader>
#include <osgEarth/Revisioning>
#include <osgEarth/IOTypes>
#include <osgEarth/DateTime>
#include <osg/BoundingBox>
#include <osg/Callback>
#include <vector>

namespace osg {
    class StateSet;
}
namespace osgDB {
    class Options;
}

//! Macro to use when defining a LayerOptions class
#define META_LayerOptions(LIBRARY, MYCLASS, SUPERCLASS) \
    protected: \
        virtual void mergeConfig(const osgEarth::Config& conf) { \
            SUPERCLASS ::mergeConfig(conf); \
            fromConfig(conf); \
        } \
        using super = SUPERCLASS; \
    public: \
        OE_COMMENT("Construct empty layer options") \
        MYCLASS () : SUPERCLASS() { fromConfig(_conf); } \
        \
        OE_COMMENT("Construct layer options from serialized data") \
        MYCLASS (const osgEarth::ConfigOptions& opt) : SUPERCLASS(opt) { fromConfig(_conf); } \
        \
        osgEarth::Config& _internal() { return _conf; } \
        const osgEarth::Config& _internal() const { return _conf; }


//! Macro to use when defining a Layer class
#define META_Layer(LIBRARY, MYCLASS, OPTIONS, SUPERCLASS, SLUG) \
    private: \
        OPTIONS * _options; \
        OPTIONS   _optionsConcrete; \
        const OPTIONS * _options0; \
        const OPTIONS   _optionsConcrete0; \
        MYCLASS ( const MYCLASS& rhs, const osg::CopyOp& op ) { } \
        using super = SUPERCLASS; \
    protected: \
        OE_COMMENT("Construct a new layer with default options") \
        MYCLASS (OPTIONS* optr, const OPTIONS* optr0) : \
            SUPERCLASS (optr? optr: &_optionsConcrete, optr0? optr0 : &_optionsConcrete0), \
            _options(optr ? optr : &_optionsConcrete), \
            _options0(optr0 ? optr0 : &_optionsConcrete0) { } \
    public: \
        META_Object(LIBRARY, MYCLASS); \
        \
        OE_COMMENT("Construct a new layer with default options") \
        MYCLASS () : \
            SUPERCLASS (&_optionsConcrete, &_optionsConcrete0), \
            _options(&_optionsConcrete), \
            _options0(&_optionsConcrete0) { MYCLASS::init(); } \
        \
        OE_COMMENT("Construct a new layer with user-defined options") \
        MYCLASS (const OPTIONS& o) : \
            SUPERCLASS (&_optionsConcrete, &_optionsConcrete0), \
            _options(&_optionsConcrete), _optionsConcrete(o), \
            _options0(&_optionsConcrete0), _optionsConcrete0(o) { MYCLASS::init(); } \
        \
        OE_COMMENT("Mutable options for this layer") \
        OPTIONS& options() { return *_options; } \
        \
        OE_COMMENT("Immutable options for this layer") \
        const OPTIONS& options() const { return *_options; } \
        \
        OE_COMMENT("Immutable original (constructor) options for this layer") \
        const OPTIONS& options_original() const { return *_options0; } \
        \
        OE_COMMENT("Configuration key for this layer (e.g. for earth files)") \
        virtual const char* getConfigKey() const { return #SLUG ; }


//! Macro for defining a layer with a default Options structure
#define META_LayerNoOptions(LIBRARY, MYCLASS, SUPERCLASS, SLUG) \
    public: \
    struct Options : public SUPERCLASS::Options { \
        META_LayerOptions(LIBRARY, Options, SUPERCLASS::Options); \
    private: \
        void fromConfig(const osgEarth::Config&) { } \
    }; \
    META_Layer(LIBRARY, MYCLASS, Options, SUPERCLASS, SLUG)
    


//! Macro to use when defining a Layer class
#define META_Layer_Abstract(LIBRARY, MYCLASS, OPTIONS, SUPERCLASS) \
    private: \
        OPTIONS * _options; \
        const OPTIONS * _options0; \
        MYCLASS ( const MYCLASS& rhs, const osg::CopyOp& op ) { } \
    protected: \
        OE_COMMENT("Construct a new layer with default options") \
        MYCLASS (OPTIONS* optr, const OPTIONS* optr0) : \
            SUPERCLASS (optr, optr0), \
            _options(optr), \
            _options0(optr0) { } \
        MYCLASS () : SUPERCLASS () { } \
        using super = SUPERCLASS; \
    public: \
        OE_COMMENT("Mutable options for this layer") \
        OPTIONS& options() { return *_options; } \
        \
        OE_COMMENT("Immutable options for this layer") \
        const OPTIONS& options() const { return *_options; } \
        \
        OE_COMMENT("Immutable original (constructor) options for this layer") \
        const OPTIONS& options_original() const { return *_options0; }

//! Templated inline property implementation macro
#define OE_LAYER_PROPERTY_IMPL(CLASS, TYPE, FUNC, OPTION) \
    void CLASS ::set ## FUNC (const TYPE & value) { options(). OPTION () = value; }\
    const TYPE & CLASS ::get ## FUNC () const { return options(). OPTION ().get(); }


namespace osgEarth
{
    class GeoExtent;
    class SequenceControl;
    class TerrainEngine;
    class TerrainResources;
    class TileKey;

    //! Base class for layer property callbacks
    struct LayerCallback : public osg::Referenced
    {
        virtual void onOpen(class Layer*) { }
        virtual void onClose(class Layer*) { }
        typedef void (LayerCallback::*MethodPtr)(class Layer* layer);
    };

    /**
     * Base class for all Map layers.
     *
     * Subclass Layer to create a new layer type. Use the META_Layer macro
     * to establish the standard options framework.
     *
     * When you create a Layer, init() is called. Do all one-time construction
     * activity where.
     *
     * When you add a Layer to a Map, the follow methods are called in order:
     *
     *   setReadOptions() sets OSG options for IO activity;
     *   open() to initialize any underlying data sources;
     *   addedToMap() to signal to the layer that it is now a member of a Map.
     */
    class OSGEARTH_EXPORT Layer : public osg::Object
    {
    public:
        META_Object(osgEarth, Layer);

        /** Layer options for serialization */
        class OSGEARTH_EXPORT Options : public ConfigOptions
        {
        public:
            META_LayerOptions(osgEarth, Options, ConfigOptions);
            OE_OPTION(std::string, name);
            OE_OPTION(bool, openAutomatically, true);
            OE_OPTION(bool, terrainPatch, false);
            OE_OPTION(std::string, cacheId);
            OE_OPTION(CachePolicy, cachePolicy);
            OE_OPTION(std::string, shaderDefine);
            OE_OPTION(std::string, attribution);
            OE_OPTION(ShaderOptions, shader);
            OE_OPTION_VECTOR(ShaderOptions, shaders);
            OE_OPTION(ProxySettings, proxySettings);
            OE_OPTION(std::string, osgOptionString);
            OE_OPTION(unsigned, l2CacheSize, 0u);
            virtual Config getConfig() const;
        private:
            void fromConfig(const Config& conf);
        };

    public:
        //! Hints that a layer can set to influence the operation of
        //! the map engine
        class Hints
        {
        public:
            OE_OPTION(CachePolicy, cachePolicy);
            OE_OPTION(unsigned, L2CacheSize);
            OE_OPTION(bool, dynamic);
        };

    public:
        //! Constructs a map layer
        Layer();

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

        //! osgDB read options for this Layer.
        //! If you set this, do so prior to calling open().
        virtual void setReadOptions(const osgDB::Options* options);

        //! osgDB read options for this Layer.
        const osgDB::Options* getReadOptions() const;

        //! Open a layer.
        virtual Status open() final;

        //! Open a layer. Shortcut for calling setReadOptions() followed by open().
        virtual Status open(const osgDB::Options* options) final;

        //! Close this layer.
        virtual Status close();

        //! Whether the layer is open
        bool isOpen() const;

        //! Status of this layer
        const Status& getStatus() const;

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

        //! Serialize this layer into a Config object (if applicable)
        virtual Config getConfig() const;

        //! Whether to automatically open this layer (by calling open) when
        //! adding the layer to a Map or when opening a map containing this Layer.
        virtual void setOpenAutomatically(bool value);

        //! Whether to automatically open this layer (by calling open) when
        //! adding the layer to a Map or when opening a map containing this Layer.
        virtual bool getOpenAutomatically() const;

        //! Cacheing policy. Only set this before opening the layer or adding to a map.
        void setCachePolicy(const CachePolicy& value);
        const CachePolicy& getCachePolicy() const;

        //! Optional scene graph provided by the layer.
        //! 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* getNode() const { return 0L; }

        //! Extent of this layer's data.
        //! This method may return GeoExtent::INVALID which means that the
        //! extent is unavailable (not necessarily that there is no data).
        virtual const GeoExtent& getExtent() const;

        //! Temporal extent of this layer's data.
        virtual DateTimeExtent getDateTimeExtent() const;

        //! Unique caching ID for this layer.
        //! Only set this before opening the layer or adding to a map.
        //! WARNING: You should be Very Careful when using this. The Layer will
        //! automatically generate a cache ID that is sufficient most of the time.
        //! Setting your own cache ID will require manual cache invalidation when
        //! you change certain properties. Use are your own risk!
        void setCacheID(const std::string& value);
        virtual std::string getCacheID() const;

        //! Callbacks that one can use to detect scene graph changes
        SceneGraphCallbacks* getSceneGraphCallbacks() const;

        //! Hints that a subclass can set to influence the engine
        const Hints& getHints() const;

        //! Options string to pass to OpenSceneGraph reader/writers
        const std::string& getOsgOptionString() const;

    public:

        //! Layer's stateset, creating it is necessary
        osg::StateSet* getOrCreateStateSet();

        //! Layer's stateset, or NULL if none exists
        osg::StateSet* getStateSet() const;

        //! Stateset that should be applied to an entire terrain traversal
        virtual osg::StateSet* getSharedStateSet(osg::NodeVisitor* nv) const { return NULL; }

        //! How (and if) to use this layer when rendering terrain tiles.
        enum RenderType
        {
            //! Layer does not draw anything (directly)
            RENDERTYPE_NONE,

            //! Layer requires a terrain rendering pass that draws terrain tiles with texturing
            RENDERTYPE_TERRAIN_SURFACE,

            //! Layer requires a terrain rendering pass that emits terrian patches or
            //! invokes a custom drawing function
            RENDERTYPE_TERRAIN_PATCH,

            //! Layer that renders its own node graph or other geometry (no terrain)
            RENDERTYPE_CUSTOM = RENDERTYPE_NONE
        };

        //! Rendering type of this layer
        RenderType getRenderType() const { return _renderType; }

        //! Rendering type of this layer
        void setRenderType(RenderType value) { _renderType = value; }

        //! Callback that modifies the layer's bounding box for a given tile key
        virtual void modifyTileBoundingBox(const TileKey& key, osg::BoundingBox& box) const;

        //! Class type name without namespace. For example if the leaf class type
        const char* getTypeName() const;

        //! Attribution to be displayed by the application
        virtual std::string getAttribution() const;

        //! Attribution to be displayed by the application
        virtual void setAttribution(const std::string& attribution);

        //! Set a serialized user property
        void setUserProperty(
            const std::string& key, 
            const std::string& value);

        //! Get a serialized user property
        template<typename T>
        inline T getUserProperty(
            const std::string& key,
            T fallback) const;

    public:

        //! Traversal callback
        class OSGEARTH_EXPORT TraversalCallback : public osg::Callback
        {
        public:
            virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) const =0;
        protected:
            void traverse(osg::Node* node, osg::NodeVisitor* nv) const;
        };

        //! Callback invoked by the terrain engine on this layer before applying
        //! @deprecated replace with cull() override
        void setCullCallback(TraversalCallback* tc);
        const TraversalCallback* getCullCallback() const;

        //! Called to traverse this layer
        //! @deprecated Replace with cull() override
        void apply(osg::Node* node, osg::NodeVisitor* nv) const;

        //! Called by the terrain engine during the update traversal
        virtual void update(osg::NodeVisitor& nv) { }

        //! 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*) { }

    public: // osg::Object

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

        virtual void resizeGLObjectBuffers(unsigned maxSize);

        virtual void releaseGLObjects(osg::State* state) const;


    public: // Public internal methods

        //! Creates a layer from serialized data - internal use
        static osg::ref_ptr<Layer> create(const ConfigOptions& options);

        //! Creates a layer from serialized data and attempt to cast it
        //! to a specific type
        template<class T>
        static osg::ref_ptr<T> create_as(const ConfigOptions& options) {
            auto layer = create(options);
            return osg::ref_ptr<T>(dynamic_cast<T*>(layer.get()));
        }

        //! Extracts config options from a DB options - internal use
        static const ConfigOptions& getConfigOptions(const osgDB::Options*);

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

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

        //! Revision number of this layer
        int getRevision() const { return (int)_revision; }

        //! Increment the revision number for this layer, which will
        //! invalidate caches.
        virtual void dirty();

        class Options;

    public: // deprecated

        //! @deprecated - use getOpenAutomatically
        bool getEnabled() const;

        //! @deprecated - use setOpenAutomatically
        void setEnabled(bool value);

    protected:

        //! Constructs a map layer by deserializing options.
        Layer(
            Layer::Options* options,
            const Layer::Options* options0);

        //! DTOR
        virtual ~Layer();

        //! post-ctor initialization, chain to subclasses.
        //! MAKE SURE you call the superclass init() if you override this!
        virtual void init();

        //! Called by open() to connect to external resources and return a status.
        //! MAKE SURE you call superclass openImplementation() if you override this!
        //! This is where you will connect to back-end data sources if
        //! appropriate. When added to a map, init() is called before open()
        //! and addedToMap() is called after open() if it succeeds.
        //! By default, returns STATUS_OK.
        virtual Status openImplementation();

        //! Called by close() to shut down the resources associated with a layer.
        virtual Status closeImplementation();

        //! Prepares the layer for rendering if necessary.
        virtual void prepareForRendering(TerrainEngine*);

        //! Sets the status for this layer - internal
        const Status& setStatus(const Status& status) const;

        //! Sets the status for this layer with a message - internal
        const Status& setStatus(const Status::Code& statusCode, const std::string& message) const;

        //! invoke layer callbacks
        void fireCallback(LayerCallback::MethodPtr);

        //! mutable layer hints for the subclass to optionally access
        Hints& layerHints();

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

    private:
        const std::string& _layerName;
        UID _uid;
        osg::ref_ptr<osg::StateSet> _stateSet;
        RenderType _renderType;
        mutable Status _status;
        osg::ref_ptr<SceneGraphCallbacks> _sceneGraphCallbacks;
        osg::ref_ptr<TraversalCallback> _traversalCallback;
        Hints _hints;
        std::atomic_int _revision = { 1 };
        std::string _runtimeCacheId;
        osg::ref_ptr<osgDB::Options> _readOptions;
        osg::ref_ptr<CacheSettings> _cacheSettings;
        std::vector<osg::ref_ptr<LayerShader> > _shaders;
        mutable Threading::ReadWriteMutex _inuse_mutex;

        //! Prepares the layer for rendering if necessary.
        void invoke_prepareForRendering(TerrainEngine*);

    protected:
        typedef std::vector<osg::ref_ptr<LayerCallback> > CallbackVector;
        CallbackVector _callbacks;

        osgDB::Options* getMutableReadOptions() { return _readOptions.get(); }

        void bumpRevision();

        // subclass can call this to change an option that requires
        // a re-opening of the layer.
        template<typename T, typename V>
        void setOptionThatRequiresReopen(T& target, const V& value);

        // subclass can call this to change an option that requires
        // a re-opening of the layer.
        template<typename T>
        void resetOptionThatRequiresReopen(T& target);

        //! internal cache information
        CacheSettings* getCacheSettings() { return _cacheSettings.get(); }
        const CacheSettings* getCacheSettings() const { return _cacheSettings.get(); }

        //! subclass access to a mutex that serializes the 
        //! Layer open and close methods with respect to any asynchronous
        //! functions that require the layer to remain open
        Threading::ReadWriteMutex& inUseMutex() const { return _inuse_mutex; }

        //! Layers that this layer wants to add to the map
        std::vector<osg::ref_ptr<Layer>> _sublayers;

    public:

        Layer::Options& options() { return *_options; }
        const Layer::Options& options() const { return *_options; }
        const Layer::Options& options_original() const { return *_options0; }
        virtual const char* getConfigKey() const { return "layer" ; }

        //! A layer can report statistics for debugging by overriding this function
        using Stats = std::vector<std::pair<std::string, std::string>>;
        virtual Stats reportStats() const { return {}; }

    private:
        Layer::Options * _options;
        Layer::Options   _optionsConcrete;
        const Layer::Options * _options0;
        const Layer::Options   _optionsConcrete0;

        // no copying
        Layer(const Layer& rhs, const osg::CopyOp& op);

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

    using LayerVector = std::vector<osg::ref_ptr<Layer>>;


    template<typename T, typename V>
    void Layer::setOptionThatRequiresReopen(T& target, const V& value) {
        if (target != value) {
            bool wasOpen = isOpen();
            if (wasOpen) close();
            target = value;
            if (wasOpen) open();
        }
    }
    template<typename T>
    void Layer::resetOptionThatRequiresReopen(T& target) {
        if (target.isSet()) {
            bool wasOpen = isOpen();
            if (wasOpen) close();
            target.unset();
            if (wasOpen) open();
        }
    }
    template<typename T>
    T Layer::getUserProperty(const std::string& key, T fallback) const {
        return options()._internal().value(key, fallback);
    }


#define REGISTER_OSGEARTH_LAYER(NAME,CLASS) \
    extern "C" void osgdb_osgearth_##NAME(void) {} \
    static osgEarth::RegisterPluginLoader< osgEarth::PluginLoader<CLASS, osgEarth::Layer> > g_proxy_##CLASS_##NAME(OE_STRINGIFY(osgearth_layer_##NAME));

#define USE_OSGEARTH_LAYER(NAME) \
    extern "C" void osgdb_osgearth_##NAME(void); \
    static osgDB::PluginFunctionProxy proxy_osgearth_layer_##NAME(OE_STRINGIFY(osgdb_osgearth_##NAME));

} // namespace osgEarth
