#pragma once

#include <stdexcept>
#include <string>

#include "data/dv.hpp"
#include "typedefs.hpp"

inline int is_box(int c) {
    switch (c) {
        case 'B':
        case 'b':
            return 2;
        case 'O':
        case 'o':
            return 8;
        case 'X':
        case 'x':
            return 16;
    }
    return 10;
}

inline bool is_digit(int c) {
    return (c >= '0' && c <= '9');
}

inline bool is_whitespace(int c) {
    return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f';
}

inline bool is_identifier_start(int c) {
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
}

inline bool is_identifier_part(int c) {
    return is_identifier_start(c) || is_digit(c) || c == '-';
}

inline bool is_json_identifier_start(int c) {
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' ||
           c == '.';
}

inline bool is_json_identifier_part(int c) {
    return is_json_identifier_start(c) || is_digit(c) || c == '-';
}

inline int hexchar2int(int c) {
    if (c >= '0' && c <= '9') {
        return c - '0';
    }
    if (c >= 'a' && c <= 'f') {
        return 10 + c - 'a';
    }
    if (c >= 'A' && c <= 'F') {
        return 10 + c - 'A';
    }
    return -1;
}

class parsing_error : public std::runtime_error {
public:
    std::string filename;
    std::string source;
    uint pos;
    uint line;
    uint linestart;

    parsing_error(
        const std::string& message,
        std::string_view filename,
        std::string_view source,
        uint pos,
        uint line,
        uint linestart
    );
    std::string errorLog() const;
};

class BasicParser {
protected:
    std::string_view filename;
    std::string_view source;
    uint pos = 0;
    uint line = 1;
    uint linestart = 0;

    virtual void skipWhitespace();
    void skip(size_t n);
    void skipLine();
    bool skipTo(const std::string& substring);
    void expect(char expected);
    void expect(const std::string& substring);
    bool isNext(const std::string& substring);
    void expectNewLine();
    void goBack(size_t count = 1);
    void reset();

    int64_t parseSimpleInt(int base);
    dv::value parseNumber(int sign);
    dv::value parseNumber();
    std::string parseString(char chr, bool closeRequired = true);

    parsing_error error(const std::string& message);
public:
    std::string_view readUntil(char c);
    std::string_view readUntilEOL();
    std::string parseName();
    std::string parseXmlName();
    bool hasNext();
    size_t remain() const;
    char peek();
    char peekInLine();
    char peekNoJump();
    char nextChar();

    BasicParser(std::string_view file, std::string_view source);
};
