/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2020 Pelican Mapping
 * MIT License
 */
#ifndef OSGEARTH_PROCEDURAL_GroundCover
#define OSGEARTH_PROCEDURAL_GroundCover 1

#include "Export"
#include <osgEarth/Config>
#include <osgEarth/ImageLayer>
#include <osgEarth/LandCoverLayer>
#include <osgEarth/ResourceLibrary>
#include <osgEarth/BillboardSymbol>
#include <osg/BoundingBox>
#include <osg/Shader>

namespace osgDB {
    class Options;
}
namespace osgEarth {
    class Map;
}

namespace osgEarth { namespace Splat
{
    using namespace osgEarth;
    
    class OSGEARTHSPLAT_EXPORT GroundCoverBiomeOptions : public ConfigOptions
    {
    public:
        GroundCoverBiomeOptions(const ConfigOptions& conf = ConfigOptions()) : ConfigOptions(conf) {
            fromConfig( _conf );
        }

        /** Name of the biome classes to use. This is one or more class names from the legend,
          * separated by whitespace. e.g.: "forest grassland swamp" */
        optional<std::string>& biomeClasses() { return _biomeClasses; }
        const optional<std::string>& biomeClasses() const { return _biomeClasses; }

        /** Symbology used to conigure rendering in this biome */
        SymbolVector& symbols() { return _symbols; }
        const SymbolVector& symbols() const { return _symbols; }

        /** Percentage of land that this layer's instances will cover [0..1]. Lower
        * values will result is more "patchiness" of placement. */
        optional<float>& fill() { return _fill; }
        const optional<float>& fill() const { return _fill; }

    protected:
        optional<std::string> _biomeClasses;
        SymbolVector _symbols;
        optional<float> _fill;

    public:    
        void fromConfig(const Config& conf);
        Config getConfig() const;
        virtual void mergeConfig( const Config& conf ) {
            ConfigOptions::mergeConfig( conf );
            fromConfig( conf );
        }
    };
    typedef std::vector<GroundCoverBiomeOptions> GroundCoverBiomeOptionsVector;


    class OSGEARTHSPLAT_EXPORT GroundCoverOptions : public ConfigOptions
    {
    public:
        GroundCoverOptions(const ConfigOptions& conf = ConfigOptions());

        /** Name of the land cover layer */
        optional<std::string>& name() { return _name; }
        const optional<std::string>& name() const { return _name; }

        /** Maximum viewing distance from camera */
        optional<float>& maxDistance() { return _maxDistance; }
        const optional<float>& maxDistance() const { return _maxDistance; }

        /** Wind speed [0..1] */
        optional<float>& wind() { return _wind; }
        const optional<float>& wind() const { return _wind; }

        /** Metric that controls the number of land cover instances will render
          * in a given area. [1..5] */
        optional<float>& density() { return _density; }
        const optional<float>& density() const { return _density; }

        //! How far apart the land cover instances are
        optional<float>& spacing() { return _spacing; }
        const optional<float>& spacing() const { return _spacing; }

        /** Percentage of land that this layer's instances will cover [0..1]. Lower
          * values will result is more "patchiness" of placement. */
        optional<float>& fill() { return _fill; }
        const optional<float>& fill() const { return _fill; }

        /** Brightness factor for rendering [1..], 1 = default */
        optional<float>& brightness() { return _brightness; }
        const optional<float>& brightness() const { return _brightness; }

        /** Contrast factor for rendering [0..], 0 = default */
        optional<float>& contrast() { return _contrast; }
        const optional<float>& contrast() const { return _contrast; }

        /** Biomes comprising this layer. */
        GroundCoverBiomeOptionsVector& biomes() { return _biomes; }
        const GroundCoverBiomeOptionsVector& biomes() const { return _biomes; }

    public:
        virtual Config getConfig() const;
    protected:
        virtual void mergeConfig(const Config& conf) {
            ConfigOptions::mergeConfig(conf);
            fromConfig(conf);
        }
        void fromConfig(const Config& conf);

    protected:
        optional<std::string> _name;
        optional<unsigned> _lod;
        optional<float> _maxDistance;
        optional<float> _density;
        optional<float> _spacing;
        optional<float> _fill;
        optional<float> _wind;
        optional<float> _brightness;
        optional<float> _contrast;
        GroundCoverBiomeOptionsVector _biomes;
    };

    //! Base class for any ground cover object
    class OSGEARTHSPLAT_EXPORT GroundCoverObject : public osg::Referenced
    {
    public:
        enum Type {
            TYPE_BILLBOARD,
            TYPE_MODEL
        };
        virtual Type getType() const =0;
    };

    //! Collection of ground cover objects
    typedef std::vector<osg::ref_ptr<GroundCoverObject> > GroundCoverObjects;

