/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2013 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_TILE_SOURCE_H
#define OSGEARTH_TILE_SOURCE_H 1

// Need to undef Status in case it has been defined in Xlib.h. This can happen on Linux
#undef Status

#include <limits.h>

#include <osg/Version>

#include <osgEarth/Common>
#include <osgEarth/CachePolicy>
#include <osgEarth/TileKey>
#include <osgEarth/Profile>
#include <osgEarth/MemCache>
#include <osgEarth/Progress>
#include <osgEarth/ThreadingUtils>

#include <osg/Referenced>
#include <osg/Object>
#include <osg/Image>
#include <osg/Shape>
#if OSG_MIN_VERSION_REQUIRED(2,9,5)
#include <osgDB/Options>
#endif
#include <osgDB/ReadFile>

#include <OpenThreads/Mutex>

#include <string>


#define TILESOURCE_CONFIG "tileSourceConfig"


namespace osgEarth
{
    /**
     * Configuration options for a tile source driver.
     */
    class OSGEARTH_EXPORT TileSourceOptions : public DriverConfigOptions
    {
    public:

        optional<int>& tileSize() { return _tileSize; }
        const optional<int>& tileSize() const { return _tileSize; }

        optional<float>& noDataValue() { return _noDataValue; }
        const optional<float>& noDataValue() const { return _noDataValue; }

        optional<float>& noDataMinValue() { return _noDataMinValue; }
        const optional<float>& noDataMinValue() const { return _noDataMinValue; }

        optional<float>& noDataMaxValue() { return _noDataMaxValue; }
        const optional<float>& noDataMaxValue() const { return _noDataMaxValue; }

        optional<std::string>& blacklistFilename() { return _blacklistFilename; }
        const optional<std::string>& blacklistFilename() const { return _blacklistFilename; }

        optional<ProfileOptions>& profile() { return _profileOptions; }
        const optional<ProfileOptions>& profile() const { return _profileOptions; }

        optional<int>& L2CacheSize() { return _L2CacheSize; }
        const optional<int>& L2CacheSize() const { return _L2CacheSize; }

        optional<bool>& bilinearReprojection() { return _bilinearReprojection; }
        const optional<bool>& bilinearReprojection() const { return _bilinearReprojection; }

    public:
        TileSourceOptions( const ConfigOptions& options =ConfigOptions() );

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

    public:
        virtual Config getConfig() const;

    protected:
        virtual void mergeConfig( const Config& conf );

    private:
        void fromConfig( const Config& conf );

        optional<int>            _tileSize;
        optional<float>          _noDataValue, _noDataMinValue, _noDataMaxValue;
        optional<ProfileOptions> _profileOptions;
        optional<std::string>    _blacklistFilename;
        optional<int>            _L2CacheSize;
        optional<bool>           _bilinearReprojection;
    };

    typedef std::vector<TileSourceOptions> TileSourceOptionsVector;

    /**
     * A collection of tiles that should be considered blacklisted
     */
    class OSGEARTH_EXPORT TileBlacklist : public virtual osg::Referenced
    {
    public:
        /**
         *Creates a new TileBlacklist
         */
        TileBlacklist();

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

        /**
         *Adds the given tile to the blacklist
         */
        void add(const osgTerrain::TileID &tile);

        /**
         *Removes the given tile from the blacklist
         */
        void remove(const osgTerrain::TileID& tile);

        /**
         *Removes all tiles from the blacklist
         */
        void clear();

        /**
         *Returns whether the given tile is in the blacklist
         */
        bool contains(const osgTerrain::TileID &tile) const;

        /**
         *Returns the size of the blacklist
         */
        unsigned int size() const;

        /**
         *Reads a TileBlacklist from the given istream
         */
        static TileBlacklist* read(std::istream &in);

        /**
         *Reads a TileBlacklist from the given filename
         */
        static TileBlacklist* read(const std::string &filename);

        /**
         *Writes this TileBlacklist to the given ostream
         */
        void write(std::ostream &output) const;

