/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#pragma once

#include <osgEarth/Config>
#include <osgEarth/URI>
#include <osgEarth/Units>
#include <vector>
#include <stack>

namespace osgEarth
{
    struct String : public std::string {
        String() = default;
        String(const std::string& init) : std::string(init) {}
    };

    /**
    * A container for a value that can either be a literal value or
    * an evaluated expression.
    */
    template<typename T>
    class Expression
    {
    public:
        //! Construct empty expression
        Expression() = default;

        //! Construct with an expression string that will be evaluated
        Expression(const std::string& input) :
            _expression(input)
        {
            preevaluate();
        }

        //! Construct with an expression string that will be evaluated
        Expression(const std::string& input, const std::string& referrer) :
            _expression(input),
            _referrer(referrer)
        {
            preevaluate();
        }

        //! Construct with a literal (pre-evaluated) value.
        Expression(const T& literal_value)
        {
            _expression = {};
            _literal = literal_value;
            preevaluate();
        }

        //! Deserialize
        Expression(const Config& conf)
        {
            // expression usually contained in the value:
            conf.get("expression", _expression);
            if (_expression.empty())
                _expression = conf.value();

            conf.get("literal", _literal);
            conf.get("default_units", _defaultUnits);
            _referrer = conf.referrer();

            preevaluate();
        }

        //! Set a literal value
        inline void setLiteral(const T& literal_value)
        {
            _expression = {};
            _literal = literal_value;
            preevaluate();
        }

        //! Set a literal value with a reffer
        inline void setLiteral(const T& literal_value, const std::string& referrer)
        {
            _expression = {};
            _literal = literal_value;
            _referrer = referrer;
            preevaluate();
        }

        //! Sets the units to use when calculating a value from either 
        //! a unit-less literal or a unit-less expression result.
        inline void setDefaultUnits(const UnitsType& value)
        {
            _defaultUnits = value;
            preevaluate();
        }

        //! The referrer string, if set
        inline const std::string& referrer() const {
            return _referrer;
        }

        //! The original expression string
        inline const std::string& expression() const {
            return _expression;
        }

        //! Serialize
        inline Config getConfig() const
        {
            return Config("expression", _expression)
                .set("literal", _literal)
                .set("default_units", _defaultUnits)
                .setReferrer(_referrer);
        }

        //! Evaluate the expression in the context of a feature.
        //! This method is capable of invoking any global evaluateExpression(...)
        //! function that takes a string as its first argument.
        template<typename... Args>
        inline T eval(Args... args) const
        {
            if (_literal.isSet())
                return _literal.value();

            if (_preevaluated.isSet())
                return _preevaluated.value();

            return construct(evaluateExpression(_expression, args...));
        }

        //! Return a copy of the literal value
        inline const T& literal() const
        {
            if (_literal.isSet())
                return _literal.value();
            else
                return _preevaluated.value();
        }

        //! Attempt to pre-evaluate a simple expression
        inline void preevaluate()
        {
            if (!_literal.isSet())
            {
                _preevaluated.unset();
                auto a = osgEarth::isValidNumber(_expression);
                if (a.first == true)
                    _preevaluated = a.second;
            }
        }   

        //! Anything here?
        inline operator bool() const
        {
            return !_expression.empty() || _literal.isSet() || _preevaluated.isSet();
        }

    private:
        std::string _expression;
        optional<T> _literal;
        optional<UnitsType> _defaultUnits = Units::METERS;
        optional<T> _preevaluated;
        std::string _referrer;

        inline T construct(const std::string& r) const
        {
            return T(r);
        }
    };


    using StringExpression = Expression<String>;
    using NumericExpression = Expression<double>;

} // namespace osgEarth

#if 0
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::NumericExpression);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::StringExpression);
#endif

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<float>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<double>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<int>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<unsigned>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<bool>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<osgEarth::String>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<osgEarth::Distance>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<osgEarth::Angle>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<osgEarth::Duration>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<osgEarth::Speed>);
OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Expression<osgEarth::URI>);

