/* -*-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/>
 */
#ifndef OSGEARTH_DECAL_LAYER
#define OSGEARTH_DECAL_LAYER 1

#include <osgEarth/ImageLayer>
#include <osgEarth/ElevationLayer>
#include <osgEarth/LandCoverLayer>
#include <osg/Image>

namespace osgEarth {
    class Profile;
}

namespace osgEarth { namespace Contrib
{
    /**
     * Image layer to applies georeferenced "decals" on the terrain.
     */
    class OSGEARTH_EXPORT DecalImageLayer : public osgEarth::ImageLayer
    {
    public: // serialization
        class OSGEARTH_EXPORT Options : public ImageLayer::Options {
        public:
            META_LayerOptions(osgEarth, Options, ImageLayer::Options);
            virtual Config getConfig() const;
        private:
            void fromConfig(const Config& conf);
        };

    public:
        META_Layer(osgEarth, DecalImageLayer, Options, osgEarth::ImageLayer, DecalImage);

        //! Adds a decal to the layer.
        bool addDecal(const std::string& id, const GeoExtent& extent, const osg::Image* image);

        //! Removes a decal with the specified ID that was returned from addDecal.
        void removeDecal(const std::string& id);

        //! Extent covered by the decal with the given ID.
        const GeoExtent& getDecalExtent(const std::string& id) const;

        //! Removes all decals
        void clearDecals();

        //! Set blend functions
        //! Supported values are
        //! GL_ONE, GL_ZERO, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
        //! GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA
        void setBlendFuncs(
            GLenum R_source, GLenum R_dest,
            GLenum G_source, GLenum G_dest,
            GLenum B_source, GLenum B_dest,
            GLenum A_source, GLenum A_dest);

        //! Set blend equation
        //! Supported values are
        //! GL_FUNC_ADD, GL_FUNC_MIN, GL_FUNC_MAX
        void setBlendEquations(
            GLenum R_equation,
            GLenum G_equation,
            GLenum B_equation,
            GLenum A_equation);

    public: // ImageLayer

        //! Creates an image for a tile key
        virtual GeoImage createImageImplementation(
            const TileKey& key,
            ProgressCallback* progress) const override;

        //! Compositing entry point
        virtual GeoImage createImageImplementation(
            const GeoImage& canvas,
            const TileKey& key,
            ProgressCallback* progress) const override;

    protected: // Layer

        // post-ctor initialization
        virtual void init();

        virtual ~DecalImageLayer() { }

    private:
        struct Decal {
            GeoExtent _extent;
            osg::ref_ptr<const osg::Image> _image;
        };
        mutable ReadWriteMutex _data_mutex;
        std::list<Decal> _decalList;
        using DecalIndex = std::unordered_map<std::string, std::list<Decal>::iterator>;
        DecalIndex _decalIndex;
        GLenum _src[4];
        GLenum _dst[4];
        GLenum _equation[4];
    };

    /**
    * Layer for making local modifications to elevation data.
    */
    class OSGEARTH_EXPORT DecalElevationLayer : public osgEarth::ElevationLayer
    {
    public: // serialization
        class OSGEARTH_EXPORT Options : public ElevationLayer::Options {
        public:
            META_LayerOptions(osgEarth, Options, ElevationLayer::Options);
            virtual Config getConfig() const;
        private:
            void fromConfig(const Config& conf);
        };

    public:
        META_Layer(osgEarth, DecalElevationLayer, Options, osgEarth::ElevationLayer, DecalElevation);

        //! Adds a heightfield "decal" to the terrain. Each pixel (in the specified channel)
        //! contains a normalized height value [0..1]. This normalized value is mapped to
        //! the elevation range [minElevation..maxElevation] to produce the final height.
        //! Overlapping decals will result in the sum of overlapping height values.
        //!
        //! @param id Unique ID to give this decal
        //! @param extent Geospatial extent of the decal
        //! @param image Raster containing normalized offset values
        //! @param minElevOffset Elevation offset corresponding to zero
        //! @param maxElevOffset Elevation offset corresponding to one
        //! @param channel Image channel containing the normalized offset value
        //! @return true upon success
        bool addDecal(
            const std::string& id, 
            const GeoExtent& extent, 
            const osg::Image* image, 
            float minElevOffset,
            float maxElevOffset,
            GLenum channel =GL_RED);

