#pragma once
#include "bx/string.h"
#include <bx/file.h>
#include <cctype>
#include <cstdint>

#include "Logging.h"
#include "TypeDef.h"

enum class TokenType : uint8_t
{
    StartOfFile,
    TypeKeyword,
    EnumKeyword,
    Name,
    ScopeStart,
    ScopeEnd,
};

struct ParseStack
{
    static constexpr size_t MaxSize = 32;
    TokenType Stack[MaxSize] = {TokenType::StartOfFile};
    int32_t StackIdx = 0;

    TokenType GetCurrent()
    {
        return Stack[StackIdx];
    }
    bool Push(TokenType T)
    {
        if (StackIdx >= MaxSize - 1)
        {
            return false;
        }
        StackIdx++;
        Stack[StackIdx] = T;
        return true;
    }
    TokenType Pop()
    {
        if (StackIdx != 0)
        {
            return TokenType::StartOfFile;
        }
        TokenType T = Stack[StackIdx];
        StackIdx--;
        return T;
    }
};

class Parser
{
  public:
    enum Result : uint8_t
    {
        OK,
        Error,
        EndOfFile,
    };

    static constexpr int32_t MaxBufferSize = 1024 * 1024;
    static constexpr int32_t MaxTokens = UINT16_MAX;
    static bool IsValidNameCharacter(char c)
    {
        return isgraph(c) && c != '"' && c != '(' && c != ')';
    }

    int32_t BufferSize = 0;
    char Buffer[MaxBufferSize]{0};
    char* ReadPtr = &Buffer[0];
    uint32_t Line = 0;

    Def::DefinitionFile Definitions;
    ParseStack Stack;

  public:
    Result Parse();
    void CalculateHashes();

  private:
    int32_t GetRemaining();
    Result SkipWhitespace(bool* SkippedNewLine = nullptr);
    Result SkipLine();
    bool CmpAdvance(bx::StringView expect, Result& Res, bool RequireGap = false);
    Result ExpectChar(bx::StringView expect);
    Result Advance(int32_t Amount);
    Result HandleFileStart();
    Result HandleType();
    Result HandleEnum();
    Result ReadName(char* Target);
    Result ReadUint(uint32_t& Out);
    Result ReadNativeFieldType(Def::FieldType& FieldT);
    Result ReadDefinedFieldType(Def::FieldType& FieldT);
    Result ReadTypeToken();
    Result ReadOptionalEnumValues(Def::Enum& Enum, int32_t EntryIdx);

    uint32_t CalculateTypeHash(const Def::Type& t);

    void ErrorLine()
    {
        char line[64]{0};
        for (int32_t i = 0; i < sizeof(line) - 1; ++i)
        {
            line[i] = ReadPtr[0];
            if (Advance(1) != Result::OK) break;
            if (*ReadPtr == '\n' || *ReadPtr == '\r') break;
        }
        LOG_ERROR(Line, "%s", line);
    }
};
