#include "POTranslator.h"

#include <QDebug>
#include "FileSystem.h"

struct POEntry {
    QString text;
    bool fuzzy;
};

struct POTranslatorPrivate {
    QString filename;
    QHash<QByteArray, POEntry> mapping;
    QHash<QByteArray, POEntry> mapping_disambiguatrion;
    bool loaded = false;

    void reload();
};

class ParserArray : public QByteArray {
   public:
    ParserArray(const QByteArray& in) : QByteArray(in) {}
    bool chomp(const char* data, int length)
    {
        if (startsWith(data)) {
            remove(0, length);
            return true;
        }
        return false;
    }
    bool chompString(QByteArray& appendHere)
    {
        QByteArray msg;
        bool escape = false;
        if (size() < 2) {
            qDebug() << "String fragment is too short";
            return false;
        }
        if (!startsWith('"')) {
            qDebug() << "String fragment does not start with \"";
            return false;
        }
        if (!endsWith('"')) {
            qDebug() << "String fragment does not end with \", instead, there is" << at(size() - 1);
            return false;
        }
        for (int i = 1; i < size() - 1; i++) {
            char c = operator[](i);
            if (escape) {
                switch (c) {
                    case 'r':
                        msg += '\r';
                        break;
                    case 'n':
                        msg += '\n';
                        break;
                    case 't':
                        msg += '\t';
                        break;
                    case 'v':
                        msg += '\v';
                        break;
                    case 'a':
                        msg += '\a';
                        break;
                    case 'b':
                        msg += '\b';
                        break;
                    case 'f':
                        msg += '\f';
                        break;
                    case '"':
                        msg += '"';
                        break;
                    case '\\':
                        msg.append('\\');
                        break;
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7': {
                        int octal_start = i;
                        while ((c = operator[](i)) >= '0' && c <= '7') {
                            i++;
                            if (i == length() - 1) {
                                qDebug() << "Something went bad while parsing an octal escape string...";
                                return false;
                            }
                        }
                        msg += mid(octal_start, i - octal_start).toUInt(0, 8);
                        break;
                    }
                    case 'x': {
                        // chomp the 'x'
                        i++;
                        int hex_start = i;
                        while (isxdigit(operator[](i))) {
                            i++;
                            if (i == length() - 1) {
                                qDebug() << "Something went bad while parsing a hex escape string...";
                                return false;
                            }
                        }
                        msg += mid(hex_start, i - hex_start).toUInt(0, 16);
                        break;
                    }
                    default: {
                        qDebug() << "Invalid escape sequence character:" << c;
                        return false;
                    }
                }
                escape = false;
            } else if (c == '\\') {
                escape = true;
            } else {
                msg += c;
            }
        }
        if (escape) {
            qDebug() << "Unterminated escape sequence...";
            return false;
        }
        appendHere += msg;
        return true;
    }
};

