/* -*-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/>
 */
#pragma once

#include <osgEarth/Common>
#include <osgEarth/optional>
#include <osgEarth/StringUtils>
#include <osg/Vec2f>
#include <osg/Vec3d>
#include <vector>
#include <istream>
#include <functional>

namespace osgDB
{
    class Options;
}

#define OE_CONFIG_MOVE_SEMATICS

namespace osgEarth
{
    using namespace osgEarth::Util;
    using ConfigSet = std::vector<class Config>;
    class URI;

    /**
     * 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
    {
    protected:
        std::string _key;
        std::string _defaultValue;
        std::string _referrer;
        std::string _externalRef;
        ConfigSet _children;
        bool _isLocation = false;
        bool _isNumber = false;

        void clear() {
            _key.clear();
            _defaultValue.clear();
            _referrer.clear();
            _externalRef.clear();
            _children.clear();
            _isLocation = false;
            _isNumber = false;
        }

        inline bool keys_equal(const std::string& lhs, const std::string& rhs) const
        {
            return ci_equals(lhs, rhs);
        }

    public:
        Config() = default;

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

        template<typename T>
        explicit Config(const std::string& key, const T& value) :
            _key(key)
        {
            setValue(value);
        }

        explicit Config(const std::string& key, const Config& value) :
            _key(key)
        {
            add(value);
        }

#ifdef OE_CONFIG_MOVE_SEMATICS
        explicit Config(const std::string& key, Config&& value) noexcept :
            _key(key)
        {
            add(value);
        }
        
        Config& operator=(Config&& rhs) noexcept {
            if (this != &rhs) {
                _key = std::move(rhs._key);
                _defaultValue = std::move(rhs._defaultValue);
                _referrer = std::move(rhs._referrer);
                _externalRef = std::move(rhs._externalRef);
                _children = std::move(rhs._children);
                _isLocation = rhs._isLocation;
                _isNumber = rhs._isNumber;
                rhs.clear();
            }
            return *this;        
        }

        Config(Config&& rhs) noexcept {
            *this = rhs;
        }

#endif

        // Copy functions
        Config(const Config& rhs) = default;
        Config& operator=(const Config& rhs) = default;

        /**
         * 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);

        //! Populate this object from a URI
        bool fromURI(const URI&);

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

        /** Main setValue method - see specializations inline below */
        template<typename T>
        void setValue(const T& value) {
            _defaultValue = Stringify() << value;
        }

