/* vim: set expandtab ts=4 sw=4: */
/*
 * You may redistribute this program and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "benc/serialization/json/JsonBencMessageReader.h"
#include "benc/List.h"
#include "benc/Dict.h"
#include "benc/String.h"
#include "exception/Er.h"
#include "memory/Allocator.h"
#include "wire/Message.h"
#include "util/Gcc.h"
#include "util/Hex.h"
#include "util/Base10.h"

#include <stdbool.h>

struct Context {
    struct Message* const msg;
    struct Allocator* const alloc;
    const bool lax;
    int line;
    uintptr_t beginningLastLine;
};

static int getColumn(struct Context* ctx)
{
    return (uintptr_t) ctx->msg->msgbytes - ctx->beginningLastLine;
}

#define ERROR0(ctx, message) \
    return Er__raise(Gcc_SHORT_FILE, Gcc_LINE, ctx->alloc,    \
        "Error parsing config (line %d column %d): " message, \
        ctx->line, getColumn(ctx))
#define ERROR(ctx, message, ...) \
    return Er__raise(Gcc_SHORT_FILE, Gcc_LINE, ctx->alloc,    \
        "Error parsing config (line %d column %d): " message, \
        ctx->line, getColumn(ctx), __VA_ARGS__)

static Er_DEFUN(uint8_t peak(struct Context* ctx))
{
    if (!Message_getLength(ctx->msg)) { ERROR0(ctx, "Out of content while reading"); }
    Er_ret(ctx->msg->msgbytes[0]);
}

static Er_DEFUN(void skip(struct Context* ctx, int num))
{
    if (num > Message_getLength(ctx->msg)) { ERROR0(ctx, "Out of content while reading"); }
    for (int i = 0; i < num; i++) {
        if (ctx->msg->msgbytes[i] == '\n') {
            ctx->beginningLastLine = (uintptr_t) &ctx->msg->msgbytes[i];
            ctx->line++;
        }
    }
    Er(Message_eshift(ctx->msg, -num));
    Er_ret();
}

static Er_DEFUN(bool assertChar(struct Context* ctx, char chr, bool lax))
{
    char c = Er(peak(ctx));
    if (c != chr) {
        if (lax == true) { Er_ret(false); }
        ERROR(ctx, "Expected char [%c] but got [%c]", chr, c);
    }
    Er_ret(true);
}

static Er_DEFUN(void parseComment(struct Context* ctx))
{
    Er(assertChar(ctx, '/', false));
    Er(skip(ctx, 1));
    uint8_t secondChar = Er(peak(ctx));
    if (secondChar != '/' && secondChar != '*') { ERROR(ctx, "Unexpected char [%c]", secondChar); }
    bool lastCharSplat = false;
    for (;;) {
        Er(skip(ctx, 1));
        uint8_t chr = Er(peak(ctx));
        if (lastCharSplat && secondChar == '*' && chr == '/') {
            // get rid of the trailing *
            Er(skip(ctx, 1));
        } else if (secondChar == '/' && chr == '\n') {
        } else {
            lastCharSplat = (chr == '*');
            continue;
        }
        Er_ret();
    }
}

static Er_DEFUN(void parseWhitespaceAndComments(struct Context* ctx))
{
    for (;;) {
        switch (Er(peak(ctx))) {
            case '\n':
            case ' ':
            case '\r':
            case '\t':
                Er(skip(ctx, 1));
                continue;

            case '/':
                Er(parseComment(ctx));
                continue;

            default: break;
        }
        Er_ret();
    }
    ERROR0(ctx, "Reached end of message while parsing");
}

static Er_DEFUN(String* parseString(struct Context* ctx))
{
    Er(assertChar(ctx, '"', false));
    int line = ctx->line;
    uintptr_t beginningLastLine = ctx->beginningLastLine;
    int msgLen = Message_getLength(ctx->msg);

    String* out = NULL;
    uint32_t pos = 0;
    #define PUSHOUT(chr) do { \
        if (out) {                              \
            Assert_true(out->len > pos);        \
            out->bytes[pos] = (chr);            \
        }                                       \
        pos++;                                  \
    } while (0)
    // CHECKFILES_IGNORE expecting a ;

    for (;;) {
        Er(skip(ctx, 1));
        uint8_t bchar = Er(peak(ctx));
        switch (bchar) {
            case '"': {
                Er(skip(ctx, 1));
                if (out) { Er_ret(out); }
                // got the length, reset and then copy the string next cycle
                ctx->line = line;
                ctx->beginningLastLine = beginningLastLine;
                Er(Message_eshift(ctx->msg, msgLen - Message_getLength(ctx->msg)));
                out = String_newBinary(NULL, pos, ctx->alloc);
                pos = 0;
                continue;
            }
            case '\0':
            case '\n': {
                ERROR0(ctx, "Unterminated string");
            }
            case '\\': {
                Er(skip(ctx, 1));
                uint8_t x = Er(peak(ctx));
                Er(skip(ctx, 1));
                if (x != 'x') {
                    ERROR0(ctx, "Char \\ only allowed if followed by x (as in \\xff)");
                }
                uint8_t high = Er(peak(ctx));
                Er(skip(ctx, 1));
                uint8_t low = Er(peak(ctx));
                int byte = Hex_decodeByte(high, low);
                if (byte < 0 || (byte > 255)) { ERROR0(ctx, "Invalid hex encoding"); }
                PUSHOUT(byte);
                continue;
            }
            default: {
                PUSHOUT(bchar);
                continue;
            }
        }
    }
    #undef PUSHOUT
}

static Er_DEFUN(int64_t parseInteger(struct Context* ctx))
{
    Er_ret( Er(Base10_read(ctx->msg)) );
}

static Er_DEFUN(Object* parseGeneric(struct Context* ctx));

static Er_DEFUN(List* parseList(struct Context* ctx))
{
    Er(assertChar(ctx, '[', false));
    Er(skip(ctx, 1));
    struct List_Item* first = NULL;
    struct List_Item* last = NULL;
    for (int i = 0; ; i++) {
        for (;;) {
            Er(parseWhitespaceAndComments(ctx));
            // lax mode, allow ,, extra ,,, commas
            if (!ctx->lax || Er(peak(ctx)) != ',') { break; }
            Er(skip(ctx, 1));
        }
        if (Er(peak(ctx)) == ']') {
            Er(skip(ctx, 1));
            List* out = Allocator_malloc(ctx->alloc, sizeof(List));
            *out = first;
            Er_ret(out);
        }
        if (i && Er(assertChar(ctx, ',', ctx->lax))) {
            Er(skip(ctx, 1));
            Er(parseWhitespaceAndComments(ctx));
        }
        struct List_Item* item = Allocator_calloc(ctx->alloc, sizeof(struct List_Item), 1);
        item->elem = Er(parseGeneric(ctx));
        if (last) {
            last->next = item;
        } else {
            first = item;
        }
        last = item;
    }
}

static Er_DEFUN(Dict* parseDict(struct Context* ctx))
{
    Er(assertChar(ctx, '{', false));
    Er(skip(ctx, 1));
    struct Dict_Entry* last = NULL;
    struct Dict_Entry* first = NULL;
    for (int i = 0; ; i++) {
        for (;;) {
            Er(parseWhitespaceAndComments(ctx));
            if (!ctx->lax || Er(peak(ctx)) != ',') { break; }
            Er(skip(ctx, 1));
        }
        if (Er(peak(ctx)) == '}') {
            Er(skip(ctx, 1));
            Dict* out = Allocator_malloc(ctx->alloc, sizeof(Dict));
            *out = first;
            Er_ret(out);
        }
        if (i && Er(assertChar(ctx, ',', ctx->lax))) {
            Er(skip(ctx, 1));
            Er(parseWhitespaceAndComments(ctx));
        }
        struct Dict_Entry* entry = Allocator_calloc(ctx->alloc, sizeof(struct Dict_Entry), 1);
        entry->key = Er(parseString(ctx));
        Er(parseWhitespaceAndComments(ctx));
        if (Er(assertChar(ctx, ':', ctx->lax))) {
            Er(skip(ctx, 1));
            Er(parseWhitespaceAndComments(ctx));
        }
        entry->val = Er(parseGeneric(ctx));
        if (last) {
            last->next = entry;
        } else {
            first = entry;
        }
        last = entry;
    }
}

static Er_DEFUN(Object* parseGeneric(struct Context* ctx))
{
    Object* out = Allocator_calloc(ctx->alloc, sizeof(Object), 1);
    uint8_t c = Er(peak(ctx));
    switch (c) {
        case '-':
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9': {
            out->type = Object_INTEGER;
            out->as.number = Er(parseInteger(ctx));
            break;
        }
        case '[': {
            out->type = Object_LIST;
            out->as.list = Er(parseList(ctx));
            break;
        }
        case '{': {
            out->type = Object_DICT;
            out->as.dictionary = Er(parseDict(ctx));
            break;
        }
        case '"': {
            out->type = Object_STRING;
            out->as.string = Er(parseString(ctx));
            break;
        }
        default:
            ERROR(ctx, "While looking for something to parse: "
                   "expected one of - 0 1 2 3 4 5 6 7 8 9 [ { \", found [%c]", c);
    }
    Er_ret(out);
}

Er_DEFUN(Dict* JsonBencMessageReader_read(
    struct Message* msg,
    struct Allocator* alloc,
    bool lax
)) {
    struct Context ctx = {
        .msg = msg,
        .alloc = alloc,
        .lax = lax,
        .line = 1,
        .beginningLastLine = (uintptr_t) msg->msgbytes
    };
    Er_ret( Er(parseDict(&ctx)) );
}

const char* JsonBencMessageReader_readNoExcept(
    struct Message* msg,
    struct Allocator* alloc,
    Dict** outPtr,
    bool lax)
{
    struct Er_Ret* er = NULL;
    Dict* out = Er_check(&er, JsonBencMessageReader_read(msg, alloc, lax));
    if (er) {
        return er->message;
    }
    *outPtr = out;
    return NULL;
}