void POTranslatorPrivate::reload()
{
    QFile file(filename);
    if (!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text)) {
        qDebug() << "Failed to open PO file:" << filename << "error:" << file.errorString();
        return;
    }

    QByteArray context;
    QByteArray disambiguation;
    QByteArray id;
    QByteArray str;
    bool fuzzy = false;
    bool nextFuzzy = false;

    enum class Mode { First, MessageContext, MessageId, MessageString } mode = Mode::First;

    int lineNumber = 0;
    QHash<QByteArray, POEntry> newMapping;
    QHash<QByteArray, POEntry> newMapping_disambiguation;
    auto endEntry = [&]() {
        auto strStr = QString::fromUtf8(str);
        // NOTE: PO header has empty id. We skip it.
        if (!id.isEmpty()) {
            auto normalKey = context + "|" + id;
            newMapping.insert(normalKey, { strStr, fuzzy });
            if (!disambiguation.isEmpty()) {
                auto disambiguationKey = context + "|" + id + "@" + disambiguation;
                newMapping_disambiguation.insert(disambiguationKey, { strStr, fuzzy });
            }
        }
        context.clear();
        disambiguation.clear();
        id.clear();
        str.clear();
        fuzzy = nextFuzzy;
        nextFuzzy = false;
    };
    while (!file.atEnd()) {
        ParserArray line = file.readLine();
        if (line.endsWith('\n')) {
            line.resize(line.size() - 1);
        }
        if (line.endsWith('\r')) {
            line.resize(line.size() - 1);
        }

        if (!line.size()) {
            // NIL
        } else if (line[0] == '#') {
            if (line.contains(", fuzzy")) {
                nextFuzzy = true;
            }
        } else if (line.startsWith('"')) {
            QByteArray temp;
            QByteArray* out = &temp;

            switch (mode) {
                case Mode::First:
                    qDebug() << "Unexpected escaped string during initial state... line:" << lineNumber;
                    return;
                case Mode::MessageString:
                    out = &str;
                    break;
                case Mode::MessageContext:
                    out = &context;
                    break;
                case Mode::MessageId:
                    out = &id;
                    break;
            }
            if (!line.chompString(*out)) {
                qDebug() << "Badly formatted string on line:" << lineNumber;
                return;
            }
        } else if (line.chomp("msgctxt ", 8)) {
            switch (mode) {
                case Mode::First:
                    break;
                case Mode::MessageString:
                    endEntry();
                    break;
                case Mode::MessageContext:
                case Mode::MessageId:
                    qDebug() << "Unexpected msgctxt line:" << lineNumber;
                    return;
            }
            if (line.chompString(context)) {
                auto parts = context.split('|');
                context = parts[0];
                if (parts.size() > 1 && !parts[1].isEmpty()) {
                    disambiguation = parts[1];
                }
                mode = Mode::MessageContext;
            }
        } else if (line.chomp("msgid ", 6)) {
            switch (mode) {
                case Mode::MessageContext:
                case Mode::First:
                    break;
                case Mode::MessageString:
                    endEntry();
                    break;
                case Mode::MessageId:
                    qDebug() << "Unexpected msgid line:" << lineNumber;
                    return;
            }
            if (line.chompString(id)) {
                mode = Mode::MessageId;
            }
        } else if (line.chomp("msgstr ", 7)) {
            switch (mode) {
                case Mode::First:
                case Mode::MessageString:
                case Mode::MessageContext:
                    qDebug() << "Unexpected msgstr line:" << lineNumber;
                    return;
                case Mode::MessageId:
                    break;
            }
            if (line.chompString(str)) {
                mode = Mode::MessageString;
            }
        } else {
            qDebug() << "I did not understand line:" << lineNumber << ":" << QString::fromUtf8(line);
        }
        lineNumber++;
    }
    endEntry();
    mapping = std::move(newMapping);
    mapping_disambiguatrion = std::move(newMapping_disambiguation);
    loaded = true;
}

POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslator(parent)
{
    d = new POTranslatorPrivate;
    d->filename = filename;
    d->reload();
}

POTranslator::~POTranslator()
{
    delete d;
}

QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, [[maybe_unused]] int n) const
{
    if (disambiguation) {
        auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation);
        auto iter = d->mapping_disambiguatrion.find(disambiguationKey);
        if (iter != d->mapping_disambiguatrion.end()) {
            auto& entry = *iter;
            if (entry.text.isEmpty()) {
                qDebug() << "Translation entry has no content:" << disambiguationKey;
            }
            if (entry.fuzzy) {
                qDebug() << "Translation entry is fuzzy:" << disambiguationKey << "->" << entry.text;
            }
            return entry.text;
        }
    }
    auto key = QByteArray(context) + "|" + QByteArray(sourceText);
    auto iter = d->mapping.find(key);
    if (iter != d->mapping.end()) {
        auto& entry = *iter;
        if (entry.text.isEmpty()) {
            qDebug() << "Translation entry has no content:" << key;
        }
        if (entry.fuzzy) {
            qDebug() << "Translation entry is fuzzy:" << key << "->" << entry.text;
        }
        return entry.text;
    }
    return QString();
}

bool POTranslator::isEmpty() const
{
    return !d->loaded;
}
