/* -*-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/>
 */
#ifndef OSGEARTH_STRING_UTILS_H
#define OSGEARTH_STRING_UTILS_H 1

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

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

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

    /**
     * True is "ref" starts with "pattern"
     */
    extern OSGEARTH_EXPORT bool startsWith(
        const std::string& ref,
        const std::string& pattern,
        bool               caseSensitive =true,
        const std::locale& locale        =std::locale() );

    /**
     * True is "ref" ends with "pattern"
     */
    extern OSGEARTH_EXPORT bool endsWith(
        const std::string& ref,
        const std::string& pattern,
        bool               caseSensitive =true,
        const std::locale& locale        =std::locale() );

    /**
     * Case-insensitive compare
     */
    extern OSGEARTH_EXPORT bool ciEquals(
        const std::string& lhs,
        const std::string& rhs,
        const std::locale& local = std::locale() );

    /**
     * Case-insensitive STL comparator
     */
    struct OSGEARTH_EXPORT CIStringComp {
        bool operator()(const std::string& lhs, const std::string& rhs) const;
    };


    extern OSGEARTH_EXPORT std::string joinStrings( const StringVector& input, char delim );

    /** Returns a lower-case version of the input string. */
    extern OSGEARTH_EXPORT std::string toLower( const std::string& input );

    /** 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 (poor man's MD5) */
    extern OSGEARTH_EXPORT unsigned 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, const std::string& delims=",");


    //------------------------------------------------------------------------
    // 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 specialization for integers (to handle hex)
#define AS_INT_DEC_OR_HEX(TYPE) \
    template<> inline TYPE \
    as< TYPE >(const std::string& str, const TYPE & dv) { \
        TYPE temp = dv; \
        std::istringstream strin( trim(str) ); \
        if ( !strin.eof() ) { \
            if ( str.length() >= 2 && str[0] == '0' && str[1] == 'x' ) { \
                strin.seekg( 2 ); \
                strin >> std::hex >> temp; \
            } \
            else { \
                strin >> temp; \
            } \
        } \
        return temp; \
    }

    AS_INT_DEC_OR_HEX(int)
    AS_INT_DEC_OR_HEX(unsigned)
    AS_INT_DEC_OR_HEX(short)
    AS_INT_DEC_OR_HEX(unsigned short)
    AS_INT_DEC_OR_HEX(long)
    AS_INT_DEC_OR_HEX(unsigned long)

    // template specialization for a bool
    template<> inline bool
    as<bool>( const std::string& str, const bool& default_value )
    {
        std::string temp = toLower(str);
        return
            temp == "true"  || temp == "yes" || temp == "on" ? true :
            temp == "false" || temp == "no" || temp == "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;
    }

    // snips a substring and parses it.
    template<typename T> inline bool
    as(const std::string& in, unsigned start, unsigned len, T default_value)
    {
        std::string buf;
        std::copy( in.begin()+start, in.begin()+start+len, std::back_inserter(buf) );
        return as<T>(buf, default_value);
    }

    // converts a primitive to a string
    template<typename T> inline std::string
    toString(const T& value)
    {
        std::stringstream out;
		//out << std::setprecision(20) << std::fixed << value;
		out << std::setprecision(20) <<  value;
        std::string outStr;
        outStr = out.str();
        return outStr;
    }

    // template speciallization for a bool to print out "true" or "false"
    template<> inline std::string
    toString<bool>(const bool& value)
    {
        return value ? "true" : "false";
    }

    template<> inline std::string
    toString<osg::Vec3f>(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( 
            const std::string& delims =" \t\r\n", 
            const std::string& quotes ="'\"" );

        StringTokenizer(
            const std::string& input,
            StringVector& output,
            const std::string& delims =" \t\r\n", 
            const std::string& quotes ="'\"",
            bool keepEmpties =true, 
            bool trimTokens =true);

        StringTokenizer(
            const std::string& input,
            StringTable& output,
            const std::string& delims = " \t\r\n",
            const std::string& keypairseparators = "=",
            const std::string& quotes = "'\"",
            bool keepEmpties = true,
            bool trimTokens = true);


        void tokenize( const std::string& input, StringVector& output ) const;

        bool& keepEmpties() { return _allowEmpties; }

        bool& trimTokens() { return _trimTokens; }

        void addDelim( char delim, bool keepAsToken =false );

        void addDelims( const std::string& delims, bool keepAsTokens =false );

        void addQuote( char delim, bool keepInToken =false );

        void addQuotes( const std::string& delims, bool keepInTokens =false );

    private:
        using TokenMap = std::map<char,bool>;
        TokenMap _delims;
        TokenMap _quotes;
        bool     _allowEmpties;
        bool     _trimTokens;
    };
} }


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


#endif // OSGEARTH_STRING_UTILS_H

