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

#include <osgEarth/Common>
#include <osg/Vec3>
#include <osg/Vec3d>
#include <osg/Vec4>
#include <osg/Vec4ub>
#include <string>
#include <cstdint>
#include <algorithm>
#include <vector>
#include <sstream>
#include <locale>
#include <iomanip>
#include <map>
#include <unordered_map>
#include <set>
#include <cctype>

namespace osgEarth {
    namespace Util
    {
        extern OSGEARTH_EXPORT const std::string EMPTY_STRING;

        using StringVector = std::vector<std::string>;
        using StringSet = std::set<std::string>;
        using StringTable = std::unordered_map<std::string, std::string>;

        //! Replaces all the instances of "pattern" with "replacement" in "in_out"
        extern OSGEARTH_EXPORT std::string& replaceIn(
            std::string& in_out,
            const std::string& pattern,
            const std::string& replacement);

        //! Replaces all the instances of "pattern" with "replacement" in "in_out" (case-insensitive)
        extern OSGEARTH_EXPORT std::string& ciReplaceIn(
            std::string& in_out,
            const std::string& pattern,
            const std::string& replacement);

        //! Whether string contains a single valid number and nothing else.
        extern OSGEARTH_EXPORT std::pair<bool, double> isValidNumber(const std::string& str);

        //! Trims whitespace from the ends of a string.
        extern OSGEARTH_EXPORT std::string trim(const std::string& in);

        //! Trims whitespace from the ends of a string; in-place modification on the string to reduce string copies.
        extern OSGEARTH_EXPORT void trim2(std::string& str);

        //! Removes leading and trailing whitespace, and replaces all other
        //! whitespace with single spaces
        extern OSGEARTH_EXPORT std::string trimAndCompress(const std::string& in);

        //! Returns a lower case character, taking a fast path for ASCII values.
        inline char toLower(unsigned char input) {
            return (input < 0x80) ? input | ((input >= 'A' && input <= 'Z') ? 0x20 : 0x00) : std::tolower(input);
        }

        //! Returns a lower-case version of the input string.
        inline std::string toLower(const std::string& input) {
            std::string output(input);
            std::transform(output.begin(), output.end(), output.begin(), [&](unsigned char c) {
                return toLower(c); });
            return output;
        }

        //! Lower-cases a string in sutu.
        inline std::string& toLowerInPlace(std::string& input) {
            std::transform(input.begin(), input.end(), input.begin(), [&](unsigned char c) {
                return toLower(c); });
            return input;
        }

        //! True is "ref" starts with "pattern"
        inline bool startsWith(const std::string& ref, const std::string& pattern) {
            if (pattern.size() > ref.size()) return false;
            return ref.compare(0, pattern.size(), pattern) == 0;
        }

        //! True is "ref" starts with "pattern" (case-insensitive)
        inline bool ciStartsWith(const std::string& ref, const std::string& pattern) {
            if (pattern.size() > ref.size()) return false;
            for (size_t i = 0; i < pattern.size(); ++i)
                if (toLower(ref[i]) != toLower(pattern[i]))
                    return false;
            return true;
        }

        //! True is "ref" ends with "pattern"
        inline bool endsWith(const std::string& ref, const std::string& pattern) {
            if (pattern.size() > ref.size()) return false;
            return ref.compare(ref.size() - pattern.size(), pattern.size(), pattern) == 0;
        }

        //! True is "ref" ends with "pattern" (case-insensitive)
        inline bool ciEndsWith(const std::string& ref, const std::string& pattern) {
            if (pattern.size() > ref.size()) return false;
            size_t offset = ref.size() - pattern.size();
            for (size_t i = 0; i < pattern.size(); ++i)
                if (toLower(ref[offset + i]) != toLower(pattern[i]))
                    return false;
            return true;
        }

        //! True is "ref" contains "pattern"
        inline bool contains(const std::string& ref, const std::string& pattern) {
            return ref.find(pattern) != std::string::npos;
        }

        //! True is "ref" contains "pattern" (case-insensitive)
        inline bool ciContains(const std::string& ref, const std::string& pattern) {
            size_t patternLen = pattern.size();
            if (patternLen == 0) return true;
            size_t refLen = ref.size();
            if (patternLen > refLen) return false;
            for (size_t i = 0; i <= refLen - patternLen; ++i) {
                size_t j = 0;
                for (; j < patternLen; ++j) {
                    if (toLower(ref[i + j]) != toLower(pattern[j]))
                        break;
                }
                if (j == patternLen)
                    return true;
            }
            return false;
        }

        //! Case-insensitive string comparison.
        inline bool ci_equals(const std::string& lhs, const std::string& rhs) {
            if (lhs.size() != rhs.size()) return false;
            for (size_t i = 0; i < lhs.size(); ++i)
                if (toLower(lhs[i]) != toLower(rhs[i]))
                    return false;
            return true;
        }

