#include "description_parser.h"

#include "runtime/runtime.h"
#include "runtime/mem_arena.h"

#include <stdbool.h>
#include <limits.h>

extern int memcmp(const void *s1, const void *s2, size_t n);
extern void *memcpy(void *dest, const void *src, size_t n);

static bool IsAllowedChar(char c) {
    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
           (c == '.') || (c == '_') || (c == '/');
}

static bool IsWhitespace(char c) {
    return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

static void SkipWhitespace(rt_parse_state *state) {
    while (state->at < state->length && IsWhitespace(state->text[state->at])) {
        if (state->text[state->at] == '\n')
            ++state->line;
        ++state->at;
    }
}

static bool ParseAttribute(rt_parse_state *state, rt_text_span *_name) {
    rt_text_span name;
    name.start  = &state->text[state->at];
    name.length = 0;
    while (state->at < state->length && !IsWhitespace(state->text[state->at])) {
        if (IsAllowedChar(state->text[state->at])) {
            ++state->at;
            ++name.length;
        } else {
            rtReportError("AC",
                          "%s:%d Unexpected character %c",
                          state->file,
                          state->line,
                          state->text[state->at]);
            return false;
        }
    }
    *_name = name;
    return true;
}

static bool ParseValue(rt_parse_state *state, rt_text_span *_value) {
    rt_text_span value;
    value.start  = &state->text[state->at];
    value.length = 0;
    while (state->at < state->length && !IsWhitespace(state->text[state->at]) &&
           state->text[state->at] != ';') {
        if (IsAllowedChar(state->text[state->at])) {
            ++state->at;
            ++value.length;
        } else {
            rtReportError("AC",
                          "%s:%d Unexpected character %c",
                          state->file,
                          state->line,
                          state->text[state->at]);
            return false;
        }
    }
    *_value = value;
    return true;
}

#define BLOCK_BEGIN        "BEGIN"
#define BLOCK_BEGIN_LENGTH 5
#define BLOCK_END        "END"
#define BLOCK_END_LENGTH 3

RT_INLINE static bool IsBlockBegin(rt_parse_state *state) {
    return (state->length - state->at >= BLOCK_BEGIN_LENGTH) &&
           (memcmp(&state->text[state->at], BLOCK_BEGIN, BLOCK_BEGIN_LENGTH) == 0);
}

RT_INLINE static bool IsBlockEnd(rt_parse_state *state) {
    return (state->length - state->at >= BLOCK_END_LENGTH) &&
           (memcmp(&state->text[state->at], BLOCK_END, BLOCK_END_LENGTH) == 0);
}

static bool ParseBlock(rt_parse_state *state, rt_text_span *p_value) {
    rt_text_span value;
    value.start = &state->text[state->at];
    value.length = 0;
    while (state->at < state->length) {
        if (state->text[state->at] == BLOCK_END[0] && IsBlockEnd(state)) {
            *p_value = value;
            return true;
        }
        ++value.length;
        ++state->at;
    }
    /* did not find END */
    return false;
}

static bool ParseStmtList(rt_parse_state *state, unsigned int *list_index, rt_arena *arena);

static bool ParseStmt(rt_parse_state *state, unsigned int *stmt_index, rt_arena *arena) {
    rt_parsed_stmt stmt;
    stmt.next = UINT_MAX; /* end of list */

    SkipWhitespace(state);
    if (!ParseAttribute(state, &stmt.attribute))
        return false;
    SkipWhitespace(state);

    if (state->at == state->length) {
        rtReportError("AC", "%s:%d Expected either a value or '{'", state->file, state->line);
        return false;
    }

    if (state->text[state->at] == '{') {
        /* Consume '{' */
        ++state->at;

        stmt.form = RT_STMT_FORM_LIST;
        if (!ParseStmtList(state, &stmt.list_index, arena))
            return false;

        /* Consume '}' */
        if (state->at < state->length && state->text[state->at] == '}')
            ++state->at;
    } else if (IsBlockBegin(state)) {
        /* Consume BEGIN */
        state->at += BLOCK_BEGIN_LENGTH;

        stmt.form = RT_STMT_FORM_BLOCK;
        if (!ParseBlock(state, &stmt.block))
            return false;

        /* Consume END */
        state->at += BLOCK_END_LENGTH;
    } else {
        stmt.form = RT_STMT_FORM_VALUE;
        if (!ParseValue(state, &stmt.value))
            return false;

        /* Consume ';' */
        if (state->at < state->length && state->text[state->at] == ';')
            ++state->at;
    }

    SkipWhitespace(state);

    /* Add statement to array */
    if (state->statement_count == state->statement_capacity) {
        unsigned int cap     = (state->statement_capacity > 0) ? state->statement_capacity * 2 : 64;
        rt_parsed_stmt *temp = rtArenaPush(arena, sizeof(rt_parsed_stmt) * cap);
        if (!temp) {
            rtReportError("AC", "While parsing %s: Out of memory\n", state->file);
            return false;
        }
        if (state->statement_capacity > 0)
            memcpy(temp, state->statements, sizeof(rt_parsed_stmt) * state->statement_capacity);
        state->statements         = temp;
        state->statement_capacity = cap;
    }
    state->statements[state->statement_count] = stmt;
    *stmt_index                               = state->statement_count++;

    return true;
}

static bool ParseStmtList(rt_parse_state *state, unsigned int *list_index, rt_arena *arena) {
    rt_parsed_stmt_list list;
    list.first = UINT_MAX;
    list.count = 0;

    unsigned int last = UINT_MAX;

    while (state->at < state->length && state->text[state->at] != '}') {
        unsigned int stmt;
        if (!ParseStmt(state, &stmt, arena))
            return false;

        if (last != UINT_MAX)
            state->statements[last].next = stmt;
        else
            list.first = stmt;
        last = stmt;

        ++list.count;
    }

    /* Add list to array */
    if (state->statement_list_count == state->statement_list_capacity) {
        unsigned int cap =
            (state->statement_list_capacity > 0) ? state->statement_list_capacity * 2 : 64;
        rt_parsed_stmt_list *temp = rtArenaPush(arena, sizeof(rt_parsed_stmt_list) * cap);
        if (!temp) {
            rtReportError("AC", "While parsing %s: Out of memory\n", state->file);
            return false;
        }
        if (state->statement_list_capacity > 0)
            memcpy(temp,
                   state->statement_lists,
                   sizeof(rt_parsed_stmt_list) * state->statement_list_capacity);
        state->statement_lists         = temp;
        state->statement_list_capacity = cap;
    }
    state->statement_lists[state->statement_list_count] = list;
    *list_index                                         = state->statement_list_count++;
    return true;
}

const rt_parsed_stmt * rtFindStatement(const rt_parse_state *state, unsigned int list_index, const char *attribute) {
    if (list_index >= state->statement_list_count)
        return NULL;
    const rt_parsed_stmt_list *list = &state->statement_lists[list_index];
    unsigned int stmt_index         = list->first;
    for (unsigned int i = 0; i < list->count; ++i) {
        const rt_parsed_stmt *stmt = &state->statements[stmt_index];
        if (rtCompareSpanToString(stmt->attribute, attribute) == 0)
            return stmt;
        stmt_index = stmt->next;
    }
    return NULL;
}


rt_result rtParseDescription(const char *text,
                             size_t length,
                             const char *file_path,
                             unsigned int *_root_list,
                             rt_parse_state *_state,
                             struct rt_arena_s *arena) {

    rt_parse_state state = {.text                    = text,
                            .at                      = 0,
                            .length                  = length,
                            .line                    = 1,
                            .file                    = file_path,
                            .statements              = NULL,
                            .statement_lists         = NULL,
                            .statement_capacity      = 0,
                            .statement_list_capacity = 0};

    unsigned int root_list = 0;
    if (!ParseStmtList(&state, &root_list, arena)) {
        return 1;
    }
    *_root_list = root_list;
    *_state     = state;

    return RT_SUCCESS;
}