template<> inline bool osgEarth::Expression<bool>::construct(const std::string& r) const {
    return r == "true" || r == "1" || r == "yes" || r == "on";
}
template<> inline float osgEarth::Expression<float>::construct(const std::string& r) const {
    return std::atof(r.c_str());
}
template<> inline double osgEarth::Expression<double>::construct(const std::string& r) const {
    return std::atof(r.c_str());
}
template<> inline int osgEarth::Expression<int>::construct(const std::string& r) const {
    return std::atoi(r.c_str());
}
template<> inline unsigned osgEarth::Expression<unsigned>::construct(const std::string& r) const {
    return std::atoi(r.c_str());
}
template<> inline osgEarth::String osgEarth::Expression<osgEarth::String>::construct(const std::string& r) const {
    return osgEarth::String{ r };
}
template<> inline osgEarth::Distance osgEarth::Expression<osgEarth::Distance>::construct(const std::string& r) const {
    return _defaultUnits.isSet() ? osgEarth::Distance(r, _defaultUnits.value()) : osgEarth::Distance(r, osgEarth::Units::METERS);
}
template<> inline osgEarth::Angle osgEarth::Expression<osgEarth::Angle>::construct(const std::string& r) const {
    return _defaultUnits.isSet() ? osgEarth::Angle(r, _defaultUnits.value()) : osgEarth::Angle(r, osgEarth::Units::DEGREES);
}
template<> inline osgEarth::Duration osgEarth::Expression<osgEarth::Duration>::construct(const std::string& r) const {
    return _defaultUnits.isSet() ? osgEarth::Duration(r, _defaultUnits.value()) : osgEarth::Duration(r, osgEarth::Units::DEGREES);
}
template<> inline osgEarth::Speed osgEarth::Expression<osgEarth::Speed>::construct(const std::string& r) const {
    return _defaultUnits.isSet() ? osgEarth::Speed(r, _defaultUnits.value()) : osgEarth::Speed(r, osgEarth::Units::DEGREES);
}
template<> inline osgEarth::URI osgEarth::Expression<osgEarth::URI>::construct(const std::string& r) const {
    return osgEarth::URI(r, osgEarth::URIContext(_referrer));
}

template<> inline void osgEarth::Expression<bool>::preevaluate() {
    if (!_literal.isSet()) {
        _preevaluated.unset(); 
        if (_expression == "true" || _expression == "1" || _expression == "yes" || _expression == "on" || _expression == "TRUE" || _expression == "YES" || _expression == "ON")
            _preevaluated = true;
        else if (_expression == "false" || _expression == "0" || _expression == "no" || _expression == "off" || _expression == "FALSE" || _expression == "NO" || _expression == "OFF")
            _preevaluated = false;
    }
}
template<> inline void osgEarth::Expression<osgEarth::String>::preevaluate() {
    if (!_literal.isSet()) {
        _preevaluated.unset();
        if (_expression.front() == '\"' && _expression.back() == '\"')
        {
            const std::string allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
            size_t pos = _expression.find_first_not_of(allowed, 1);
            if (pos == std::string::npos || (pos == _expression.length() - 1))
            {
                _preevaluated = _expression.substr(1, _expression.size() - 2);
            }
        }
    }
}
template<> inline void osgEarth::Expression<osgEarth::Distance>::preevaluate() {
    if (!_literal.isSet()) {
        _preevaluated.unset();
        auto a = construct(_expression);
        if (a.fullyParsed()) _preevaluated = a;
    }
}
template<> inline void osgEarth::Expression<osgEarth::Angle>::preevaluate() {
    if (!_literal.isSet()) {
        _preevaluated.unset();
        auto a = construct(_expression);
        if (a.fullyParsed()) _preevaluated = a;
    }
}
template<> inline void osgEarth::Expression<osgEarth::Duration>::preevaluate() {
    if (!_literal.isSet()) {
        _preevaluated.unset();
        auto a = construct(_expression);
        if (a.fullyParsed()) _preevaluated = a;
    }
}
template<> inline void osgEarth::Expression<osgEarth::Speed>::preevaluate() {
    if (!_literal.isSet()) {
        _preevaluated.unset();
        auto a = construct(_expression);
        if (a.fullyParsed()) _preevaluated = a;
    }
}
template<> inline void osgEarth::Expression<osgEarth::URI>::preevaluate() {
    //nop
}
