#pragma once
#ifndef PARSEUTIL_H
#define PARSEUTIL_H

#include "heallocation.h"
#include "log.h"

#include <QString>
#include <QList>
#include <QMap>
#include <QRegularExpression>

enum TokenClass {
    Number,
    Operator,
    Error,
};

class Token {
public:
    Token(QString value = "", QString type = "") {
        this->value = value;
        this->type = TokenClass::Operator;
        if (type == "decimal" || type == "hex") {
            this->type = TokenClass::Number;
            this->operatorPrecedence = -1;
        } else if (type == "operator") {
            this->operatorPrecedence = precedenceMap[value];
        } else if (type == "error") {
            this->type = TokenClass::Error;
        }
    }
    static QMap<QString, int> precedenceMap;
    QString value;
    TokenClass type;
    int operatorPrecedence; // only relevant for operator tokens
};

class ParseUtil
{
public:
    ParseUtil() { };
    void set_root(const QString &dir);
    static QString readTextFile(const QString &path);
    static int textFileLineCount(const QString &path);
    QList<QStringList> parseAsm(const QString &filename);
    int evaluateDefine(const QString&, const QMap<QString, int>&);
    QStringList readCArray(const QString &text, const QString &label);
    QMap<QString, QString> readNamedIndexCArray(const QString &text, const QString &label);
    QString readCIncbin(const QString &text, const QString &label);
    QMap<QString, int> readCDefines(const QString &filename, const QStringList &prefixes, QMap<QString, int> = { });
    QStringList readCDefinesSorted(const QString&, const QStringList&, const QMap<QString, int>& = { });
    QList<QStringList> getLabelMacros(const QList<QStringList>&, const QString&);
    QStringList getLabelValues(const QList<QStringList>&, const QString&);
    bool tryParseJsonFile(QJsonDocument *out, const QString &filepath);
    bool tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath);
    bool ensureFieldsExist(const QJsonObject &obj, const QList<QString> &fields);

    // Returns the 1-indexed line number for the definition of scriptLabel in the scripts file at filePath.
    // Returns 0 if a definition for scriptLabel cannot be found.
    static int getScriptLineNumber(const QString &filePath, const QString &scriptLabel);
    static int getRawScriptLineNumber(QString text, const QString &scriptLabel);
    static int getPoryScriptLineNumber(QString text, const QString &scriptLabel);
    static QStringList getGlobalScriptLabels(const QString &filePath);
    static QStringList getGlobalRawScriptLabels(QString text);
    static QStringList getGlobalPoryScriptLabels(QString text);
    static QString removeStringLiterals(QString text);
    static QString removeLineComments(QString text, const QString &commentSymbol);
    static QString removeLineComments(QString text, const QStringList &commentSymbols);

    static QStringList splitShellCommand(QStringView command);

private:
    QString root;
    QString text;
    QString file;
    QList<Token> tokenizeExpression(QString expression, const QMap<QString, int> &knownIdentifiers);
    QList<Token> generatePostfix(const QList<Token> &tokens);
    int evaluatePostfix(const QList<Token> &postfix);
    void error(const QString &message, const QString &expression);

    static const QRegularExpression re_incScriptLabel;
    static const QRegularExpression re_globalIncScriptLabel;
    static const QRegularExpression re_poryScriptLabel;
    static const QRegularExpression re_globalPoryScriptLabel;
    static const QRegularExpression re_poryRawSection;
};

#endif // PARSEUTIL_H