/* 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.
 */

// interface header
#include "Bundle.h"

// system headers
#include <fstream>
#include <stdio.h>
#include <string.h>

// local implementation headers
#include "StateDatabase.h"
#include "AnsiCodes.h"

Bundle::Bundle(const Bundle *pBundle)
{
    if (pBundle == NULL)
        return;

    mappings = pBundle->mappings;
}

void Bundle::load(const std::string &path)
{
    std::string untranslated;
    std::string translated;
    char buffer[1024];

    std::ifstream poStrm(path.c_str());
    if (!poStrm.good())
        return;

    poStrm.getline(buffer,1024);
    while (poStrm.good())
    {
        std::string line = buffer;
        std::string data;
        TLineType type = parseLine(line, data);
        if (type == tMSGID)
        {
            if (untranslated.length() > 0)
            {
                mappings.erase(untranslated);
                ensureNormalText(translated);
                mappings.insert(std::pair<std::string,std::string>(untranslated, translated));
            }
            untranslated = data;
            translated.resize(0);
        }
        else if (type == tMSGSTR)
        {
            if (untranslated.length() > 0)
                translated = data;
        }
        else if (type == tAPPEND)
        {
            if (untranslated.length() > 0)
                translated += data;
        }
        else if (type == tERROR)
        {

        }


        poStrm.getline(buffer,1024);
    }

    if ((untranslated.length() > 0) && (translated.length() > 0))
    {
        mappings.erase(untranslated);
        ensureNormalText(translated);
        mappings.insert(std::pair<std::string,std::string>(untranslated, translated));
    }
}

Bundle::TLineType Bundle::parseLine(const std::string &line, std::string &data) const
{
    int startPos, endPos;
    TLineType type;

    data.resize(0);
    startPos = line.find_first_not_of("\t \r\n");

    if ((startPos < 0) || (line.at(startPos) == '#'))
        return tCOMMENT;

    else if (line.at(startPos) == '"')
    {
        endPos = line.find_first_of('"', startPos+1);
        if (endPos < 0)
            endPos = line.length();
        data = line.substr(startPos+1, endPos-startPos-1);
        return tAPPEND;
    }

    endPos = line.find_first_of("\t \r\n\"");
    if (endPos < 0)
        endPos = line.length();
    std::string key = line.substr(startPos, endPos-startPos);
    if (key == "msgid")
        type = tMSGID;
    else if (key == "msgstr")
        type = tMSGSTR;
    else
        return tERROR;

    startPos = line.find_first_of('"', endPos + 1);
    if (startPos >= 0)
    {
        startPos++;
        endPos = line.find_first_of('"', startPos);
        if (endPos < 0)
            endPos = line.length();
        data = line.substr(startPos, endPos-startPos);
    }
    return type;
}

#include <set>
static std::set<std::string> unmapped;

std::string Bundle::getLocalString(const std::string &key) const
{
    if (key == "") return key;
    BundleStringMap::const_iterator it = mappings.find(key);
    if (it != mappings.end())
        return it->second;
    else
    {
        if (BZDB.getDebug())
        {
            if (unmapped.find(key) == unmapped.end())
            {
                unmapped.insert(key);
                std::string stripped = stripAnsiCodes (key);
                std::string debugStr = "Unmapped Locale String: " + stripped + "\n";
                logDebugMessage(1,"%s", debugStr.c_str());
            }
        }
        return key;
    }
}

const char utf8bytes[256] =
{
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,5,5,5,5,6,6,6,6
};