        /**
         *Writes this TileBlacklist to the given filename
         */
        void write(const std::string &filename) const;

    private:
        typedef std::set< osgTerrain::TileID > BlacklistedTiles;
        BlacklistedTiles _tiles;
        osgEarth::Threading::ReadWriteMutex _mutex;
    };

    /**
     * A TileSource is an object that can create image and/or heightfield tiles. Driver
     * plugins are responsible for creating and returning a TileSource that the Map
     * will then use to create tiles for tile keys.
     */
    class OSGEARTH_EXPORT TileSource : public virtual osg::Object
    {
    public:
        /** Initialization status */
        struct Status
        {
        public:
            Status() { }
            Status(const Status& rhs) : _msg(rhs._msg) { }
            Status(const std::string& msg) : _msg(msg) { }
            bool isOK() const { return _msg.empty(); }
            bool isError() const { return !_msg.empty(); }
            const std::string& message() const { return _msg; }
            bool operator == (const Status& rhs) const { return _msg.compare(rhs._msg)==0; }
            bool operator != (const Status& rhs) const { return _msg.compare(rhs._msg)!=0; }
            static Status Error(const std::string& msg) { return Status(msg); }
        private:
            std::string _msg;
        };

        static Status STATUS_OK;

    public:
        struct ImageOperation : public osg::Referenced {
            virtual void operator()( osg::ref_ptr<osg::Image>& in_out_image ) =0;
        };

        struct HeightFieldOperation : public osg::Referenced {
            virtual void operator()( osg::ref_ptr<osg::HeightField>& in_out_hf ) =0;
        };

    public:
        TileSource( const TileSourceOptions& options =TileSourceOptions() );

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

        /**
         * Gets the number of pixels per tile for this TileSource.
         */
        virtual int getPixelsPerTile() const;

        /**
         * Gets the list of areas with data for this TileSource
         */
        const DataExtentList& getDataExtents() const { return _dataExtents; }
        DataExtentList& getDataExtents() { return _dataExtents; }

        /**
         * Creates an image for the given TileKey. The TileKey's profile must match
         * the profile of the TileSource.
         */
        virtual osg::Image* createImage(
            const TileKey&        key,
            ImageOperation*       op        =0L,
            ProgressCallback*     progress  =0L );

        /**
         * Creates a heightfield for the given TileKey. The TileKey's profile must match
         * the profile of the TileSource.
         */
        virtual osg::HeightField* createHeightField(
            const TileKey&        key,
            HeightFieldOperation* op        =0L,
            ProgressCallback*     progress  =0L );

    public:

        /**
         * Returns True if this tile source initialized properly and has a valid
         * profile.
         */
        virtual bool isOK() const;
        bool isValid() const { return isOK(); }

        /**
         * Gets the Profile of the TileSource
         */
        virtual const Profile* getProfile() const;

        /**
         * Gets the nodata elevation value
         */
        virtual float getNoDataValue() {
            return _options.noDataValue().value(); }

        /**
         * Gets the nodata min value
         */
        virtual float getNoDataMinValue() {
            return _options.noDataMinValue().value(); }

        /**
         * Gets the nodata max value
         */
        virtual float getNoDataMaxValue() {
            return _options.noDataMaxValue().value(); }        

        /**
         * Gets the preferred extension for this TileSource
         */
        virtual std::string getExtension() const {return "png";}

        /**
         *Gets the blacklist for this TileSource
         */
        TileBlacklist* getBlacklist();
        const TileBlacklist* getBlacklist() const;

        /**
         * Whether or not the source has data for the given TileKey
         */
        virtual bool hasData(const TileKey& key) const;

        /**
         * Whether the tile source can generate data for the specified LOD.
         */
        virtual bool hasDataAtLOD( unsigned lod ) const;

        /**
         * Whether the tile source can generate data within the specified extent
         */
        virtual bool hasDataInExtent( const GeoExtent& extent ) const;

        /**
         * Whether this TileSource produces tiles whose data can change after
         * it's been created.
         */
        virtual bool isDynamic() const { return false; }

