#include <algorithm>
#include "MessagesDecoder.h"
#include "Gmm.h"

void MessagesDecoder::CmdmapRegisterCommand(string &command, uint16_t value)
{
    cmdmap[value] = command;
    if (command.rfind("STRVAR_", 0) == 0)
        strvar_codes.insert(value);
}

void MessagesDecoder::CharmapRegisterCharacter(string &code, uint16_t value)
{
    charmap[value] = code;
}

static string ConvertIntToHexStringN(unsigned value, StrConvMode mode, int n) {
    string dest;
    bool printing_zeroes = mode == STR_CONV_MODE_LEADING_ZEROS;
    unsigned shift = 4 * (n - 1);

    for (int i = 0; i < n; i++) {
        unsigned nybble = (value >> shift) & 0xF;
        if (nybble == 0 && !printing_zeroes) {
            if (i == n - 1) printing_zeroes = true;
            else if (mode == STR_CONV_MODE_RIGHT_ALIGN) {
                dest += ' ';
                continue;
            }
        }
        if (nybble != 0 || printing_zeroes) {
            if (nybble < 10) {
                dest += (char)('0' + nybble);
            } else {
                dest += (char)('A' + nybble - 10);
            }
        }
        shift -= 4;
    }

    return dest;
}

void MessagesDecoder::ReadMessagesFromBin(string& filename)
{
    ifstream infile(filename, ios_base::binary);
    if (!infile.good()) {
        throw ifstream::failure("Unable to open file \"" + filename + "\" for reading");
    }
    infile.read((char*)&header, sizeof(header));
    debug_printf("%d lines\n", header.count);
    alloc_table.resize(header.count);
    infile.read((char*)alloc_table.data(), (streamsize)(sizeof(MsgAlloc) * header.count));
    int i = 1;
    for (auto & alloc : alloc_table) {
        alloc.decrypt(header.key, i);
        u16string str;
        str.resize(alloc.length);
        infile.read((char*)str.data(), (2 * alloc.length));
        DecryptU16String(str, i);
        vec_encoded.push_back(str);
        i++;
    }
    infile.close();
}

u16string MessagesDecoder::DecodeTrainerNameMessage(u16string const &message)
{
    int bit = 0;
    u16string out = u"\uf100";
    auto src_p = message.cbegin() + 1;
    char16_t cur_char;
    do {
        cur_char = ((*src_p >> bit) & 0x1FF);
        bit += 9;
        if (bit >= 15) {
            src_p++;
            bit -= 15;
            if (bit != 0 && src_p < message.cend()) {
                cur_char |= (*src_p << (9 - bit)) & 0x1FF;
            }
        }
        out += cur_char;
    } while (src_p < message.cend() && cur_char != 0x1FF);
    return out;
}

string MessagesDecoder::DecodeMessage(u16string &message, int &i) {
    string decoded;
    bool is_trname = false;
    for (size_t j = 0; j < message.size(); j++) {
        uint16_t code = message[j];
        debug_printf("%04X ", code);

        if (charmap.find(code) != charmap.end()) {
            decoded += charmap[code];
        }
        else if (code == (is_trname ? 0x01FF : 0xFFFF)) {
            break;
        }
        else if (code == 0xFFFE) {
            decoded += '{';
            j++;
            code = message[j++];
            debug_printf("%04X ", code);
            string command;
            bool is_strvar = false;
            if (find(strvar_codes.cbegin(), strvar_codes.cend(), code & 0xFF00) != strvar_codes.cend()) {
                is_strvar = true;
                command = "STRVAR_" + ConvertIntToHexStringN((code >> 8), STR_CONV_MODE_LEFT_ALIGN, 2);
            }
            else if (cmdmap.find(code) != cmdmap.end()) {
                command = cmdmap[code];
            } else {
                throw runtime_error("Invalid control code in " + binfilename + ": " + ConvertIntToHexStringN(code, STR_CONV_MODE_LEADING_ZEROS, 4) + " at line " + to_string(i) + ":" + to_string(j));
            }
            decoded += command;
            int nargs = message[j++];
            debug_printf("%04X ", nargs);
            if (is_strvar) {
                decoded += ' ';
                decoded += to_string(code & 0xFF);
                if (nargs != 0)
                    decoded += ',';
            }
            for (int k = 0; k < nargs; k++) {
                decoded += ' ';
                decoded += to_string(message[j + k]);
                debug_printf("%04X ", message[j + k]);
                if (k != nargs - 1)
                    decoded += ',';
            }
            decoded += '}';
            j += nargs - 1;
        }
        else if (code == 0xF100) {
            decoded += "{TRNAME}";
            message = DecodeTrainerNameMessage(message);
            is_trname = true;
        }
        else {
            throw runtime_error("invalid character in " + binfilename + ": " + ConvertIntToHexStringN(code, STR_CONV_MODE_LEADING_ZEROS, 4) + " at " + to_string(i) + ":" + to_string(j));
        }
    }
    MsgAlloc & alloc = alloc_table[i];
    debug_printf("msg %d: at 0x%08X, count %d\n", i + 1, alloc.offset, alloc.length);
    return decoded;
}

template <typename T> void MessagesDecoder::WriteBinaryFile(string& filename, T& data) {
    ofstream outfile(filename, ios_base::binary);
    if (!outfile.good()) {
        throw ofstream::failure("Unable to open file \"" + filename + "\" for writing");
    }
    outfile.write((const char *)&data, sizeof(data));
    outfile.close();
}

void MessagesDecoder::WriteMessagesToText(string& filename) {
    stringstream ss;
    for (string& text : vec_decoded) {
        ss << text << "\r\n";
    }
    WriteTextFile(filename, ss.str());
}

void MessagesDecoder::WriteMessagesToGMM(string &filename) {
    GMM(filename, std::ios::out).ToFile(*this);
}

// Public virtual functions

void MessagesDecoder::ReadInput()
{
    ReadMessagesFromBin(binfilename);
}

void MessagesDecoder::Convert()
{
    for (int i = 0; i < (int)vec_encoded.size(); i++) {
        u16string message = vec_encoded[i];
        string decoded = DecodeMessage(message, i);
        vec_decoded.push_back(decoded);
    }
}

void MessagesDecoder::WriteOutput()
{
    switch (text_format) {
    case PlainText:
        WriteMessagesToText(textfilename);
        break;
    case GamefreakGMM:
        WriteMessagesToGMM(textfilename);
        break;
    }
}