// TODO: sort this and bsearch it. perhaps divided by utf8 length
const char *translationTable[][2] =
{
    {"â", "a"}, {"à", "a"}, {"á", "a"}, {"ã", "a"},
    {"å", "aa"},
    {"ä", "ae"}, {"æ", "ae"},
    {"Â", "A"},
    {"Ä", "AE"}, {"Æ", "AE"},
    {"Å", "AA"},
    {"ç", "c"},
    {"é", "e"}, {"è", "e"}, {"ê", "e"}, {"ë", "e"},
    {"î", "i"}, {"ï", "i"}, {"í", "i"},
    {"ô", "o"}, {"ó", "o"}, {"õ", "o"},
    {"ö", "oe"}, {"ø", "oe"},
    {"Ö", "OE"}, {"Ø", "OE"},
    {"û", "u"}, {"ù", "u"}, {"ú", "u"},
    {"ü", "ue"},
    {"Ü", "UE"},
    {"ñ", "n"},
    {"ß", "ss"},
    {"¿", "?"},
    {"¡", "!"},
};

void Bundle::ensureNormalText(std::string &msg)
{
    // BZFlag's font system only supports ascii
    // convert msg to ascii
    // If you don't like it fix it.

    for (std::string::size_type i = 0; i < msg.length(); i++)
    {
        unsigned char c = msg.at(i);
        if (((c >= 'A') && (c <= 'Z'))
                || ((c >= 'a') && (c <= 'z'))
                || ((c >= '0') && (c <= '9'))
                || (c == '}') || (c == '{') || (c == ' ')
                || (c == ':') || (c == '/') || (c == '-')
                || (c == ',') || (c == '&') || (c == '?')
                || (c == '<') || (c == '>') || (c == '.')
                || (c == '(') || (c == ')') || (c == '%')
                || (c == '!') || (c == '+') || (c == '-')
                || (c == '$') || (c == ';') || (c == '@')
                || (c == '[') || (c == ']')
                || (c == '=') || (c == '\''))
        {
            ; // this char's ok by me
        }
        else
        {
            std::string replacement = "0x";
            unsigned int trans;
            // TODO: optimize this
            for (trans = 0; trans < sizeof(translationTable) / sizeof (char *) / 2; trans++)
            {
                if (!strncmp(translationTable[trans][0],&(msg.c_str()[i]),utf8bytes[(int)c]))
                {
                    replacement = translationTable[trans][1];
                    break;
                }
            }
            if (trans == sizeof(translationTable) / sizeof (char *) / 2)
            {
                // didn't find a match
                for (int j = 0; j < utf8bytes[(int)c]; j++)
                {
                    //for (int j = 0; j < 1; j++) {
                    char hexchar[30];
                    sprintf(hexchar, "%2X", (unsigned char)msg.at(i+j));
                    replacement += hexchar;
                }
            }
            msg.replace(i,utf8bytes[(int)c],replacement);
            i += replacement.length() - 1;
        }
    }
    //std::cout << "\"" + msg + "\"\n";
}


std::string Bundle::formatMessage(const std::string &key, const std::vector<std::string> *parms) const
{
    std::string messageIn = getLocalString(key);
    std::string messageOut;

    if (!parms || (parms->size() == 0))
        return messageIn;

    int parmCnt = parms->size();
    int startPos = 0;
    int lCurlyPos = messageIn.find_first_of("{");

    while (lCurlyPos >= 0)
    {
        messageOut += messageIn.substr(startPos, lCurlyPos - startPos);
        int rCurlyPos = messageIn.find_first_of("}", lCurlyPos++);
        if (rCurlyPos < 0)
        {
            messageOut += messageIn.substr(lCurlyPos);
            return messageOut;
        }
        std::string numStr = messageIn.substr(lCurlyPos, rCurlyPos-lCurlyPos);
        int num;
        if (sscanf(numStr.c_str(), "%d", &num) != 1)
            messageOut += messageIn.substr(lCurlyPos, rCurlyPos-lCurlyPos);
        else
        {
            num--;
            if ((num >= 0) && (num < parmCnt))
                messageOut += (*parms)[num];
        }
        startPos = rCurlyPos+1;
        lCurlyPos = messageIn.find_first_of("{", startPos);
    }
    messageOut += messageIn.substr(startPos);
    return messageOut;
}

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