#include "json.h"
#include "../collections/collection.h"
#include "../util/str.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char* key;
    JsonValue* val;
} KV;

DEFINE_VEC(JsonValue*, Arr, arr)
DEFINE_VEC(KV, Obj, obj)

struct JsonValue {
    JsonType type;
    union {
        bool bool_val;
        char* str_val;
        Arr arr_val;
        Obj obj_val;
    };
};

bool json_is(const JsonValue* value, JsonType type)
{
    return value != NULL && value->type == type;
}

bool json_bool(const JsonValue* value)
{
    return value->bool_val;
}

int64_t json_int(const JsonValue* value)
{
    return strtoll(value->str_val, NULL, 10);
}

double json_float(const JsonValue* value)
{
    return strtof(value->str_val, NULL);
}

const char* json_string(const JsonValue* value)
{
    return value->str_val;
}

size_t json_array_size(const JsonValue* value)
{
    return value->arr_val.size;
}

const JsonValue* json_array_get(const JsonValue* value, size_t idx)
{
    return value->arr_val.data[idx];
}

bool json_object_has(const JsonValue* value, const char* key)
{
    for (size_t i = 0; i < value->obj_val.size; ++i) {
        KV* kv = &value->obj_val.data[i];
        if (strcmp(key, kv->key) == 0) {
            return true;
        }
    }
    return false;
}

const JsonValue* json_object_get(const JsonValue* value, const char* key)
{
    for (size_t i = 0; i < value->obj_val.size; ++i) {
        KV* kv = &value->obj_val.data[i];
        if (strcmp(key, kv->key) == 0) {
            return kv->val;
        }
    }
    return NULL;
}

void json_free(JsonValue* value)
{
    switch (value->type) {
        case JsonType_Error:
        case JsonType_Null:
        case JsonType_Bool:
            break;
        case JsonType_Number:
        case JsonType_String:
            free(value->str_val);
            break;
        case JsonType_Array:
            for (size_t i = 0; i < value->arr_val.size; ++i) {
                json_free(value->arr_val.data[i]);
            }
            arr_destroy(&value->arr_val);
            break;
        case JsonType_Object:
            for (size_t i = 0; i < value->obj_val.size; ++i) {
                free(value->obj_val.data[i].key);
                json_free(value->obj_val.data[i].val);
            }
            obj_destroy(&value->obj_val);
            break;
    }
    free(value);
}

JsonValue* json_parse(const char* text, size_t text_len)
{
    JsonParser p;
    json_parser_construct(&p, text, text_len);
    JsonValue* json = json_parser_parse(&p);
    json_parser_destroy(&p);
    return json;
}

#define TOK_EOF '\0'
#define TOK_ERROR 'e'
#define TOK_NULL 'n'
#define TOK_FALSE 'f'
#define TOK_TRUE 't'
#define TOK_NUMBER '0'
#define TOK_STRING '"'

static inline JsonValue* alloc(JsonValue init);
static inline void lex(JsonParser* p);
static inline void lstep(JsonParser* p);

void json_parser_construct(JsonParser* p, const char* text, size_t text_len)
{
    *p = (JsonParser) {
        text,
        text_len,
        .i = 0,
        .ch = text[0],
        .curr_tok = TOK_EOF,
        .curr_val = NULL,
    };
    lex(p);
}

void json_parser_destroy(JsonParser* p)
{
    (void)p;
}

