/* bzflag
 * Copyright (c) 1993-2025 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// BZFlag common header
#include "common.h"

// interface header
#include "TextUtils.h"

// system headers
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <curl/curl.h>
#include <string>
#include <string.h>
#include <algorithm>
#include <sstream>
#include <stdarg.h>
#include <vector>
#include <stdio.h>

namespace TextUtils
{
std::string vformat(const char* fmt, va_list args)
{
    const int fixedbs = 8192;
    char buffer[fixedbs];
    const int bs = vsnprintf(buffer, fixedbs, fmt, args) + 1;
    if (bs > fixedbs)
    {
        char *bufp = new char[bs];
        vsnprintf(bufp, bs, fmt, args);
        std::string ret = bufp;
        delete[] bufp;
        return ret;
    }

    return buffer;
}


std::string format(const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    std::string result = vformat(fmt, args);
    va_end(args);
    return result;
}


std::string replace_all(const std::string& in, const std::string& replaceMe, const std::string& withMe)
{
    std::string::size_type beginPos = 0;
    std::string::size_type endPos = 0;
    std::ostringstream tempStream;

    endPos = in.find(replaceMe);
    if (endPos == std::string::npos)
        return in; // can't find anything to replace
    if (replaceMe.empty()) return in; // can't replace nothing with something -- can do reverse

    while (endPos != std::string::npos)
    {
        // push the  part up to
        tempStream << in.substr(beginPos,endPos-beginPos);
        tempStream << withMe;
        beginPos = endPos + replaceMe.size();
        endPos = in.find(replaceMe,beginPos);
    }
    tempStream << in.substr(beginPos);
    return tempStream.str();
}


std::string no_whitespace(const std::string &s)
{
    const int sourcesize = (int)s.size();

    int count = 0, i = 0, j = 0;
    for (i = 0; i < sourcesize; i++)
        if (!isWhitespace(s[i]))
            count++;

    // create result string of correct size
    std::string result(count, ' ');

    for (i = 0, j = 0; i < sourcesize; i++)
        if (!isWhitespace(s[i]))
            result[j++] = s[i];

    return result;
}


std::vector<std::string> tokenize(const std::string& in, const std::string &delims, const int maxTokens,
                                  const bool useQuotes)
{
    std::vector<std::string> tokens;
    int numTokens = 0;
    bool inQuote = false;

    std::ostringstream currentToken;

    const std::string::size_type len = in.size();
    std::string::size_type pos = in.find_first_not_of(delims);

    int currentChar  = (pos == std::string::npos) ? -1 : in[pos];
    bool enoughTokens = (maxTokens && (numTokens >= (maxTokens-1)));

    while (pos < len && pos != std::string::npos && !enoughTokens)
    {

        // get next token
        bool tokenDone = false;
        bool foundSlash = false;

        currentChar = (pos < len) ? in[pos] : -1;
        while ((currentChar != -1) && !tokenDone)
        {

            tokenDone = false;

            if (delims.find(currentChar) != std::string::npos && !inQuote)   // currentChar is a delim
            {
                pos ++;
                break; // breaks out of inner while loop
            }

            if (!useQuotes)
                currentToken << char(currentChar);
            else
            {

                switch (currentChar)
                {
                case '\\' : // found a backslash
                    if (foundSlash)
                    {
                        currentToken << char(currentChar);
                        foundSlash = false;
                    }
                    else
                        foundSlash = true;
                    break;
                case '\"' : // found a quote
                    if (foundSlash)   // found \"
                    {
                        currentToken << char(currentChar);
                        foundSlash = false;
                    }
                    else     // found unescaped "
                    {
                        if (inQuote)   // exiting a quote
                        {
                            // finish off current token
                            tokenDone = true;
                            inQuote = false;
                            //slurp off one additional delimeter if possible
                            if (pos+1 < len &&
                                    delims.find(in[pos+1]) != std::string::npos)
                                pos++;

                        }
                        else     // entering a quote
                        {
                            // finish off current token
                            tokenDone = true;
                            inQuote = true;
                        }
                    }
                    break;
                default:
                    if (foundSlash)   // don't care about slashes except for above cases
                    {
                        currentToken << '\\';
                        foundSlash = false;
                    }
                    currentToken << char(currentChar);
                    break;
                }
            }

            pos++;
            currentChar = (pos < len) ? in[pos] : -1;
        } // end of getting a Token

        if (currentToken.str().size() > 0)   // if the token is something add to list
        {
            tokens.push_back(currentToken.str());
            currentToken.str("");
            numTokens ++;
        }

        enoughTokens = (maxTokens && (numTokens >= (maxTokens-1)));
        if ((pos < len) && (pos != std::string::npos))
            pos = in.find_first_not_of(delims,pos);

    } // end of getting all tokens -- either EOL or max tokens reached

    if (enoughTokens && pos != std::string::npos)
    {
        std::string lastToken = in.substr(pos);
        if (lastToken.size() > 0)
            tokens.push_back(lastToken);
    }

    return tokens;
}

bool parseDuration(const char *duration, int &durationInt)
{
    if (strcasecmp(duration,"short") == 0
            || strcasecmp(duration,"default") == 0)
    {
        durationInt = -1;
        return true;
    }
    if (strcasecmp(duration,"forever") == 0
            || strcasecmp(duration,"max") == 0)
    {
        durationInt = 0;
        return true;
    }

    regex_t preg;
    int res = regcomp(&preg, "^([[:digit:]]+[hwdm]?)+$",
                      REG_ICASE | REG_NOSUB | REG_EXTENDED);
    res = regexec(&preg, duration, 0, NULL, 0);
    regfree(&preg);
    if (res == REG_NOMATCH)
        return false;

    durationInt = 0;
    int t = 0;
    int len = (int)strlen(duration);
    for (int i = 0; i < len; i++)
    {
        if (isdigit(duration[i]))
            t = t * 10 + (duration[i] - '0');
        else if (duration[i] == 'h' || duration[i] == 'H')
        {
            durationInt += (t * 60);
            t = 0;
        }
        else if (duration[i] == 'd' || duration[i] == 'D')
        {
            durationInt += (t * 1440);
            t = 0;
        }
        else if (duration[i] == 'w' || duration[i] == 'W')
        {
            durationInt += (t * 10080);
            t = 0;
        }
        else if (duration[i] == 'm' || duration[i] == 'M')
        {
            durationInt += (t);
            t = 0;
        }
    }
    durationInt += t;
    return true;
}

std::string url_encode(const std::string &text)
{
    std::string encoded = "";
    char *output = curl_easy_escape(NULL, text.c_str(), 0);

    if (output)
    {
        encoded = output;
        curl_free(output);
    }

    return encoded;
}

std::string url_decode(const std::string &text)
{
    std::string decoded = "";
    char *output = curl_easy_unescape(NULL, text.c_str(), 0, NULL);

    if (output)
    {
        decoded = output;
        curl_free(output);
    }

    return decoded;
}

std::string escape_nonprintable(const std::string &text, const char quotechar)
{
    std::string destination;
    for (int i=0;  i < (int) text.size(); i++)
    {
        const unsigned char c = text[i];
        if (c != '\\' && c != quotechar && (c == ' ' || isVisible(c)))
            destination += c;
        else
            destination += format("\\%-3.3o", c);
    }
    return destination;
}

std::string escape(const std::string &text, char escaper)
{
    std::string destination;
    for (int i = 0; i < (int) text.size(); i++)
    {
        char c = text[i];
        if (!isAlphanumeric(c))
            destination += escaper;
        destination += c;
    }
    return destination;
}

std::string unescape(const std::string &text, char escaper)
{
    const int len = (int) text.size();
    std::string destination;
    for (int i = 0; i < len; i++)
    {
        char c = text[i];
        if (c == escaper)
        {
            if (i < len - 1)
                destination += text[++i];
            // Should print an error otherwise
        }
        else
            destination += c;
    }
    return destination;
}

int unescape_lookup(const std::string &text, char escaper, char sep)
{
    int position = -1;
    for (int i = 0; i < (int) text.size(); i++)
    {
        char c = text[i];
        if (c == sep)
        {
            position = i;
            break;
        }
        if (c == escaper)
            i++;
    }
    return position;
}

// return a copy of a string, truncated to specified length,
//    make last char a '`' if truncation took place
std::string str_trunc_continued (const std::string &text, int len)
{
    std::string retstr = std::string (text, 0, len);
    if (retstr.size() == (unsigned int)len)
        retstr[len-1] = '~';
    return retstr;
}

size_t find_first_substr(const std::string &findin, const std::string &findwhat, size_t offset)
{
    if (findwhat.size())
    {
        for (size_t f = offset; f < findin.size(); f++)
        {
            if (findin[f] == findwhat[0])
            {
                size_t start = f;
                for (size_t w = 1; w < findwhat.size(); w++)
                {
                    if (f+w > findin.size())
                        return std::string::npos;
                    if (findin[f+w] != findwhat[w])
                    {
                        f+=w;
                        w = findwhat.size();
                    }
                }
                if (start == f)
                    return f;
            }
        }
    }
    return std::string::npos;
}

}

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