        //! Adds a heightfield "decal" to the terrain. Each pixel (red channel for GL_RED, 
        //! blue channel for GL_RGB/A) contains a height value. This value is multiplied by
        //! "scale" to produce the final height for that location.
        //! Overlapping decals will result in the sum of overlapping height values.
        //!
        //! @param id Unique ID to give this decal
        //! @param extent Geospatial extent of the decal
        //! @param image Raster containing offset values
        //! @param elevScale Scale factor to apply to raster values
        //! @param channel Image channel containing the normalized offset value
        //! @return true upon success
        bool addDecal(
            const std::string& id, 
            const GeoExtent& extent, 
            const osg::Image* image, 
            float elevScale,
            GLenum channel =GL_RED);

        //! Removes a decal with the specified ID that was returned from addDecal.
        void removeDecal(const std::string& id);

        //! Extent covered by the decal with the given ID.
        const GeoExtent& getDecalExtent(const std::string& id) const;

        //! Removes all decals
        void clearDecals();

    public: // ElevationLayer

        //! Creates an image for a tile key
        virtual GeoHeightField createHeightFieldImplementation(const TileKey& key, ProgressCallback* progress) const;      

    protected: // Layer

        // post-ctor initialization
        virtual void init();

    protected:

        virtual ~DecalElevationLayer() { }

    private:

        struct Decal {
            GeoHeightField _heightfield;
        };
        mutable ReadWriteMutex _data_mutex;
        std::list<Decal> _decalList;
        using DecalIndex = std::unordered_map<std::string, std::list<Decal>::iterator>;
        DecalIndex _decalIndex;
    };


    class OSGEARTH_EXPORT DecalLandCoverLayer : public osgEarth::LandCoverLayer
    {
    public: // serialization
        class OSGEARTH_EXPORT Options : public LandCoverLayer::Options {
        public:
            META_LayerOptions(osgEarth, Options, LandCoverLayer::Options);
            virtual Config getConfig() const;
        private:
            void fromConfig(const Config& conf);
        };

    public:
        META_Layer(osgEarth, DecalLandCoverLayer, Options, osgEarth::LandCoverLayer, DecalLandCover);

        //! Adds a land cover decal to the layer.
        bool addDecal(const std::string& id, const GeoExtent& extent, const osg::Image* image);

        //! Removes a decal with the specified ID that was returned from addDecal.
        void removeDecal(const std::string& id);

        //! Extent covered by the decal with the given ID.
        const GeoExtent& getDecalExtent(const std::string& id) const;

        //! Removes all decals
        void clearDecals();

    public: // ImageLayer

        //! Open this layer
        Status openImplementation();

        //! Creates an image for a tile key
        virtual GeoImage createImageImplementation(const TileKey& key, ProgressCallback* progress) const;

    protected: // Layer

        // post-ctor initialization
        virtual void init();

    protected:

        virtual ~DecalLandCoverLayer() { }

    private:
        struct Decal {
            osg::ref_ptr<const osg::Image> _image;
            GeoExtent _extent;
        };
        mutable ReadWriteMutex _data_mutex;
        std::list<Decal> _decalList;
        using DecalIndex = std::unordered_map<std::string, std::list<Decal>::iterator>;
        DecalIndex _decalIndex;
    };

} } // namespace osgEarth

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Contrib::DecalImageLayer::Options);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Contrib::DecalElevationLayer::Options);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Contrib::DecalLandCoverLayer::Options);

#endif // OSGEARTH_DECAL_LAYER
