/* -*-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_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() { }

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

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

        Config( const Config& rhs ) 
            : _key(rhs._key), _defaultValue(rhs._defaultValue), _children(rhs._children), _referrer(rhs._referrer), _refMap(rhs._refMap) { }

        virtual ~Config();

        /** Context for resolving relative URIs that occur in this Config */
        void setReferrer( const std::string& value );
        void inheritReferrer( const std::string& value );
        const std::string& referrer() const { return _referrer; }

        bool fromXML( std::istream& in );

        std::string toJSON( bool pretty =false ) const;

        bool fromJSON( const std::string& json );

        bool empty() const {
            return _key.empty() && _defaultValue.empty() && _children.empty();
        }

        bool isSimple() const {
            return !_key.empty() && !_defaultValue.empty() && _children.empty();
        }

        std::string& key() { return _key; }
        const std::string& key() const { return _key; }

        const std::string& value() const { return _defaultValue; }
        std::string& value() { return _defaultValue; }

        const ConfigSet& children() const { return _children; }

        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;
        }

        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;
        }

        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;
            }
        }

        Config child( const std::string& key ) const;

        const Config* child_ptr( const std::string& key ) const;

        Config* mutable_child( const std::string& key );

        void merge( const Config& rhs );

        Config* find( const std::string& key, bool checkThis =true );
        const Config* find( const std::string& key, bool checkThis =true) const;

        template<typename T>
        void addIfSet( const std::string& key, const optional<T>& opt ) {
            if ( opt.isSet() ) {
                add( key, osgEarth::toString<T>( opt.value() ) );
            }
        }
        
        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 );
            }
        }

        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 );
            }
        }

        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 );
        }

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

        void add( const Config& conf ) {
            _children.push_back( conf );
            //_children.back().setReferrer( _referrer );
            _children.back().inheritReferrer( _referrer );
        }

        void add( const std::string& key, const Config& conf ) {
            Config temp = conf;
            temp.key() = key;
            add( temp );
        }

        void add( const ConfigSet& set ) {
            for( ConfigSet::const_iterator i = set.begin(); i != set.end(); i++ )
                add( *i );
        }

        template<typename T>
        void addObj( const std::string& key, const T& value ) {
            Config conf = value.getConfig();
            conf.key() = key;
            add( conf );
        }

        template<typename T>
        void updateIfSet( const std::string& key, const optional<T>& opt ) {
            if ( opt.isSet() ) {
                remove(key);
                add( key, osgEarth::toString<T>( opt.value() ) );
            }
        }
        
        template<typename T>
        void updateObjIfSet( const std::string& key, const osg::ref_ptr<T>& opt ) {
            if ( opt.valid() ) {
                remove(key);
                Config conf = opt->getConfig();
                conf.key() = key;
                add( conf );
            }
        }

        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 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 );
            }
        }

        template<typename T>
        void update( const std::string& key, const T& value ) {
            update( Config(key, Stringify() << value) );
        }

        void update( const Config& conf ) {
            remove(conf.key());
            add( conf );
        }

        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 set( const std::string& key, const T& value ) {
            update( key, value );
        }

        bool hasValue( const std::string& key ) const {
            return !value(key).empty();
        }

        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;
        }

        const std::string referrer( const std::string& key ) const {
            return child(key).referrer();
        }

        // populates a primitive value.
        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 );
        }

        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;
        }

        // for Configurable's
        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 a Referenced that takes a Config in the constructor.
        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;
        }

        template<typename T>
        bool getObjIfSet( const std::string& key, T& output ) const {
            if ( hasChild(key) ) {
                output = T( child(key) );
                return true;
            }
            return false;
        }

        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;
        }

        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;
        }

        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
        Config operator - ( const Config& rhs ) const;

    protected:
        std::string _key;
        std::string _defaultValue;
        ConfigSet   _children;   
        std::string _referrer;        

        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
    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 );
        _children.back().inheritReferrer( _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() ) { }

        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 { return _conf; }

        virtual Config getConfig( bool isolate ) const { return isolate ? newConfig() : _conf; }

        Config newConfig() const { Config c; c.setReferrer(referrer()); return c; }

        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 Config getConfig( bool isolate ) const {
            Config conf = ConfigOptions::getConfig( isolate );
            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

