/* -*-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_CONFIG_H
#define OSGEARTH_CONFIG_H 1

#include <osgEarth/Common>
#include <osgEarth/optional>
#include <osgEarth/StringUtils>
#include <osg/Object>
#include <osg/Version>
#include <osgDB/Options>
#include <list>
#include <stack>
#include <istream>

namespace osgEarth
{
    class URI;

    typedef std::list<class Config> ConfigSet;


    // general-purpose name/value pair set.
    struct Properties : public std::map<std::string,std::string> {
        std::string get( const std::string& key ) const {
            std::map<std::string,std::string>::const_iterator i = find(key);
            return i != end()? i->second : std::string();
        }
    };


    /**
     * Config is a general-purpose container for serializable data. You store an object's members
     * to Config, and then translate the Config to a particular format (like XML or JSON). Likewise,
     * the object can de-serialize a Config back into member data. Config support the optional<>
     * template for optional values.
     */
    class OSGEARTH_EXPORT Config
    {
    public:
        Config()
            : _isLocation(false) { }

        Config( const std::string& key )
            : _key(key), _isLocation(false) { }

        Config( const std::string& key, const std::string& value ) 
            : _key( key ), _defaultValue( value ), _isLocation(false) { }

        /** Copy ctor */
        Config( const Config& rhs ) 
            : _key(rhs._key), _defaultValue(rhs._defaultValue), _children(rhs._children), _referrer(rhs._referrer), _isLocation(rhs._isLocation), _externalRef(rhs._externalRef), _refMap(rhs._refMap) { }

        virtual ~Config();

        /**
         * Referrer is the context for resolving relative pathnames that occur in this object.
         * For example, if the value is a filename "file.txt" and the referrer is "C:/temp/a.earth",
         * then the full path of the file is "C:/temp/file.txt".
         *
         * Calling this sets a referrer on this object and its children. 
         */
        void setReferrer( const std::string& value );

        /** Access this object's "relative-to" location. */
        const std::string& referrer() const { return _referrer; }

        /** Referrer associated with a key */
        const std::string referrer( const std::string& key ) const {
            return child(key).referrer();
        }

        /** Sets whether this Config's value represents a location, i.e. a URI, filename, or
            other string that can be relocated to be relative to a different referrer. */
        void setIsLocation(bool tf) { _isLocation = tf; }
        bool isLocation() const     { return _isLocation; }

        /** Hint that this Config came from an externally referenced resource. */
        const std::string& externalRef() const { return _externalRef;}
        void setExternalRef(const std::string& externalRef) { _externalRef = externalRef; }

        /** Populate this object from an XML input stream. */
        bool fromXML( std::istream& in );

        /** Encode this object as JSON. */
        std::string toJSON( bool pretty =false ) const;

        /** Populate this object from a JSON string. */
        bool fromJSON( const std::string& json );
        static Config readJSON(const std::string& json);

        /** True if this object contains no data. */
        bool empty() const {
            return _key.empty() && _defaultValue.empty() && _children.empty();
        }

        /** True is this object is a simple key/value pair with no children. */
        bool isSimple() const {
            return !_key.empty() && !_defaultValue.empty() && _children.empty();
        }

        /** The key value for this object */
        std::string& key() { return _key; }
        const std::string& key() const { return _key; }

        /** The value corresponding to the key */
        const std::string& value() const { return _defaultValue; }
        std::string& value() { return _defaultValue; }

        /** Child objects. */
        ConfigSet& children() { return _children; }
        const ConfigSet& children() const { return _children; }

        /** A collection of all the children of this object with a particular key */
        const ConfigSet children( const std::string& key ) const {
            ConfigSet r;
            for(ConfigSet::const_iterator i = _children.begin(); i != _children.end(); i++ ) {
                if ( i->key() == key )
                    r.push_back( *i );
            }
            return r;
        }

        /** Whether this object has a child with a given key */
        bool hasChild( const std::string& key ) const {
            for(ConfigSet::const_iterator i = _children.begin(); i != _children.end(); i++ )
                if ( i->key() == key )
                    return true;
            return false;
        }

        /** Removes all children with the given key */
        void remove( const std::string& key ) {
            for(ConfigSet::iterator i = _children.begin(); i != _children.end(); ) {
                if ( i->key() == key )
                    i = _children.erase( i );
                else
                    ++i;
            }
        }

        /** First child with the given key */
        //Config child( const std::string& key ) const;
        const Config& child( const std::string& key ) const;

        /** Pointer to the first child with the given key, or NULL if none exist */
        const Config* child_ptr( const std::string& key ) const;

        /** Mutable pointer to the first child with the given key, or NULL if none exist */
        Config* mutable_child( const std::string& key );

        /** Merge the contents of another Config object into this object.. danger, read the code
            before you use this */
        void merge( const Config& rhs );

        /** Locate (recursively) the first descendant object with this key, optionally checking
            the current object as well */
        Config* find( const std::string& key, bool checkThis =true );
        const Config* find( const std::string& key, bool checkThis =true) const;

        /** Adds an optional value as a child, but only if its value is set */
        template<typename T>
        void addIfSet( const std::string& key, const optional<T>& opt ) {
            if ( opt.isSet() ) {
                add( key, osgEarth::toString<T>( opt.value() ) );
            }
        }
        
        /** Adds a referenced object as a child, but only if its value is set */
        template<typename T>
        void addObjIfSet( const std::string& key, const osg::ref_ptr<T>& opt ) {
            if ( opt.valid() ) {
                Config conf = opt->getConfig();
                conf.key() = key;
                add( conf );
            }
        }

        /** Adds an optional object as a child, but only if its value is set */
        template<typename T>
        void addObjIfSet( const std::string& key, const optional<T>& obj ) {
            if ( obj.isSet() ) {
                Config conf = obj->getConfig();
                conf.key() = key;
                add( conf );
            }
        }

        /** Adds an enumeration value as a child, but only if its value is set */
        template<typename X, typename Y>
        void addIfSet( const std::string& key, const std::string& val, const optional<X>& target, const Y& targetValue ) {
            if ( target.isSetTo( targetValue ) )
                add( key, val );
        }

        /** Add a value as a child */
        template<typename T>
        void add( const std::string& key, const T& value ) {
            _children.push_back( Config(key, Stringify() << value) );
            _children.back().setReferrer( _referrer );
        }

        /** Add a Config as a child */
        void add( const Config& conf ) {
            _children.push_back( conf );
            _children.back().setReferrer( _referrer );
        }

        /** Add a config as a child, assigning it a key */
        void add( const std::string& key, const Config& conf ) {
            Config temp = conf;
            temp.key() = key;
            add( temp );
        }

        /** Add a set of config objects as children. */
        void add( const ConfigSet& set ) {
            for( ConfigSet::const_iterator i = set.begin(); i != set.end(); i++ )
                add( *i );
        }

        /** Adds an object as a child. */
        template<typename T>
        void addObj( const std::string& key, const T& value ) {
            Config conf = value.getConfig();
            conf.key() = key;
            add( conf );
        }

        /** Adds or replaces a value as a child, but only if it is set */
        template<typename T>
        void updateIfSet( const std::string& key, const optional<T>& opt ) {
            remove(key);
            if ( opt.isSet() ) {
                add( key, osgEarth::toString<T>( opt.value() ) );
            }
        }
        template<typename T>
        void set( const std::string& key, const optional<T>& opt ) {
            updateIfSet(key, opt);
        }
        
        /** Adds or replaces a referenced object as a child, but only if it is set */
        template<typename T>
        void updateObjIfSet( const std::string& key, const osg::ref_ptr<T>& opt ) {
            remove(key);
            if ( opt.valid() ) {
                Config conf = opt->getConfig();
                conf.key() = key;
                add( conf );
            }
        }
        template<typename T>
        void setObj( const std::string& key, const osg::ref_ptr<T>& opt ) {
            updateObjIfSet(key, opt);
        }
        
        /** Adds or replaces an object as a child, but only if it is set */
        template<typename T>
        void updateObjIfSet( const std::string& key, const optional<T>& obj ) {
            if ( obj.isSet() ) {
                remove(key);
                Config conf = obj->getConfig();
                conf.key() = key;
                add( conf );
            }
        }
        template<typename T>
        void setObj( const std::string& key, const optional<T>& obj ) {
            updateObjIfSet(key, obj);
        }
        
        /** Adds or replaces an enumeration value as a child, but only if it is set */
        template<typename X, typename Y>
        void updateIfSet( const std::string& key, const std::string& val, const optional<X>& target, const Y& targetValue ) {
            if (target.isSetTo(targetValue)) {
                remove(key);
                add( key, val );
            }
        }

        // If target is set to targetValue, set key to val.
        template<typename X, typename Y>
        void set( const std::string& key, const std::string& val, const optional<X>& target, const Y& targetValue ) {
            updateIfSet(key, val, target, targetValue);
        }
        
        /** Adds or replaces value as a child */
        template<typename T>
        void update( const std::string& key, const T& value ) {
            update( Config(key, Stringify() << value) );
        }

        /** Adds or replaces a config as a child. */
        void update( const Config& conf ) {
            remove(conf.key());
            add( conf );
        }
        template<typename T>
        void set( const std::string& key, const T& value ) {
            update( key, value );
        }

        /** Adds or replaces an object as a child */
        template<typename T>
        void updateObj( const std::string& key, const T& value ) {
            remove(key);
            Config conf = value.getConfig();
            conf.key() = key;
            add( conf );
        }
        template<typename T>
        void setObj( const std::string& key, const T& value ) {
            updateObj(key, value);
        }

        /** Whether this object has the key OR has a child with the key */
        bool hasValue( const std::string& key ) const {
            return !value(key).empty();
        }

        /** The value of this object (if the key matches) or a matching child object */
        const std::string value( const std::string& key ) const {
            std::string r = trim(child(key).value());
            if ( r.empty() && _key == key )
                r = _defaultValue;
            return r;
        }

        /** Value cast to a particular primitive type (with fallback in case casting fails) */
        template<typename T>
        T value( const std::string& key, T fallback ) const {
            std::string r;
            if ( hasChild( key ) )
                r = child(key).value();
            return osgEarth::as<T>( r, fallback );
        }

        /** Value case to a boolean */
        bool boolValue( bool fallback ) const {
            return osgEarth::as<bool>( _defaultValue, fallback );
        }

        /** Populates the output value iff the Config exists. */
        template<typename T>
        bool getIfSet( const std::string& key, optional<T>& output ) const {
            std::string r;
            if ( hasChild(key) )
                r = child(key).value();
            if ( !r.empty() ) {
                output = osgEarth::as<T>( r, output.defaultValue() );
                return true;
            } 
            else
                return false;
        }
        
        /** Populates the output object iff the Config exists. */
        template<typename T>
        bool getObjIfSet( const std::string& key, optional<T>& output ) const {
            if ( hasChild( key ) ) {
                output = T( child(key) );
                return true;
            }
            else
                return false;
        }
        
        /** Populates the output referenced value iff the Config exists. */
        template<typename T>
        bool getObjIfSet( const std::string& key, osg::ref_ptr<T>& output ) const {
            if ( hasChild( key ) ) {
                output = new T( child(key) );
                return true;
            }
            else
                return false;
        }
        
        /** Populates the output object value iff the Config exists. */
        template<typename T>
        bool getObjIfSet( const std::string& key, T& output ) const {
            if ( hasChild(key) ) {
                output = T( child(key) );
                return true;
            }
            return false;
        }
        
        /** Populates the output enumerable pair iff the Config exists. */
        template<typename X, typename Y>
        bool getIfSet( const std::string& key, const std::string& val, optional<X>& target, const Y& targetValue ) const {
            if ( hasValue( key ) && value( key ) == val ) {
                target = targetValue;
                return true;
            }
            else 
                return false;
        }
        
        /** Populates the output enumerable pair iff the Config exists. */
        template<typename X, typename Y>
        bool getIfSet( const std::string& key, const std::string& val, X& target, const Y& targetValue ) const {
            if ( hasValue(key) && value(key) == val ) {
                target = targetValue;
                return true;
            }
            return false;
        }
        
        /** Populates the ouptut value iff the Config exists. */
        template<typename T>
        bool getIfSet( const std::string& key, T& output ) const {
            if ( hasValue(key) ) {
                output = value<T>(key, output);
                return true;
            }
            return false;
        }

        /** support for conveying non-serializable objects in a Config (in memory only) */
        typedef std::map<std::string, osg::ref_ptr<osg::Referenced> > RefMap;

        void addNonSerializable( const std::string& key, osg::Referenced* obj ) {
            _refMap[key] = obj;
        }
        
        void updateNonSerializable( const std::string& key, osg::Referenced* obj ) {
            _refMap[key] = obj;
        }

        template<typename X>
        X* getNonSerializable( const std::string& key ) const {
            RefMap::const_iterator i = _refMap.find(key);
            return i == _refMap.end() ? 0 : dynamic_cast<X*>( i->second.get() );
        }

        // remove everything from (this) that also appears in rhs (diff)
        Config operator - ( const Config& rhs ) const;

    protected:
        std::string _key;
        std::string _defaultValue;
        ConfigSet   _children;   
        std::string _referrer;
        bool        _isLocation;
        std::string _externalRef;
        RefMap      _refMap;
    };



    // specialization for Config
    template <> inline
    void Config::addIfSet<Config>( const std::string& key, const optional<Config>& opt ) {
        if ( opt.isSet() ) {
            Config conf = opt.value();
            conf.key() = key;
            add( conf );
        }
    }

    template<> inline
    void Config::updateIfSet<Config>( const std::string& key, const optional<Config>& opt ) {
        if ( opt.isSet() ) {
            remove(key);
            Config conf = opt.value();
            conf.key() = key;
            add( conf );
        }
    }

    template<> inline
    void Config::set<Config>( const std::string& key, const optional<Config>& opt ) {
        remove(key);
        if ( opt.isSet() ) {
            Config conf = opt.value();
            conf.key() = key;
            add( conf );
        }
    }

    template<> inline
    bool Config::getIfSet<Config>( const std::string& key, optional<Config>& output ) const {
        if ( hasChild( key ) ) {
            output = child(key);
            return true;
        }
        else
            return false;
    }

    template<> inline
    void Config::add<std::string>( const std::string& key, const std::string& value ) {
        _children.push_back( Config( key, value ) );
        _children.back().setReferrer( _referrer );
    }

    template<> inline
    void Config::update<std::string>( const std::string& key, const std::string& value ) {
        remove(key);
        add( Config(key, value) );
    }

    template<> inline
    void Config::update<Config>( const std::string& key, const Config& conf ) {
        remove(key);
        Config temp = conf;
        temp.key() = key;
        add( temp );
    }

    template<> inline
    void Config::add<float>( const std::string& key, const float& value ) {
        add( key, Stringify() << std::setprecision(8) << value );
        //add( key, Stringify() << std::fixed << std::setprecision(8) << value );
    }

    template<> inline
    void Config::add<double>( const std::string& key, const double& value ) {
        add( key, Stringify() << std::setprecision(16) << value );
        //add( key, Stringify() << std::fixed << std::setprecision(16) << value );
    }

    template<> inline
    void Config::update<float>( const std::string& key, const float& value ) {
        update( key, Stringify() << std::setprecision(8) << value );
        //update( key, Stringify() << std::fixed << std::setprecision(8) << value );
    }

    template<> inline
    void Config::update<double>( const std::string& key, const double& value ) {
        update( key, Stringify() << std::setprecision(16) << value );
    }

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

    /**
     * Base class for all serializable options classes.
     */
    class OSGEARTH_EXPORT ConfigOptions
    {
    public:
        ConfigOptions( const Config& conf =Config() )
            : _conf( conf ) { }
        ConfigOptions( const ConfigOptions& rhs )
            : _conf( rhs.getConfig() ) { } //rhs._conf ) { }

        virtual ~ConfigOptions();
        
        const std::string& referrer() const { return _conf.referrer(); }

        ConfigOptions& operator = ( const ConfigOptions& rhs ) {
            if ( this != &rhs ) {
                _conf = rhs.getConfig();
                mergeConfig( _conf );
            }
            return *this;
        }

        void merge( const ConfigOptions& rhs ) {
            _conf.merge( rhs._conf );
            mergeConfig( rhs.getConfig() );
        }

        virtual Config getConfig() const;

        bool empty() const { return _conf.empty(); }

    protected:
        virtual void mergeConfig( const Config& conf ) { }

        Config _conf;
    };

    /**
     * Base configoptions class for driver options.
     */
    class OSGEARTH_EXPORT DriverConfigOptions : public ConfigOptions
    {
    public:
        DriverConfigOptions( const ConfigOptions& rhs =ConfigOptions() )
            : ConfigOptions( rhs ) { fromConfig( _conf ); }

        /** dtor */
        virtual ~DriverConfigOptions();

        /** Gets or sets the name of the driver to load */
        void setDriver( const std::string& value ) { _driver = value; }
        const std::string& getDriver() const { return _driver; }

    public:
        virtual Config getConfig() const {
            Config conf = ConfigOptions::getConfig();
            conf.set("driver", _driver);
            return conf;
        }

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

    public:
        void fromConfig( const Config& conf ) {
            _driver = conf.value( "driver" );
            if ( _driver.empty() && conf.hasValue("type") )
                _driver = conf.value("type");
        }

    private:
        std::string _name, _driver;
    };
}

#endif // OSGEARTH_CONFIG_H

