#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "Compiler.h"
#include "Operation.h"
#include "Tokenizer.h"

#define MAX_BYTES (1024 * 1024)
#define ERROR_LENGTH 256

static char error[ERROR_LENGTH] = {'\0'};

static unsigned char byteCode[MAX_BYTES];
static int writeIndex = 0;
static int line = 1;

static void cError(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vsnprintf(error, ERROR_LENGTH, format, args);
    va_end(args);
}

static void cUnexpectedToken(Token t) {
    cError("unexpected token on line %d: %s", line, tGetTokenName(t));
}

static bool cAddBytes(const void* data, int length) {
    if(writeIndex + length > MAX_BYTES) {
        cError("the compiler buffer is too small");
        return false;
    }
    memcpy(byteCode + writeIndex, data, length);
    writeIndex += length;
    return true;
}

static bool cAddOperation(Operation token) {
    unsigned char c = token;
    return cAddBytes(&c, 1) && cAddBytes(&line, sizeof(int));
}

static Token tReadTokenAndLine() {
    Token t = tReadToken();
    if(tReadInt(&line)) {
        return t;
    }
    return T_END;
}

static bool cConsumeToken(Token wanted) {
    Token t = tReadTokenAndLine();
    if(wanted == t) {
        return true;
    }
    cError("unexpected token on line %d: expected '%s' got '%s'", line, tGetTokenName(wanted), tGetTokenName(t));
    return false;
}

static bool cConsumeTokenIf(Token t) {
    if(tPeekToken() == t) {
        tReadTokenAndLine();
        return true;
    }
    return false;
}

static bool cExpression();

static bool cPrimary() {
    if(cConsumeTokenIf(T_INT)) {
        int value;
        return tReadInt(&value) && cAddOperation(OP_PUSH_INT) && cAddBytes(&value, sizeof(int));
    } else if(cConsumeTokenIf(T_FLOAT)) {
        float value;
        return tReadFloat(&value) && cAddOperation(OP_PUSH_FLOAT) && cAddBytes(&value, sizeof(float));
    } else if(cConsumeTokenIf(T_NULL)) {
        return cAddOperation(OP_PUSH_NULL);
    } else if(cConsumeTokenIf(T_TRUE)) {
        return cAddOperation(OP_PUSH_TRUE);
    } else if(cConsumeTokenIf(T_FALSE)) {
        return cAddOperation(OP_PUSH_FALSE);
    } else if(cConsumeTokenIf(T_OPEN_BRACKET)) {
        return cExpression() && cConsumeToken(T_CLOSE_BRACKET);
    }
    cUnexpectedToken(tPeekToken());
    return false;
}

static bool cMul() {
    if(!cPrimary()) {
        return false;
    }
    while(cConsumeTokenIf(T_MUL)) {
        if(!cPrimary() || !cAddOperation(OP_MUL)) {
            return false;
        }
    }
    return true;
}

static bool cAdd() {
    if(!cMul()) {
        return false;
    }
    while(cConsumeTokenIf(T_ADD)) {
        if(!cMul() || !cAddOperation(OP_ADD)) {
            return false;
        }
    }
    return true;
}

static bool cExpression() {
    return cAdd();
}

static bool cPrint() {
    return cExpression() && cConsumeToken(T_SEMICOLON) && cAddOperation(OP_PRINT);
}

static bool cLine() {
    Token t = tReadTokenAndLine();
    if(t == T_END) {
        return false;
    } else if(t == T_PRINT) {
        return cPrint();
    }
    cUnexpectedToken(t);
    return false;
}

unsigned char* cCompile(int* codeLength) {
    writeIndex = 0;
    error[0] = '\0';
    while(cLine()) {
    }
    if(error[0] != '\0') {
        return NULL;
    }
    unsigned char* bytes = malloc(writeIndex);
    memcpy(bytes, byteCode, writeIndex);
    *codeLength = writeIndex;
    return bytes;
}

const char* cGetError() {
    return error;
}