        /**
         * A hint as to what kind of caching policy would be appropriate to employ
         * on this data source. By default, this is the default, which is to use a
         * cache if one is configured. But a TileSource can report that caching should
         * not be used (for whatever reason) by returning CachePolicy::NO_CACHE.
         */
        virtual CachePolicy getCachePolicyHint() const { return CachePolicy::DEFAULT; }


        /**
         * Returns true if data from this source can be cached to disk
         * @deprecated. Use getCachePolicyHint instead.
         */
        bool supportsPersistentCaching() const {
            return getCachePolicyHint() != CachePolicy::NO_CACHE; }

        /**
         * Starts up the tile source.
         */
        const Status& startup( const osgDB::Options* dbOptions );


        /** The options used to construct this tile source. */
        const TileSourceOptions& getOptions() const { return _options; }

    public:

        /* methods required by osg::Object */
        virtual osg::Object* cloneType() const { return 0; } // cloneType() not appropriate
        virtual osg::Object* clone(const osg::CopyOp&) const { return 0; } // clone() not appropriate
        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const TileSource*>(obj)!=NULL; }
        virtual const char* className() const { return "TileSource"; }
        virtual const char* libraryName() const { return "osgEarth"; }

    protected:

        virtual ~TileSource();

        /**
         * Initializes the tile source (called by startup)
         *
         * The osgEarth engine calls this function to initialize a TileSource using an
         * active osgDB::Options. The method returns a status code indicating whether
         * intialization succeeded (in which case the owning layer will become enabled)
         * or failed (in which case the owning layer will become disabled.
         *
         * This method replaces the now-deprecated initialize method below.
         *
         * The Subclass should override this to report a correct initialization status.
         * The default implementation reports STATUS_OK (for compatibility).
         */
        virtual Status initialize( const osgDB::Options* dbOptions );

        /**
         * Creates an image for the given TileKey.
         * The returned object is new and is the responsibility of the caller.
         */
        virtual osg::Image* createImage(
            const TileKey&        key,
            ProgressCallback*     progress ) = 0;

        /**
         * Creates a heightfield for the given TileKey
         * The returned object is new and is the responsibility of the caller.
         */
        virtual osg::HeightField* createHeightField(
            const TileKey&        key,
            ProgressCallback*     progress );

        /**
         * Called by subclasses to initialize their profile
         */
        void setProfile( const Profile* profile );

        /**
         * Sets the status of this tile source. Called by a subclass to
         * set the status
         */
        void setStatus( Status status );


    protected: // deprecated

        /**
         * @deprecated
         * Initializes the TileSource. This is the old initialize method; it will exists
         * for backwards compatibility with older user-defined TileSource implementations.
         * Consider updating to the new initialize() method above since it properly
         * reports the results of the initialization.
         */
        virtual void initialize(
            const osgDB::Options* dbOptions,
            const Profile*        overrideProfile ) { }

    private:

        osg::ref_ptr<const Profile> _profile;
        const TileSourceOptions     _options;

        friend class Map;
        friend class MapEngine;
        friend class TileSourceFactory;
        friend class CompositeTileSource;

        osg::ref_ptr< TileBlacklist > _blacklist;
        std::string _blacklistFilename;

        osg::ref_ptr<MemCache> _memCache;

        DataExtentList _dataExtents;
        Status         _status;
    };


    typedef std::vector< osg::ref_ptr<TileSource> > TileSourceVector;

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

    class OSGEARTH_EXPORT TileSourceDriver : public osgDB::ReaderWriter
    {
    protected:
        const TileSourceOptions& getTileSourceOptions( const osgDB::ReaderWriter::Options* rwOpt ) const;
    };

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

    /**
     * Creates TileSource instances and chains them together to create
     * tile source "pipelines" for data access and processing.
     */
    class OSGEARTH_EXPORT TileSourceFactory
    {
    public:
        static TileSource* create( const TileSourceOptions& options );
    };
}

#endif // OSGEARTH_TILE_SOURCE_H