        // backwards compatible
        inline bool ciEquals(const std::string& lhs, const std::string& rhs) {
            return ci_equals(lhs, rhs);
        }

        //! Parse a string into a double, return NAN if it fails.
        extern OSGEARTH_EXPORT double parseDouble(const std::string& str);

        //! Parse a string into a double, return the value and the index of the end of the parsed number.
        //! return NAN is it failes.
        extern OSGEARTH_EXPORT std::pair<double, int> parseDoubleAndIndex(const std::string& str);

        //! Joins a collection of strings into a single string, separated by the given delimiter.
        extern OSGEARTH_EXPORT std::string joinStrings(const StringVector& input, char delim);

        /** Parses a color string in the form "255 255 255 255" (r g b a [0..255]) into an OSG color. */
        extern OSGEARTH_EXPORT osg::Vec4ub stringToColor(const std::string& str, osg::Vec4ub default_value);

        /** Creates a string in the form "255 255 255 255" (r g b a [0..255]) from a color */
        extern OSGEARTH_EXPORT std::string colorToString(const osg::Vec4ub& c);

        /** Converts a string to a vec3f */
        extern OSGEARTH_EXPORT osg::Vec3f stringToVec3f(const std::string& str, const osg::Vec3f& default_value);

        /** Converts a vec3f to a string */
        extern OSGEARTH_EXPORT std::string vec3fToString(const osg::Vec3f& v);

        /** Parses an HTML color ("#rrggbb" or "#rrggbbaa") into an OSG color. */
        extern OSGEARTH_EXPORT osg::Vec4f htmlColorToVec4f(const std::string& html);

        /** Makes an HTML color ("#rrggbb" or "#rrggbbaa") from an OSG color. */
        extern OSGEARTH_EXPORT std::string vec4fToHtmlColor(const osg::Vec4f& c);

        /** Makes a valid filename out of a string */
        extern OSGEARTH_EXPORT std::string toLegalFileName(const std::string& input, bool allowSubdir = false, const char* replacementChar = NULL);

        /** Generates a hashed integer for a string */
        extern OSGEARTH_EXPORT std::uint64_t hashString(const std::string& input);

        /** Same as hashString but returns a string value. */
        extern OSGEARTH_EXPORT std::string hashToString(const std::string& input);

        //! Gets the total number of seconds formatted as H:M:S
        extern OSGEARTH_EXPORT std::string prettyPrintTime(double seconds);

        //! Gets a pretty printed version of the given size in MB.
        extern OSGEARTH_EXPORT std::string prettyPrintSize(double mb);

        //! Extract the "i-th" token from a delimited string
        extern OSGEARTH_EXPORT std::string getToken(const std::string& input, unsigned i, char delim);

        //! Remove outer quotes from a string
        extern OSGEARTH_EXPORT std::string unquote(const std::string& input);


        //------------------------------------------------------------------------
        // conversion templates

        // converts a string to primitive using serialization
        template<typename T> inline T as(const std::string& str, const T& default_value)
        {
            T temp = default_value;
            std::istringstream strin(str);
            if (!strin.eof()) strin >> temp;
            return temp;
        }

        template<> inline long long as<long long>(const std::string& str, const long long& defaultValue) {
            char* end = nullptr;
            auto value = std::strtoll(str.c_str(), &end, 0);
            return (str.c_str() == end) ? defaultValue : value;
        }

        template<> inline int as<int>(const std::string& str, const int& defaultValue) {
            return static_cast<int>(as<long long>(str, static_cast<long long>(defaultValue)));
        }
        template<> inline unsigned as<unsigned>(const std::string& str, const unsigned& defaultValue) {
            return static_cast<unsigned>(as<long long>(str, static_cast<long long>(defaultValue)));
        }
        template<> inline short as<short>(const std::string& str, const short& defaultValue) {
            return static_cast<short>(as<long long>(str, static_cast<long long>(defaultValue)));
        }
        template<> inline unsigned short as<unsigned short>(const std::string& str, const unsigned short& defaultValue) {
            return static_cast<unsigned short>(as<long long>(str, static_cast<long long>(defaultValue)));
        }
        template<> inline long as<long>(const std::string& str, const long& defaultValue) {
            return static_cast<long>(as<long long>(str, static_cast<long long>(defaultValue)));
        }
        template<> inline unsigned long as<unsigned long>(const std::string& str, const unsigned long& defaultValue) {
            return static_cast<unsigned long>(as<long long>(str, static_cast<long long>(defaultValue)));
        }
        template<> inline unsigned long long as<unsigned long long>(const std::string& str, const unsigned long long& defaultValue) {
            return static_cast<unsigned long long>(as<long long>(str, static_cast<unsigned long long>(defaultValue)));
        }

