/* -*-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_IMAGE_TERRAIN_LAYER_H
#define OSGEARTH_IMAGE_TERRAIN_LAYER_H 1

#include <osgEarth/Common>
#include <osgEarth/Config>
#include <osgEarth/ColorFilter>
#include <osgEarth/TileSource>
#include <osgEarth/TerrainLayer>
#include <osgEarth/URI>

namespace osgEarth
{
    class Profile;

    /**
     * Initialization options for an image layer.
     */
    class OSGEARTH_EXPORT ImageLayerOptions : public TerrainLayerOptions
    {
    public:
        // Construct empty options
        ImageLayerOptions();

        // Deserialize options from a config structure
        ImageLayerOptions(const ConfigOptions& options);

        // Construct options with a default name
        ImageLayerOptions(const std::string& name);

        // Consruct options with a name and a driver configuration
        ImageLayerOptions(const std::string& name, const TileSourceOptions& tileSourceOptions);

        /** dtor */
        virtual ~ImageLayerOptions() { }


    public: // properties
        
        /**
         * The initial minimum camera range at which this layer is visible.
         */
        optional<float>& minVisibleRange() { return _minRange; }
        const optional<float>& minVisibleRange() const { return _minRange; }

        /**
         * The initial maximum camera range at which this layer is visible.
         */
        optional<float>& maxVisibleRange() { return _maxRange; }
        const optional<float>& maxVisibleRange() const { return _maxRange; }

        /**
         * Gets or sets the nodata image for this MapLayer
         */
        optional<URI>& noDataImageFilename() { return _noDataImageFilename; }
        const optional<URI>& noDataImageFilename() const { return _noDataImageFilename; }

        /**
         * Gets the transparent color of this TileSource
         */
        optional<osg::Vec4ub>& transparentColor() { return _transparentColor; }
        const optional<osg::Vec4ub>& transparentColor() const { return _transparentColor; }
        
        /**
         * Filters attached to this layer.
         */
        ColorFilterChain& colorFilters() { return _colorFilters; }
        const ColorFilterChain& colorFilters() const { return _colorFilters; }

        /**
         * A shared image layer is bound to its own texture image units at render
         * so that all layers have access to its sampler.
         */
        optional<bool>& shared() { return _shared; }
        const optional<bool>& shared() const { return _shared; }

        /**
         * Whether this is a "coverage" layer, i.e a layer that conveys discrete semantic
         * values rather than color data. This is a hint to disable any features that would
         * corrupt the data, like filtering, interpolation, or lossy compression.
         */
        optional<bool>& coverage() { return _coverage; }
        const optional<bool>& coverage() const { return _coverage; }

        /**
         * Whether to feather out alpha regions for this image layer with the featherAlphaRegions function.
         * Used to get proper blending when you have datasets that abutt exactly with no overlap.
         */
        optional<bool>& featherPixels() { return _featherPixels; }
        const optional<bool>& featherPixels() const { return _featherPixels; }

        /**
         * The minification filter to be applied to textures. This is the interpolation
         * mechanism to use when the texture uses fewer screen pixels than are available.
         */
        optional<osg::Texture::FilterMode>& minFilter() { return _minFilter; }
        const optional<osg::Texture::FilterMode>& minFilter() const { return _minFilter; }

        /**
         * The magnification filter to be applied to textures. This is the interpolation
         * mechanism to use when the texture uses more screen pixels than are available.
         */
        optional<osg::Texture::FilterMode>& magFilter() { return _magFilter; }
        const optional<osg::Texture::FilterMode>& magFilter() const { return _magFilter; }

        /**
         * Texture compression mode to use. Default is "unset", which means to 
         * automatically compute the best compression mode to use.
         */
        optional<osg::Texture::InternalFormatMode>& textureCompression() { return _texcomp; }
        const optional<osg::Texture::InternalFormatMode>& textureCompression() const { return _texcomp; }

        /** For shared layer, name of hte texture sampler uniform. */
        optional<std::string>& shareTexUniformName() { return _shareTexUniformName; }
        const optional<std::string>& shareTexUniformName() const { return _shareTexUniformName; }

        /** For shared layer, name of hte texture sampler matrix uniform. */
        optional<std::string>& shareTexMatUniformName() { return _shareTexMatUniformName; }
        const optional<std::string>& shareTexMatUniformName() const { return _shareTexMatUniformName; }

    public:

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

        //optional<float>       _opacity;
        optional<float>       _minRange;
        optional<float>       _maxRange;
        optional<osg::Vec4ub> _transparentColor;
        optional<URI>         _noDataImageFilename;
        ColorFilterChain      _colorFilters;
        optional<bool>        _shared;
        optional<bool>        _coverage;
        optional<bool>        _featherPixels;
        optional<osg::Texture::FilterMode> _minFilter;
        optional<osg::Texture::FilterMode> _magFilter;
        optional<osg::Texture::InternalFormatMode> _texcomp;
        optional<std::string> _shareTexUniformName;
        optional<std::string> _shareTexMatUniformName;
    };

    //--------------------------------------------------------------------

    /**
     * Internal utility class for post-processing image tiles that come from 
     * a TileSource
     */
    class ImageLayerTileProcessor 
    {
    public:
        ImageLayerTileProcessor( const ImageLayerOptions& options =ImageLayerOptions() );

        /** dtor */
        virtual ~ImageLayerTileProcessor() { }

        void init( const ImageLayerOptions& options, const osgDB::Options* dbOptions, bool layerInTargetProfile );

        void process( osg::ref_ptr<osg::Image>& image ) const;

    private:
        ImageLayerOptions                  _options;
        osg::Vec4f                         _chromaKey;
        osg::ref_ptr<osg::Image>           _noDataImage;
        bool                               _layerInTargetProfile;
    };


    struct ImageLayerCallback : public TerrainLayerCallback
    {
        //virtual void onOpacityChanged(class ImageLayer* layer) { }
        virtual void onVisibleRangeChanged(class ImageLayer* layer) { }
        virtual void onColorFiltersChanged(class ImageLayer* layer) { }

        typedef void (ImageLayerCallback::*MethodPtr)(class ImageLayer* layer);
    };


    /**
     * A map terrain layer containing bitmap image data.
     */
    class OSGEARTH_EXPORT ImageLayer : public TerrainLayer
    {
    public:
        META_Layer(osgEarth, ImageLayer, ImageLayerOptions);

        /**
         * Construct a new ImageLayer. Use the options() to configure it
         * before adding it to a Map.
         */
        ImageLayer();

        /**
         * Constructs a new image layer.
         */
        ImageLayer(const ImageLayerOptions& options);

        /**
         * Constructs a new image layer with the given name and driver options.
         */
        ImageLayer(const std::string& name, const TileSourceOptions& tileSourceOptions);

        /**
         * Constructs a new image layer with a custom TileSource.
         *
         * Note: the ImageLayerOptions contains a driver() member for configuring a 
         * TileSource. But in this constructor, you are passing in an existing TileSource,
         * and thus the driver() member in ImageLayerOptions will not be used.
         */
        ImageLayer(const ImageLayerOptions& options, TileSource* tileSource);

    public:

        /** Override: see TerrainLayer */
        virtual void setTargetProfileHint( const Profile* profile );

        /**
         * Add a color filter (to the end of the chain)
         */
        void addColorFilter( ColorFilter* filter );

        /**
         * Remove a color filter
         */
        void removeColorFilter( ColorFilter* filter );

        /** 
         * Access the image filter chain
         */
        const ColorFilterChain& getColorFilters() const;

    public: // runtime properties

        /**
         * Minimum camera range at which this image layer is visible 
         */
        float getMinVisibleRange() const;
        void setMinVisibleRange( float minVisibleRange );
        
        /**
         * Maximum camera range at which this image layer is visible 
         */
        float getMaxVisibleRange() const;
        void setMaxVisibleRange( float maxVisibleRange );

        /**
         * Whether this layer is marked for render sharing
         */
        bool isShared() const;

        /**
         * Whether this layer represents coverage data that should not be subject
         * to color-space filtering, interpolation, or compression.
         */
        bool isCoverage() const;

        /**
         * When isShared() == true, the engine will call this function to bind the
         * shared layer to a texture image unit.
         */
        optional<int>& shareImageUnit() { return _shareImageUnit; }
        const optional<int>& shareImageUnit() const { return _shareImageUnit; }

        /**
         * When isShared() == true, this will return the name of the uniform holding the
         * image's texture. 
         */
        optional<std::string>& shareTexUniformName() { return _shareTexUniformName; }
        const optional<std::string>& shareTexUniformName() const { return _shareTexUniformName; }

        /**
         * When isShared() == true, this will return the name of the uniform holding the
         * image's texture matrix. 
         */
        optional<std::string>& shareTexMatUniformName() { return _shareTexMatUniformName; }
        const optional<std::string>& shareTexMatUniformName() const { return _shareTexMatUniformName; }
        

    public: // methods

        virtual bool createTextureSupported() const { return false; }
        virtual osg::Texture* createTexture(const TileKey& key, ProgressCallback* progress, osg::Matrixf& textureMatrix) { return 0L; }

        /**
         * Creates a GeoImage from this layer corresponding to the provided key. The
         * image is in the profile of the key and will be reprojected, mosaiced and
         * cropped automatically.
         */
        GeoImage createImage( const TileKey& key, ProgressCallback* progress = 0);

        /**
         * Creates an image that is in the image layer's native profile.
         */
        GeoImage createImageInNativeProfile(const TileKey& key, ProgressCallback* progress);

        /**
         * Applies the texture compression options to a texture.
         */
        void applyTextureCompressionMode(osg::Texture* texture) const;
       
        typedef ImageLayerCallback Callback;

    public: // Layer
        
        virtual const Status& open();

        //! Subclass can override this when not using a TileSource
        //! by calling setTileSourceExpected(false).
        virtual GeoImage createImageImplementation(const TileKey&, ProgressCallback* progress);

    protected: // Layer
            
        virtual void init();

    protected:

        /** dtor */
        virtual ~ImageLayer() { }

        // Constructor for a subclass that owns the layer options structure
        ImageLayer(ImageLayerOptions* optionsPtr);

    private:

        // Creates an image that's in the same profile as the provided key.
        GeoImage createImageInKeyProfile(const TileKey& key, ProgressCallback* progress);

        // Fetches an image from the underlying TileSource whose data matches that of the
        // key extent.
        GeoImage createImageFromTileSource(const TileKey& key, ProgressCallback* progress);

        // Fetches multiple images from the TileSource; mosaics/reprojects/crops as necessary, and
        // returns a single tile. This is called by createImageFromTileSource() if the key profile
        // doesn't match the layer profile.
        GeoImage assembleImage(const TileKey& key, ProgressCallback* progress);

        osg::ref_ptr<TileSource::ImageOperation> _preCacheOp;
        Threading::Mutex                         _mutex;
        osg::ref_ptr<osg::Image>                 _emptyImage;
        optional<int>                            _shareImageUnit;
        optional<std::string>                    _shareTexUniformName;
        optional<std::string>                    _shareTexMatUniformName;

        virtual void fireCallback(ImageLayerCallback::MethodPtr method);

        TileSource::ImageOperation* getOrCreatePreCacheOp();
    };

    typedef std::vector< osg::ref_ptr<ImageLayer> > ImageLayerVector;

} // namespace osgEarth

#endif // OSGEARTH_IMAGE_TERRAIN_LAYER_H
