//========================================================================
//
// GooString.cc
//
// Simple variable-length string type.
//
// Copyright 1996-2003 Glyph & Cog, LLC
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2006 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
// Copyright (C) 2007 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2008-2011, 2016-2018, 2022, 2025 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2011 Kenji Uno <ku@digitaldolphins.jp>
// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2012, 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2012 Pino Toscano <pino@kde.org>
// Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
// Copyright (C) 2015 William Bader <williambader@hotmail.com>
// Copyright (C) 2016 Jakub Alba <jakubalba@gmail.com>
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2018 Greg Knight <lyngvi@gmail.com>
// Copyright (C) 2019, 2022-2024 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2023 Even Rouault <even.rouault@mines-paris.org>
// Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
// Copyright (C) 2025 Jonathan Hähne <jonathan.haehne@hotmail.com>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include <config.h>

#include <cassert>
#include <cctype>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <limits>

#include "gmem.h"
#include "Error.h"
#include "GooString.h"

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

namespace {

union GooStringFormatArg {
    int i;
    unsigned int ui;
    long l;
    unsigned long ul;
    long long ll;
    unsigned long long ull;
    double f;
    char c;
    char *s;
    GooString *gs;
    std::string *r;
};

enum GooStringFormatType
{
    fmtIntDecimal,
    fmtIntHex,
    fmtIntHexUpper,
    fmtIntOctal,
    fmtIntBinary,
    fmtUIntDecimal,
    fmtUIntHex,
    fmtUIntHexUpper,
    fmtUIntOctal,
    fmtUIntBinary,
    fmtLongDecimal,
    fmtLongHex,
    fmtLongHexUpper,
    fmtLongOctal,
    fmtLongBinary,
    fmtULongDecimal,
    fmtULongHex,
    fmtULongHexUpper,
    fmtULongOctal,
    fmtULongBinary,
    fmtLongLongDecimal,
    fmtLongLongHex,
    fmtLongLongHexUpper,
    fmtLongLongOctal,
    fmtLongLongBinary,
    fmtULongLongDecimal,
    fmtULongLongHex,
    fmtULongLongHexUpper,
    fmtULongLongOctal,
    fmtULongLongBinary,
    fmtDouble,
    fmtDoubleTrimSmallAware,
    fmtDoubleTrim,
    fmtChar,
    fmtString,
    fmtGooString,
    fmtStdString,
    fmtSpace,
    fmtInvalidFormat
};

const char *const formatStrings[] = { "d",   "x",   "X",   "o",   "b",   "ud",   "ux",   "uX",   "uo",   "ub",   "ld", "lx", "lX", "lo", "lb", "uld", "ulx", "ulX", "ulo",  "ulb",
                                      "lld", "llx", "llX", "llo", "llb", "ulld", "ullx", "ullX", "ullo", "ullb", "f",  "gs", "g",  "c",  "s",  "t",   "r",   "w",   nullptr };
static_assert((fmtInvalidFormat + 1) == (sizeof(formatStrings) / sizeof(char *)));

void formatInt(long long x, char *buf, int bufSize, bool zeroFill, int width, int base, const char **p, int *len, bool upperCase = false);

void formatUInt(unsigned long long x, char *buf, int bufSize, bool zeroFill, int width, int base, const char **p, int *len, bool upperCase = false);

void formatDouble(double x, char *buf, int bufSize, int prec, bool trim, const char **p, int *len);

void formatDoubleSmallAware(double x, char *buf, int bufSize, int prec, bool trim, const char **p, int *len);

}

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

std::string GooString::format(const char *fmt, ...)
{
    std::string s;

    va_list argList;
    va_start(argList, fmt);
    appendfv(s, fmt, argList);
    va_end(argList);

    return s;
}

std::string GooString::formatv(const char *fmt, va_list argList)
{
    std::string s;

    appendfv(s, fmt, argList);

    return s;
}

std::string &GooString::appendf(std::string &that, const char *fmt, ...)
{
    va_list argList;
    va_start(argList, fmt);
    appendfv(that, fmt, argList);
    va_end(argList);

    return that;
}