JsonValue* json_parser_parse(JsonParser* p)
{
    switch (p->curr_tok) {
        case TOK_EOF:
            fprintf(stderr, "error: json: unexpected eof\n");
            return NULL;
        case TOK_ERROR:
            lex(p);
            return NULL;
        case TOK_NULL:
            lex(p);
            return alloc((JsonValue) { .type = JsonType_Null });
        case TOK_FALSE:
            lex(p);
            return alloc(
                (JsonValue) { .type = JsonType_Bool, .bool_val = false });
        case TOK_TRUE:
            lex(p);
            return alloc(
                (JsonValue) { .type = JsonType_Null, .bool_val = true });
        case TOK_NUMBER: {
            char* val = p->curr_val;
            lex(p);
            return alloc(
                (JsonValue) { .type = JsonType_Number, .str_val = val });
        }
        case TOK_STRING: {
            char* val = p->curr_val;
            lex(p);
            return alloc(
                (JsonValue) { .type = JsonType_String, .str_val = val });
        }
    }

    if (p->curr_tok == '[') {
        lex(p);
        Arr arr;
        arr_construct(&arr);
        {
            JsonValue* value = json_parser_parse(p);
            if (!value)
                return NULL;
            arr_push(&arr, value);
        }
        while (p->curr_tok != TOK_EOF && p->curr_tok != ']') {
            if (p->curr_tok != ',') {
                fprintf(stderr, "error: json: expected ',' in array\n");
                return NULL;
            }
            lex(p);
            JsonValue* value = json_parser_parse(p);
            if (!value)
                return NULL;
            arr_push(&arr, value);
        }
        if (p->curr_tok != ']') {
            fprintf(stderr, "error: json: expected ']' after array\n");
            return NULL;
        }
        lex(p);
        return alloc((JsonValue) { .type = JsonType_Array, .arr_val = arr });
    }

    if (p->curr_tok == '{') {
        lex(p);
        Obj obj;
        obj_construct(&obj);
        {
            if (p->curr_tok != '"') {
                fprintf(stderr, "error: json: expected '\"' in kv\n");
                return NULL;
            }
            char* key = p->curr_val;
            lex(p);
            if (p->curr_tok != ':') {
                fprintf(stderr, "error: json: expected ':' in kv\n");
                return NULL;
            }
            lex(p);
            JsonValue* value = json_parser_parse(p);
            if (!value)
                return NULL;
            obj_push(&obj, (KV) { key, value });
        }
        while (p->curr_tok != TOK_EOF && p->curr_tok != '}') {
            if (p->curr_tok != ',') {
                fprintf(stderr, "error: json: expected ',' in object\n");
                return NULL;
            }
            lex(p);

            if (p->curr_tok != '"') {
                fprintf(stderr, "error: json: expected '\"' in kv\n");
                return NULL;
            }
            char* key = p->curr_val;
            lex(p);
            if (p->curr_tok != ':') {
                fprintf(stderr, "error: json: expected ':' in kv\n");
                return NULL;
            }
            lex(p);
            JsonValue* value = json_parser_parse(p);
            if (!value)
                return NULL;
            obj_push(&obj, (KV) { key, value });
        }
        if (p->curr_tok != '}') {
            fprintf(stderr, "error: json: expected '}' after object\n");
            return NULL;
        }
        lex(p);
        return alloc((JsonValue) { .type = JsonType_Object, .obj_val = obj });
    }

    fprintf(stderr, "error: json: unexpeted tok\n");
    return NULL;
}

static inline JsonValue* alloc(JsonValue init)
{
    JsonValue* value = malloc(sizeof(JsonValue));
    *value = init;
    return value;
}

static inline void lex(JsonParser* p)
{
    if (p->i >= p->text_len) {
        p->curr_tok = TOK_EOF;
        return;
    }
    switch (p->ch) {
        case ' ':
        case '\t':
        case '\r':
        case '\n':
            lstep(p);
            lex(p);
            return;
        case '[':
        case ']':
        case '{':
        case '}':
        case ',':
        case ':':
            p->curr_tok = p->ch;
            lstep(p);
            return;
        case '0':
            lstep(p);
            p->curr_tok = TOK_NUMBER;
            return;
    }
    if ((p->ch >= '1' && p->ch <= '9') || p->ch == '.') {
        String value;
        string_construct(&value);
        int dec_seps = 0;
        while (p->i < p->text_len
            && ((p->ch >= '0' && p->ch <= '9')
                || (p->ch == '.' && dec_seps <= 1))) {
            if (p->ch == '.') {
                dec_seps += 1;
            }
            string_push(&value, p->ch);
            lstep(p);
        }
        // should be interned
        char* copy = string_copy(&value);

        string_destroy(&value);
        p->curr_tok = TOK_NUMBER;
        p->curr_val = copy;
        return;
    }
    if (p->ch == '"') {
        lstep(p);

        String value;
        string_construct(&value);

        while (p->i < p->text_len && p->text[p->i] != '"') {
            if (p->text[p->i] == '\\') {
                lstep(p);
                if (p->i >= p->text_len)
                    break;
                switch (p->ch) {
                    case '0':
                        string_push(&value, '\0');
                        break;
                    case 'n':
                        string_push(&value, '\n');
                        break;
                    case 'r':
                        string_push(&value, '\r');
                        break;
                    case 't':
                        string_push(&value, '\t');
                        break;
                    default:
                        string_push(&value, p->ch);
                        break;
                }
            } else {
                string_push(&value, p->ch);
            }
            lstep(p);
        }
        if (p->i >= p->text_len || p->text[p->i] != '"') {
            string_destroy(&value);
            fprintf(stderr, "error: json: bad string\n");
            p->curr_tok = TOK_ERROR;
            return;
        }
        lstep(p);

        // should be interned
        char* copy = string_copy(&value);

        string_destroy(&value);
        p->curr_tok = '"';
        p->curr_val = copy;
        return;
    }

    fprintf(stderr, "error: json: illegal char '%c'\n", p->ch);
    lstep(p);
    p->curr_tok = TOK_ERROR;
    return;
}

static inline void lstep(JsonParser* p)
{
    p->i += 1;
    p->ch = p->text[p->i];
}