    /**
     * Billboard object - camera-facing side image coupled with an optional
     * top-down image.
     */
    class OSGEARTHSPLAT_EXPORT GroundCoverBillboard : public GroundCoverObject
    {
    public:
        GroundCoverBillboard(osg::Image* sideImage, osg::Image* topImage, const BillboardSymbol* symbol)
            : _sideImage(sideImage), _topImage(topImage), _symbol(symbol) { }

        virtual Type getType() const { return TYPE_BILLBOARD; }

        osg::ref_ptr<osg::Image> _sideImage;
        osg::ref_ptr<osg::Image> _topImage;
        osg::ref_ptr<const BillboardSymbol> _symbol;
    };

    /**
    * Billboard object - camera-facing side image coupled with an optional
    * top-down image.
    */
    class OSGEARTHSPLAT_EXPORT GroundCoverModel : public GroundCoverObject
    {
    public:
        GroundCoverModel(osg::Node* model) { }

        virtual Type getType() const { return TYPE_MODEL; }

        osg::ref_ptr<osg::Node> _model;
    };

    /**
     * A Biome is a collection of ground cover objects corresponding
     * to a set of land cover classes. For example, the "forest" biome
     * might map to three different tree billboards.
     */
    class OSGEARTHSPLAT_EXPORT GroundCoverBiome : public osg::Referenced
    {
    public:
        GroundCoverBiome() : _code(0) { }

        /** classification names for this biome (space separated) */
        void setClasses( const std::string& value ) { _classNames = value; }
        const std::string& getClasses() const { return _classNames; }

        /** Objects that may be used to render land cover in this biome */
        GroundCoverObjects& getObjects() { return _objects; }
        const GroundCoverObjects& getObjects() const { return _objects; }

        /** Percentage of land that this layer's instances will cover [0..1]. Lower
          * values will result is more "patchiness" of placement. */
        optional<float>& fill() { return _fill; }
        const optional<float>& fill() const { return _fill; }

    protected:
        virtual ~GroundCoverBiome() { }

        std::string    _classNames;
        int            _code;
        optional<float> _fill;
        GroundCoverObjects _objects;

    public:
        typedef std::map<URI, osg::ref_ptr<osg::Image> > ImageCache;
        bool configure(const ConfigOptions& conf, const osgDB::Options* dbo, ImageCache& cache);
    };

    typedef std::vector< osg::ref_ptr<GroundCoverBiome> > GroundCoverBiomes;


    /**
     * Interface for controlling ground cover appearance.
     */
    class OSGEARTHSPLAT_EXPORT GroundCover : public osg::Referenced
    {
    public:
        GroundCover(const GroundCoverOptions& options);
        
        GroundCoverOptions& options() { return _options; }
        const GroundCoverOptions& options() const { return _options; }

        /** Name of this layer */
        //void setName(const std::string& name) { _name = name; }
        const std::string& getName() const { return options().name().get(); }

        /** Biomes comprising this layer. */
        GroundCoverBiomes& getBiomes() { return _biomes; }
        const GroundCoverBiomes& getBiomes() const { return _biomes; }

        //! Wind factor
        void setWind(float wind);
        float getWind() const;

        //! Spacing
        void setSpacing(float value);
        float getSpacing() const;

        //! Fill percentage
        void setFill(float value);
        float getFill() const;

        //! Max draw distance
        void setMaxDistance(float distance);
        float getMaxDistance() const;

    public:

        //! Total number of individual objects this class can render
        int getTotalNumObjects() const;

        /** Creates the shader that contains the GLSL APOI for accessing this layer's information */
        osg::Shader* createShader() const;

        /** Creates the shader that resolves land cover information into billboard data. */
        //osg::Shader* createPredicateShader(const Coverage*) const;
        osg::Shader* createPredicateShader(LandCoverDictionary*, LandCoverLayer*) const;

        /** Builds the texture object containing all the data for this layer. */
        osg::Texture* createTexture() const;

        /** The stateset containing the shaders and state for rendering this layer. */
        osg::StateSet* getOrCreateStateSet();

        osg::StateSet* getStateSet() const { return _stateSet.get(); }

        void resizeGLObjectBuffers(unsigned maxSize);

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

        //! Finds the biome representing this land cover class,
        //! or NULL if there isn't one
        const GroundCoverBiome* getBiome(const LandCoverClass*) const;

        //! Get number of models
        int getTotalNumModels() const;

    protected:
        virtual ~GroundCover() { }

        GroundCoverOptions _options;
        GroundCoverBiomes _biomes;

        osg::ref_ptr<osg::StateSet> _stateSet;

    public:
        bool configure(const osgDB::Options* readOptions);
    };

} } // namespace osgEarth::Splat

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Splat::GroundCoverOptions);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Splat::GroundCoverBiomeOptions);

#endif // OSGEARTH_PROCEDURAL_GroundCover