std::string &GooString::appendfv(std::string &that, const char *fmt, va_list argList)
{
    GooStringFormatArg *args;
    int argsLen, argsSize;
    GooStringFormatArg arg;
    int idx, width, prec;
    bool reverseAlign, zeroFill;
    GooStringFormatType ft;
    char buf[65];
    int len, i;
    const char *p0, *p1;
    const char *str;
    GooStringFormatArg argsBuf[8];

    argsLen = 0;
    argsSize = sizeof(argsBuf) / sizeof(argsBuf[0]);
    args = argsBuf;

    p0 = fmt;
    while (*p0) {
        if (*p0 == '{') {
            ++p0;
            if (*p0 == '{') {
                ++p0;
                that.push_back('{');
            } else {

                // parse the format string
                if (*p0 < '0' || *p0 > '9') {
                    break;
                }
                idx = *p0 - '0';
                for (++p0; *p0 >= '0' && *p0 <= '9'; ++p0) {
                    idx = 10 * idx + (*p0 - '0');
                }
                if (*p0 != ':') {
                    break;
                }
                ++p0;
                if (*p0 == '-') {
                    reverseAlign = true;
                    ++p0;
                } else {
                    reverseAlign = false;
                }
                width = 0;
                zeroFill = *p0 == '0';
                for (; *p0 >= '0' && *p0 <= '9'; ++p0) {
                    width = 10 * width + (*p0 - '0');
                }
                if (width < 0) {
                    width = 0;
                }
                if (*p0 == '.') {
                    ++p0;
                    prec = 0;
                    for (; *p0 >= '0' && *p0 <= '9'; ++p0) {
                        prec = 10 * prec + (*p0 - '0');
                    }
                } else {
                    prec = 0;
                }
                for (ft = static_cast<GooStringFormatType>(0); formatStrings[ft]; ft = static_cast<GooStringFormatType>(ft + 1)) {
                    if (!strncmp(p0, formatStrings[ft], strlen(formatStrings[ft]))) {
                        break;
                    }
                }
                if (!formatStrings[ft]) {
                    break;
                }
                p0 += strlen(formatStrings[ft]);
                if (*p0 != '}') {
                    break;
                }
                ++p0;

                // fetch the argument
                if (idx > argsLen) {
                    break;
                }
                if (idx == argsLen) {
                    if (argsLen == argsSize) {
                        argsSize *= 2;
                        if (args == argsBuf) {
                            args = static_cast<GooStringFormatArg *>(gmallocn(argsSize, sizeof(GooStringFormatArg)));
                            memcpy(args, argsBuf, argsLen * sizeof(GooStringFormatArg));
                        } else {
                            args = static_cast<GooStringFormatArg *>(greallocn(args, argsSize, sizeof(GooStringFormatArg)));
                        }
                    }
                    switch (ft) {
                    case fmtIntDecimal:
                    case fmtIntHex:
                    case fmtIntHexUpper:
                    case fmtIntOctal:
                    case fmtIntBinary:
                    case fmtSpace:
                        args[argsLen].i = va_arg(argList, int);
                        break;
                    case fmtUIntDecimal:
                    case fmtUIntHex:
                    case fmtUIntHexUpper:
                    case fmtUIntOctal:
                    case fmtUIntBinary:
                        args[argsLen].ui = va_arg(argList, unsigned int);
                        break;
                    case fmtLongDecimal:
                    case fmtLongHex:
                    case fmtLongHexUpper:
                    case fmtLongOctal:
                    case fmtLongBinary:
                        args[argsLen].l = va_arg(argList, long);
                        break;
                    case fmtULongDecimal:
                    case fmtULongHex:
                    case fmtULongHexUpper:
                    case fmtULongOctal:
                    case fmtULongBinary:
                        args[argsLen].ul = va_arg(argList, unsigned long);
                        break;
                    case fmtLongLongDecimal:
                    case fmtLongLongHex:
                    case fmtLongLongHexUpper:
                    case fmtLongLongOctal:
                    case fmtLongLongBinary:
                        args[argsLen].ll = va_arg(argList, long long);
                        break;
                    case fmtULongLongDecimal:
                    case fmtULongLongHex:
                    case fmtULongLongHexUpper:
                    case fmtULongLongOctal:
                    case fmtULongLongBinary:
                        args[argsLen].ull = va_arg(argList, unsigned long long);
                        break;
                    case fmtDouble:
                    case fmtDoubleTrim:
                    case fmtDoubleTrimSmallAware:
                        args[argsLen].f = va_arg(argList, double);
                        break;
                    case fmtChar:
                        args[argsLen].c = static_cast<char>(va_arg(argList, int));
                        break;
                    case fmtString:
                        args[argsLen].s = va_arg(argList, char *);
                        break;
                    case fmtGooString:
                        args[argsLen].gs = va_arg(argList, GooString *);
                        break;
                    case fmtStdString:
                        args[argsLen].r = va_arg(argList, std::string *);
                        break;
                    case fmtInvalidFormat:
                        assert(false);
                    }
                    ++argsLen;
                }

                // format the argument
                arg = args[idx];
                switch (ft) {
                case fmtIntDecimal:
                    formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
                    break;
                case fmtIntHex:
                    formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
                    break;
                case fmtIntHexUpper:
                    formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 16, &str, &len, true);
                    break;
                case fmtIntOctal:
                    formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
                    break;
                case fmtIntBinary:
                    formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
                    break;
                case fmtUIntDecimal:
                    formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
                    break;
                case fmtUIntHex:
                    formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
                    break;
                case fmtUIntHexUpper:
                    formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 16, &str, &len, true);
                    break;
                case fmtUIntOctal:
                    formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
                    break;
                case fmtUIntBinary:
                    formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
                    break;
                case fmtLongDecimal:
                    formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
                    break;
                case fmtLongHex:
                    formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
                    break;
                case fmtLongHexUpper:
                    formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 16, &str, &len, true);
                    break;
                case fmtLongOctal:
                    formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
                    break;
                case fmtLongBinary:
                    formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
                    break;
                case fmtULongDecimal:
                    formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
                    break;
                case fmtULongHex:
                    formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
                    break;
                case fmtULongHexUpper:
                    formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 16, &str, &len, true);
                    break;
                case fmtULongOctal:
                    formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
                    break;
                case fmtULongBinary:
                    formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
                    break;
                case fmtLongLongDecimal:
                    formatInt(arg.ll, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
                    break;
                case fmtLongLongHex:
                    formatInt(arg.ll, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
                    break;
                case fmtLongLongHexUpper:
                    formatInt(arg.ll, buf, sizeof(buf), zeroFill, width, 16, &str, &len, true);
                    break;
                case fmtLongLongOctal:
                    formatInt(arg.ll, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
                    break;
                case fmtLongLongBinary:
                    formatInt(arg.ll, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
                    break;
                case fmtULongLongDecimal:
                    formatUInt(arg.ull, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
                    break;
                case fmtULongLongHex:
                    formatUInt(arg.ull, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
                    break;
                case fmtULongLongHexUpper:
                    formatUInt(arg.ull, buf, sizeof(buf), zeroFill, width, 16, &str, &len, true);
                    break;
                case fmtULongLongOctal:
                    formatUInt(arg.ull, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
                    break;
                case fmtULongLongBinary:
                    formatUInt(arg.ull, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
                    break;
                case fmtDouble:
                    formatDouble(arg.f, buf, sizeof(buf), prec, false, &str, &len);
                    break;
                case fmtDoubleTrim:
                    formatDouble(arg.f, buf, sizeof(buf), prec, true, &str, &len);
                    break;
                case fmtDoubleTrimSmallAware:
                    formatDoubleSmallAware(arg.f, buf, sizeof(buf), prec, true, &str, &len);
                    break;
                case fmtChar:
                    buf[0] = arg.c;
                    str = buf;
                    len = 1;
                    reverseAlign = !reverseAlign;
                    break;
                case fmtString: {
                    str = arg.s;
                    const size_t strlen_str = strlen(str);
                    if (strlen_str > static_cast<size_t>(std::numeric_limits<int>::max())) {
                        error(errSyntaxWarning, 0, "String truncated to INT_MAX bytes");
                        len = std::numeric_limits<int>::max();
                    } else {
                        len = static_cast<int>(strlen_str);
                    }
                    reverseAlign = !reverseAlign;
                    break;
                }
                case fmtGooString:
                    if (arg.gs) {
                        str = arg.gs->c_str();
                        len = arg.gs->size();
                    } else {
                        str = "(null)";
                        len = 6;
                    }
                    reverseAlign = !reverseAlign;
                    break;
                case fmtStdString:
                    if (arg.r) {
                        str = arg.r->c_str();
                        len = arg.r->size();
                    } else {
                        str = "(null)";
                        len = 6;
                    }
                    reverseAlign = !reverseAlign;
                    break;
                case fmtSpace:
                    str = buf;
                    len = 0;
                    width = arg.i;
                    break;
                case fmtInvalidFormat:
                    assert(false);
                }

                // append the formatted arg, handling width and alignment
                if (!reverseAlign && len < width) {
                    for (i = len; i < width; ++i) {
                        that.push_back(' ');
                    }
                }
                that.append(str, len);
                if (reverseAlign && len < width) {
                    for (i = len; i < width; ++i) {
                        that.push_back(' ');
                    }
                }
            }

        } else if (*p0 == '}') {
            ++p0;
            if (*p0 == '}') {
                ++p0;
            }
            that.push_back('}');

        } else {
            for (p1 = p0 + 1; *p1 && *p1 != '{' && *p1 != '}'; ++p1) {
                ;
            }
            that.append(p0, p1 - p0);
            p0 = p1;
        }
    }

    if (args != argsBuf) {
        gfree(args);
    }

    return that;
}

namespace {

const char lowerCaseDigits[17] = "0123456789abcdef";
const char upperCaseDigits[17] = "0123456789ABCDEF";

void formatInt(long long x, char *buf, int bufSize, bool zeroFill, int width, int base, const char **p, int *len, bool upperCase)
{
    const char *vals = upperCase ? upperCaseDigits : lowerCaseDigits;
    bool neg;
    int start, i, j;
    unsigned long long abs_x;

    i = bufSize;
    if ((neg = x < 0)) {
        abs_x = -x;
    } else {
        abs_x = x;
    }
    start = neg ? 1 : 0;
    if (abs_x == 0) {
        buf[--i] = '0';
    } else {
        while (i > start && abs_x) {
            buf[--i] = vals[abs_x % base];
            abs_x /= base;
        }
    }
    if (zeroFill) {
        for (j = bufSize - i; i > start && j < width - start; ++j) {
            buf[--i] = '0';
        }
    }
    if (neg) {
        buf[--i] = '-';
    }
    *p = buf + i;
    *len = bufSize - i;
}

void formatUInt(unsigned long long x, char *buf, int bufSize, bool zeroFill, int width, int base, const char **p, int *len, bool upperCase)
{
    const char *vals = upperCase ? upperCaseDigits : lowerCaseDigits;
    int i, j;

    i = bufSize;
    if (x == 0) {
        buf[--i] = '0';
    } else {
        while (i > 0 && x) {
            buf[--i] = vals[x % base];
            x /= base;
        }
    }
    if (zeroFill) {
        for (j = bufSize - i; i > 0 && j < width; ++j) {
            buf[--i] = '0';
        }
    }
    *p = buf + i;
    *len = bufSize - i;
}

void formatDouble(double x, char *buf, int bufSize, int prec, bool trim, const char **p, int *len)
{
    bool neg, started;
    double x2;
    int d, i, j;

    if ((neg = x < 0)) {
        x = -x;
    }
    x = floor(x * pow(10.0, prec) + 0.5);
    i = bufSize;
    started = !trim;
    for (j = 0; j < prec && i > 1; ++j) {
        x2 = floor(0.1 * (x + 0.5));
        d = static_cast<int>(floor(x - 10 * x2 + 0.5));
        if (started || d != 0) {
            buf[--i] = '0' + d;
            started = true;
        }
        x = x2;
    }
    if (i > 1 && started) {
        buf[--i] = '.';
    }
    if (i > 1) {
        do {
            x2 = floor(0.1 * (x + 0.5));
            d = static_cast<int>(floor(x - 10 * x2 + 0.5));
            buf[--i] = '0' + d;
            x = x2;
        } while (i > 1 && x);
    }
    if (neg) {
        buf[--i] = '-';
    }
    *p = buf + i;
    *len = bufSize - i;
}

void formatDoubleSmallAware(double x, char *buf, int bufSize, int prec, bool trim, const char **p, int *len)
{
    double absX = fabs(x);
    if (absX >= 0.1) {
        formatDouble(x, buf, bufSize, prec, trim, p, len);
    } else {
        while (absX < 0.1 && prec < 16) {
            absX = absX * 10;
            prec++;
        }
        formatDouble(x, buf, bufSize, prec, trim, p, len);
    }
}

}

void GooString::lowerCase(std::string &s)
{
    for (auto &c : s) {
        if (std::isupper(c)) {
            c = std::tolower(c);
        }
    }
}

std::string GooString::toLowerCase(std::string_view s)
{
    std::string newString(s);
    lowerCase(newString);
    return newString;
}