        /** 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 (auto& c : _children)
                if (keys_equal(c.key(), key))
                    r.emplace_back(c);
            return r;
        }

        /** Whether this object has a child with a given key */
        bool hasChild(const std::string& key) const {
            for(auto& c : _children)
                if (keys_equal(c.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 (keys_equal(i->key(), key))
                    i = _children.erase(i);
                else
                    ++i;
            }
        }

        /** First child with the given key */
        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;

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

        //! Set a config as a child
        Config& add(const Config& conf) {
            _children.push_back(conf);
            _children.back().setReferrer(_referrer);
            return _children.back();
        }

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

        //! Add a config named key
        Config& add(const std::string& key) {
            return add(Config(key));
        }

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

#ifdef OE_CONFIG_MOVE_SEMATICS
        //! Move a config as a child
        Config& add(Config&& conf) noexcept {
            _children.emplace_back(conf);
            _children.back().setReferrer(_referrer);
            conf.clear();
            return _children.back();
        }
        Config& add(const std::string& key, Config&& conf) noexcept {
            Config& result = add(conf);
            result.key() = key;
            conf.clear();
            return result;
        }
        Config& set(Config&& conf) noexcept {
            remove(conf.key());
            return add(conf);
        }
#endif

        /** Adds or replaces an optional value as a child, but only if it is set */
        template<typename T>
        Config& set(const std::string& key, const optional<T>& opt) {
            remove(key);
            if (opt.isSet()) {
                set(Config(key, opt.get()));
            }
            return *this;
        }

        //! Sets a double floating point as a possible percentage
        Config& set(const std::string& key, const optional<double>& opt, const optional<bool>& is_percentage) {
            remove(key);
            if (opt.isSet()) {
                Config conf(key, opt.get());
                auto str = conf.value(key);
                if (is_percentage.isSetTo(true)) {
                    conf.set(key, str + '%');
                }
                set(std::move(conf));
            }
            return *this;
        }

        //! Sets a single floating point as a possible percentage
        Config& set(const std::string& key, const optional<float>& opt, const optional<bool>& is_percentage) {
            remove(key);
            if (opt.isSet()) {
                Config conf(key, opt.get());
                auto str = conf.value(key);
                if (is_percentage.isSetTo(true)) {
                    conf.set(key, str + '%');
                }
                set(std::move(conf));
            }
            return *this;
        }

        template<typename T>
        Config& set(const std::string& key, const optional<T>& opt, const optional<double>& opt_as_percentage) {
            if (opt.isSet())
                set(key, opt);
            else if (opt_as_percentage.isSet())
                set(key, std::to_string(opt_as_percentage.value() * 100.0) + '%');
            return *this;
        }

        template<typename T>
        Config& set(const std::string& key, const osg::ref_ptr<T>& obj) {
            remove(key);
            if (obj.valid()) {
                Config conf = obj->getConfig();
                conf.key() = key;
                set(std::move(conf));
            }
            return *this;
        }

        // If target is set to targetValue, set key to val.
        template<typename X, typename Y>
        Config& set(const std::string& key, const std::string& val, const optional<X>& target, const Y& targetValue) {
            if (target.isSetTo(targetValue)) {
                remove(key);
                set(key, val);
            }
            return *this;
        }

        /** Adds or replaces a config as a child. */
        Config& set(const Config& conf) {
            remove(conf.key());
            return add(conf);
        }

        //! Adds or replaces a config as a child if it's not empty.
        template<typename CALLABLE>
        Config& set_with_function(const std::string& key, CALLABLE&& f) {
            Config conf(key);
            f(conf);
            if (!conf.empty()) set(std::move(conf));
            return *this;
        }

        /** Sets a key value pair child */
        template<typename T>
        Config& set(const std::string& key, const T& value) {
            set(Config(key, value));
            return *this;
        }

        /** 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() && keys_equal(_key, key))
                r = _defaultValue;
            return r;
        }

        /** Default value transformed to another type */
        template<typename T>
        T valueAs(const T& fallback) const {
            return osgEarth::Util::as<T>(_defaultValue, fallback);
        }

        /** 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::Util::as<T>(r, fallback);
        }

        /** Populates the output value iff the Config exists. */
        template<typename T>
        bool get(const std::string& key, optional<T>& output) const {
            std::string r;
            if (hasChild(key))
                r = child(key).value();
            if (!r.empty()) {
                output = osgEarth::Util::as<T>(r, output.defaultValue());
                return true;
            }
            else
                return false;
        }

        /** Populates the output referenced value iff the Config exists. */
        template<typename T>
        bool get(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 enumerable pair iff the Config exists. */
        template<typename X, typename Y>
        bool get(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 get(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 get(const std::string& key, T& output) const {
            if (hasValue(key)) {
                output = value<T>(key, output);
                return true;
            }
            return false;
        }

        //! Object OR percentage (if ends in %)
        template<typename T>
        bool get(const std::string& key, optional<T>& opt, optional<double>& opt_as_percentage) const {
            opt.clear();
            opt_as_percentage.clear();
            if (hasChild(key)) {
                auto r = child(key).value();
                if (!r.empty() && r.back() == '%') {
                    r = r.substr(0, r.size() - 1);
                    opt_as_percentage = as<double>(r, 0.0) * 0.01;
                    return true;
                }
                else {
                    return get(key, opt);
                }
            }
            return false;
        }

        //! Float as possible percentage
        bool get(const std::string& key, optional<float>& output, optional<bool>& is_percentage) const {
            is_percentage.setDefault(false);
            is_percentage.clear();
            if (hasChild(key)) {
                auto r = child(key).value();
                if (!r.empty()) {
                    output = osgEarth::Util::as<float>(r, output.defaultValue());
                    if (trim(r).back() == '%') {
                        is_percentage = true;
                    }
                    return true;
                }
            }
            return false;
        }

        //! Double as possible percentage
        bool get(const std::string& key, optional<double>& output, optional<bool>& is_percentage) const {
            is_percentage.setDefault(false);
            is_percentage.clear();
            if (hasChild(key)) {
                auto r = child(key).value();
                if (!r.empty()) {
                    output = osgEarth::Util::as<double>(r, output.defaultValue());
                    if (trim(r).back() == '%') {
                        is_percentage = true;
                    }
                    return true;
                }
            }
            return false;
        }

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

        //! Whether the encoded value is actual a number
        bool isNumber() const { return _isNumber; }
    };

    // SPECIALIZATION - Config

    template<> inline
    Config& Config::set<Config>(const std::string& key, const Config& conf) {
        remove(key);
        Config& result = add(conf);
        result.key() = key;
        return *this;
    }

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

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

    // SPECIALIZATIONS - setValue

    template<> inline void Config::setValue<std::string>(const std::string& value) {
        _defaultValue = value;
        _isNumber = false;
    }
    template<> inline void Config::setValue<bool>(const bool& value) {
        _defaultValue = value==true? "true" : "false";
        _isNumber = false;
    }
    template<> inline void Config::setValue<short>(const short& value) {
        _defaultValue = std::to_string(value);
        _isNumber = true;
    }
    template<> inline void Config::setValue<unsigned short>(const unsigned short& value) {
        _defaultValue = std::to_string(value);
        _isNumber = true;
    }
    template<> inline void Config::setValue<int>(const int& value) {
        _defaultValue = std::to_string(value);
        _isNumber = true;
    }
    template<> inline void Config::setValue<unsigned int>(const unsigned int& value) {
        _defaultValue = std::to_string(value);
        _isNumber = true;
    }
    template<> inline void Config::setValue<long>(const long& value) {
        _defaultValue = std::to_string(value);
        _isNumber = true;
    }
    template<> inline void Config::setValue<unsigned long>(const unsigned long& value) {
        _defaultValue = std::to_string(value);
        _isNumber = true;
    }
    template<> inline void Config::setValue<float>(const float& value) {
        _defaultValue = Stringify() << std::setprecision(8) << value;
        _isNumber = true;
    }
    template<> inline void Config::setValue<double>(const double& value) {
        _defaultValue = Stringify() << std::setprecision(16) << value;
        _isNumber = true;
    }

    // Specializations (OSG types)

    template<> inline Config& Config::set<osg::Vec2f>(const std::string& key, const optional<osg::Vec2f>& opt) {
        remove(key);
        if (opt.isSet()) {
            set(key, Stringify() << std::setprecision(8) << opt->x() << "," << opt->y());
        }
        return *this;
    }
    template<> inline bool Config::get<osg::Vec2f>(const std::string& key, optional<osg::Vec2f>& output) const {
        if (hasChild(key)) {
            output.mutable_value().x() = as<float>(getToken(value(key), 0, ','), 0.0f);
            output.mutable_value().y() = as<float>(getToken(value(key), 1, ','), 0.0f);
            return true;
        }
        else
            return false;
    }

    template<> inline Config& Config::set<osg::Vec2d>(const std::string& key, const optional<osg::Vec2d>& opt) {
        remove(key);
        if (opt.isSet()) {
            set(key, Stringify() << std::setprecision(16) << opt->x() << "," << opt->y());
        }
        return *this;
    }
    template<> inline bool Config::get<osg::Vec2d>(const std::string& key, optional<osg::Vec2d>& output) const {
        if (hasChild(key)) {
            output.mutable_value().x() = as<double>(getToken(value(key), 0, ','), 0.0);
            output.mutable_value().y() = as<double>(getToken(value(key), 1, ','), 0.0);
            return true;
        }
        else
            return false;
    }

    template<> inline Config& Config::set<osg::Vec3f>(const std::string& key, const optional<osg::Vec3f>& opt) {
        remove(key);
        if (opt.isSet()) {
            set(key, Stringify() << std::setprecision(8) << opt->x() << "," << opt->y() << "," << opt->z());
        }
        return *this;
    }
    template<> inline bool Config::get<osg::Vec3f>(const std::string& key, optional<osg::Vec3f>& output) const {
        if (hasChild(key)) {
            output.mutable_value().x() = as<float>(getToken(value(key), 0, ','), 0.0f);
            output.mutable_value().y() = as<float>(getToken(value(key), 1, ','), 0.0f);
            output.mutable_value().z() = as<float>(getToken(value(key), 2, ','), 0.0f);
            return true;
        }
        else
            return false;
    }

    template<> inline Config& Config::set<osg::Vec3d>(const std::string& key, const optional<osg::Vec3d>& opt) {
        remove(key);
        if (opt.isSet()) {
            set(key, Stringify() << std::setprecision(16) << opt->x() << "," << opt->y() << "," << opt->z());
        }
        return *this;
    }
    template<> inline bool Config::get<osg::Vec3d>(const std::string& key, optional<osg::Vec3d>& output) const {
        if (hasChild(key)) {
            output.mutable_value().x() = as<double>(getToken(value(key), 0, ','), 0.0);
            output.mutable_value().y() = as<double>(getToken(value(key), 1, ','), 0.0);
            output.mutable_value().z() = as<double>(getToken(value(key), 2, ','), 0.0);
            return true;
        }
        else
            return false;
    }

    // Specializations (VECTORS)

    template<> inline Config& Config::set(const std::string& key, const std::vector<std::string>& input) {
        remove(key);
        if (!input.empty()) {
            std::ostringstream buf;
            for(size_t i=0; i<input.size(); ++i) {
                if (i > 0) buf << ',';
                bool quote = input[i].find(',') != std::string::npos;
                if (quote) buf << "\"";
                buf << input[i];
                if (quote) buf << "\"";
            }
            set(key, buf.str());
        }
        return *this;
    }
        
    template<> inline bool Config::get(const std::string& key, std::vector<std::string>& output) const {
        if (hasChild(key)) {
            output.clear();

            output = StringTokenizer()
                .delim(",")
                .standardQuotes()
                .tokenize(value(key));
                
            return true;
        }
        else return false;
    }


    // Use this macro to "activate" any object with a getConfig/ctor(const Config&) pair
    // and make it usable with Config::set/get/add.
    // NOTE: You must only use this macro in the global namespace!

#define OSGEARTH_SPECIALIZE_CONFIG(TYPE) \
    namespace osgEarth { \
        template<> inline \
        Config& Config::set<TYPE>(const std::string& key, const TYPE& obj) { \
            set( key, obj.getConfig() ); return *this; \
        } \
        template<> inline \
        Config& Config::set<TYPE>(const std::string& key, const optional<TYPE>& opt) { \
            if ( opt.isSet() ) \
                set(key, opt.get()); return *this; \
        } \
        template<> inline \
        bool Config::get<TYPE>(const std::string& key, TYPE& opt) const { \
            if ( hasChild(key) ) { \
                opt = TYPE(child(key)); \
                return true; \
            } \
            else return false; \
        } \
        template<> inline \
        bool Config::get<TYPE>(const std::string& key, optional<TYPE>& opt) const { \
            if ( hasChild(key) ) { \
                opt = TYPE(child(key)); \
                return true; \
            } \
            else return false; \
        } \
        template<> inline Config& Config::add<TYPE>(const std::string& key, const TYPE& value) { \
            Config conf = value.getConfig(); \
            conf.key() = key; \
            return add(std::move(conf)); \
        } \
    } // namespace osgEarth

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

    /**
     * Base class for all serializable options classes.
     */
    class ConfigOptions
    {
    public:
        ConfigOptions() = default;

        //! Copy constructor - it must call getConfig() so it can repopulate the internal _conf member.
        ConfigOptions(const ConfigOptions& rhs) : _conf(rhs.getConfig()) { }

        //! Copy constructor - simple Config copy.
        ConfigOptions(const Config& conf) : _conf(conf) { }

        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 {
            Config conf = _conf;
            conf.setReferrer(referrer());
            return conf;
        }

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

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

        Config _conf;
    };

    /**
     * Base configoptions class for driver options.
     * @deprecated - will be removed.
     */
    class DriverConfigOptions : public ConfigOptions
    {
    public:
        DriverConfigOptions(const ConfigOptions& rhs = ConfigOptions())
            : ConfigOptions(rhs)
        {
            fromConfig(_conf);
        }

        /** 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();
            if (!_driver.empty())
                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;
    };
}

//! Macro to use when defining a COnfigOptions class
#define META_ConfigOptions(LIBRARY, MYCLASS, SUPERCLASS) \
    protected: \
        virtual void mergeConfig(const Config& conf) { \
            SUPERCLASS ::mergeConfig(conf); \
            fromConfig(conf); \
        } \
    public: \
        MYCLASS () : SUPERCLASS() { fromConfig(_conf); } \
        MYCLASS (const ConfigOptions& opt) : SUPERCLASS(opt) { fromConfig(_conf); }

//! optional property macro
#define OE_OPTION_0_ARGS()
#define OE_OPTION_1_ARGS()
#define OE_OPTION_2_ARGS(TYPE, NAME) \
    private: \
      osgEarth::optional< TYPE > _ ## NAME ; \
      mutable std::vector<std::function<void(const TYPE&)>> _ ## NAME ## _listeners; \
    public: \
      osgEarth::optional< TYPE >& NAME () { return _ ## NAME; } \
      const osgEarth::optional< TYPE >& NAME () const { return _ ## NAME; } \
      void NAME ## Changed (std::function<void(const TYPE&)> f) const { \
        _ ## NAME ## _listeners.push_back(f); } \
      void set_ ## NAME (const TYPE& value) { \
        _ ## NAME = value; \
        for(auto& notify : _ ## NAME ## _listeners) notify(value); }

#define OE_OPTION_3_ARGS(TYPE, NAME, DEFAULT_VALUE) \
    private: \
      osgEarth::optional< TYPE > _ ## NAME {DEFAULT_VALUE}; \
      mutable std::vector<std::function<void(const TYPE&)>> _ ## NAME ## _listeners; \
    public: \
      osgEarth::optional< TYPE >& NAME () { return _ ## NAME; } \
      const osgEarth::optional< TYPE >& NAME () const { return _ ## NAME; } \
      void NAME ## Changed (std::function<void(const TYPE&)> f) const { \
        _ ## NAME ## _listeners.push_back(f); } \
      void set_ ## NAME (const TYPE& value) { \
        _ ## NAME = value; \
        for(auto& notify : _ ## NAME ## _listeners) notify(value); }

// https://stackoverflow.com/a/28074198
#define OE_OPTION_FUNC_CHOOSER(_f1, _f2, _f3, _f4, ...) _f4
#define OE_OPTION_FUNC_RECOMPOSER(ARGS) OE_OPTION_FUNC_CHOOSER ARGS
#define OE_OPTION_CHOOSE_FROM_ARG_COUNT(...) OE_OPTION_FUNC_RECOMPOSER((__VA_ARGS__, OE_OPTION_3_ARGS, OE_OPTION_2_ARGS, OE_OPTION_1_ARGS, ))
#define OE_OPTION_NO_ARG_EXPANDER() ,,OE_OPTION_0_ARGS
#define OE_OPTION_MACRO_CHOOSER(...) OE_OPTION_CHOOSE_FROM_ARG_COUNT(OE_OPTION_NO_ARG_EXPANDER __VA_ARGS__ ())
#define OE_OPTION(...) OE_OPTION_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

//! ref_ptr property macro
#define OE_OPTION_REFPTR(TYPE, NAME) \
    private: \
    osg::ref_ptr< TYPE > _ ## NAME ; \
    public: \
    osg::ref_ptr< TYPE >& NAME () { return _ ## NAME; } \
    const osg::ref_ptr< TYPE >& NAME () const { return _ ## NAME; }

//! ref_ptr property macro
#define OE_OPTION_SHAREDPTR(TYPE, NAME) \
    private: \
    std::shared_ptr< TYPE > _ ## NAME ; \
    public: \
    std::shared_ptr< TYPE >& NAME () { return _ ## NAME; } \
    const std::shared_ptr< TYPE >& NAME () const { return _ ## NAME; }

//! vector property macro
#define OE_OPTION_VECTOR(TYPE, NAME) \
    private: \
    std::vector< TYPE > _ ## NAME ; \
    public: \
    std::vector< TYPE >& NAME () { return _ ## NAME; } \
    const std::vector< TYPE >& NAME () const { return _ ## NAME; }

#define OE_OPTION_IMPL(CLASS, TYPE, FUNC, OPTION) \
    void CLASS ::set ## FUNC (const TYPE & value) { options(). set_ ## OPTION (value); }\
    const TYPE & CLASS ::get ## FUNC () const { return options(). OPTION ().get(); }

//! property macro
#define OE_PROPERTY(TYPE, NAME, DEFVAL) \
    private: \
    TYPE _ ## NAME = DEFVAL; \
    public: \
    TYPE & NAME () { return _ ## NAME; } \
    TYPE & NAME ## _mutable() { return _ ## NAME; } \
    const TYPE & NAME () const { return _ ## NAME; }

//! const property macro
#define OE_PROPERTY_CONST(TYPE, NAME, DEFVAL) \
    private: \
    TYPE _ ## NAME = DEFVAL; \
    protected: \
    TYPE & NAME () { return _ ## NAME; } \
    TYPE & NAME ## _mutable() { return _ ## NAME; } \
    public: \
    const TYPE & NAME () const { return _ ## NAME; }

#define OE_OPTION_LESS(L,R) \
    if (L().isSet() && !R().isSet()) return true; \
    if (R().isSet() && !L().isSet()) return false; \
    if (L().isSet() && R().isSet()) { \
        if (L().get() < R().get()) return true; \
        if (L().get() > R().get()) return false; }