        // template specialization for a bool
        template<> inline bool as<bool>(const std::string& str, const bool& default_value)
        {
            return ci_equals(str, "true") || ci_equals(str, "yes") || ci_equals(str, "on") ? true :
                ci_equals(str, "false") || ci_equals(str, "no") || ci_equals(str, "off") ? false :
                default_value;
        }

        template<> inline osg::Vec3f as<osg::Vec3f>(const std::string& str, const osg::Vec3f& default_value)
        {
            return stringToVec3f(str, default_value);
        }

        // template specialization for string
        template<> inline std::string as<std::string>(const std::string& str, const std::string& default_value)
        {
            return str;
        }

        inline std::string toString(bool value)
        {
            return value ? "true" : "false";
        }

        inline std::string toString(const osg::Vec3f& value)
        {
            return vec3fToString(value);
        }

        /**
         * Assembles and returns an inline string using a stream-like << operator.
         * Example:
         *     std::string str = Stringify() << "Hello, world " << variable;
         */
        struct Stringify
        {
            operator std::string() const
            {
                std::string result;
                result = buf.str();
                return result;
            }

            template<typename T>
            Stringify& operator << (const T& val) { buf << val; return (*this); }

            Stringify& operator << (const Stringify& val) { buf << (std::string)val; return (*this); }

        protected:
            std::stringstream buf;
        };

        template<> inline
            Stringify& Stringify::operator << <bool>(const bool& val) { buf << (val ? "true" : "false"); return (*this); }

        template<> inline
            Stringify& Stringify::operator << <osg::Vec3f>(const osg::Vec3f& val) {
            buf << val.x() << " " << val.y() << " " << val.z(); return (*this);
        }

        template<> inline
            Stringify& Stringify::operator << <osg::Vec3d>(const osg::Vec3d& val) {
            buf << val.x() << " " << val.y() << " " << val.z(); return (*this);
        }

        template<> inline
            Stringify& Stringify::operator << <osg::Vec4f>(const osg::Vec4f& val) {
            buf << val.r() << " " << val.g() << " " << val.b() << " " << val.a(); return (*this);
        }

        /**
         * Splits a string up into a vector of strings based on a set of
         * delimiters, quotes, and rules.
         */
        class OSGEARTH_EXPORT StringTokenizer
        {
        public:
            StringTokenizer() = default;

            //! Tokenize input into output.
            //! @return true upon success, false if there was a dangling quote.
            std::vector<std::string> operator()(const std::string& input, bool* error = nullptr) const;

            //! Backwards compatibility
            OE_DEPRECATED("Use std::vector<std::string> tokenize instead")
            void tokenize(const std::string& input, std::vector<std::string>& output) const {
                output = operator()(input, nullptr);
            }

            //! Alias
            std::vector<std::string> tokenize(const std::string& input) const {
                return operator()(input, nullptr);
            }

            //! Whether to keep emptry tokens in the output.
            StringTokenizer& keepEmpties(bool value) {
                _keepEmptyTokens = value;
                return *this;
            }

            //! Whether to trim leading and training whitespace from tokens.
            StringTokenizer& trimTokens(bool value) {
                _trimTokens = value;
                return *this;
            }

            //! Adds a delimiter and whether to keep it in the output as a separate token.
            StringTokenizer& delim(const std::string& value, bool keepAsToken = false) {
                _delims[value] = keepAsToken;
                return *this;
            }

            //! Adds a quote character and whether to keep it in the output as a separate token.
            //! Use this is the quote opener is the same as the closer (like "'")
            StringTokenizer& quote(char opener_and_closer, bool keepInToken = true) {
                _quotes[opener_and_closer] = std::make_pair(opener_and_closer, keepInToken);
                return *this;
            }

            //! Adds a quote character pair and whether to keep them in the output as separate tokens.
            //! Use this if the quote chars don't match (like '{' and '}')
            StringTokenizer& quotePair(char opener, char closer, bool keepInToken = true) {
                _quotes[opener] = std::make_pair(closer, keepInToken);
                return *this;
            }

            //! Adds standard whitespace characters as delimiters.
            StringTokenizer& whitespaceDelims() {
                return delim(" ").delim("\t").delim("\n").delim("\r");
            }

            //! Adds the standard quote characters: single and double quotes, kept in the token.
            StringTokenizer& standardQuotes() {
                return quote('\'').quote('"');
            }

            //! Ignores a situation where a quote is opened and never closed.
            StringTokenizer& ignoreDanglingQuotes() {
                _ignoreDanglingQuotes = true;
                return *this;
            }

        private:
            using DelimiterMap = std::map<std::string, bool>; // string, keep?
            using QuoteMap = std::map<char, std::pair<char, bool>>; // open, close, keep?

            DelimiterMap _delims;
            QuoteMap _quotes;
            bool _keepEmptyTokens = true;
            bool _trimTokens = true;
            bool _ignoreDanglingQuotes = false;
        };
    }
}


namespace osgEarth
{
    namespace Strings = osgEarth::Util;
}
