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

#include "Compiler.h"
#include "DataType.h"
#include "tokenizer/Tokenizer.h"
#include "utils/Functions.h"
#include "utils/Variables.h"
#include "vm/Operation.h"

#define ERROR_LENGTH 256
#define RETURN_BUFFER 16
#define BREAK_BUFFER 32

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

static ByteCode* code;

static int16 line = 1;

static Variables vars;
static Functions functions;
static Functions functionQueue;
static Structs structs;

static int returns[RETURN_BUFFER];
static int returnIndex = 0;
static bool hasReturn = false;
static DataType returnType;

static int breaks[BREAK_BUFFER];
static int breakIndex = 0;
static int forWhileStack = 0;
static int continueAt = 0;

typedef struct {
    Operation intOp;
    Operation floatOp;
    Operation boolOp;
    const char* name;
} TypedOp;

static const TypedOp TYPED_MUL = {OP_MUL_INT, OP_MUL_FLOAT, OP_NOTHING, "*"};
static const TypedOp TYPED_DIV = {OP_DIV_INT, OP_DIV_FLOAT, OP_NOTHING, "/"};
static const TypedOp TYPED_MOD = {OP_MOD_INT, OP_NOTHING, OP_NOTHING, "%"};
static const TypedOp TYPED_ADD = {OP_ADD_INT, OP_ADD_FLOAT, OP_NOTHING, "+"};
static const TypedOp TYPED_SUB = {OP_SUB_INT, OP_SUB_FLOAT, OP_NOTHING, "-"};
static const TypedOp TYPED_LESS = {OP_LESS_INT, OP_LESS_FLOAT, OP_NOTHING, "<"};
static const TypedOp TYPED_LESS_EQUAL = {OP_GREATER_INT, OP_GREATER_FLOAT,
                                         OP_NOTHING, "<="};
static const TypedOp TYPED_GREATER = {OP_GREATER_INT, OP_GREATER_FLOAT,
                                      OP_NOTHING, ">"};
static const TypedOp TYPED_GREATER_EQUAL = {OP_LESS_INT, OP_LESS_FLOAT,
                                            OP_NOTHING, ">="};
static const TypedOp TYPED_EQUAL = {OP_EQUAL_INT, OP_EQUAL_FLOAT, OP_EQUAL_BOOL,
                                    "=="};
static const TypedOp TYPED_NOT_EQUAL = {OP_EQUAL_INT, OP_EQUAL_FLOAT,
                                        OP_EQUAL_BOOL, "!="};
static const TypedOp TYPED_BIT_OR = {OP_BIT_OR, OP_NOTHING, OP_NOTHING, "|"};
static const TypedOp TYPED_BIT_XOR = {OP_BIT_XOR, OP_NOTHING, OP_NOTHING, "^"};
static const TypedOp TYPED_BIT_AND = {OP_BIT_AND, OP_NOTHING, OP_NOTHING, "&"};
static const TypedOp TYPED_LEFT_SHIFT = {OP_LEFT_SHIFT, OP_NOTHING, OP_NOTHING,
                                         "<<"};
static const TypedOp TYPED_RIGHT_SHIFT = {OP_RIGHT_SHIFT, OP_NOTHING,
                                          OP_NOTHING, ">>"};

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

static const char* cGetName(DataType dt) {
    return dtGetName(&structs, dt);
}

static void cInvalidOperation(DataType a, DataType b, const char* op) {
    cError("invalid operation: %s %s %s", cGetName(a), op, cGetName(b));
}

static void cNotDeclared(const char* name) {
    cError("variable %s has not been declared", name);
}

static void cDeclared(const char* name) {
    cError("%s has already been declared", name);
}

static void cTooMuchArguments() {
    cError("too much function arguments");
}

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

static void cAddOperation(Operation token) {
    unsigned char c = token;
    bcAddBytes(code, &c, 1);
}

static int cReserveInt() {
    return bcReserveBytes(code, sizeof(int));
}

static void cSetInt(int p, int i) {
    bcSetBytes(code, p, &i, sizeof(int));
}

static void cAddInt(int i) {
    bcAddBytes(code, &i, sizeof(int));
}

static void cAddInt16(int16 i) {
    bcAddBytes(code, &i, sizeof(int16));
}

static Token cReadTokenAndLine() {
    Token t = tReadToken();
    if(tReadInt16(&line)) {
        return t;
    }
    return T_END;
}

static void cConsumeToken(Token wanted) {
    Token t = cReadTokenAndLine();
    if(wanted != t) {
        cError("unexpected token on line %d: expected '%s' got '%s'", line,
               tGetName(wanted), tGetName(t));
    }
}

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

static void cConstantInt() {
    int value;
    if(!tReadInt(&value)) {
        cError("int token without an int on line %d", line);
    }
    cAddOperation(OP_PUSH_INT);
    cAddInt(value);
}

static void cConstantFloat() {
    float value;
    if(!tReadFloat(&value)) {
        cError("float token without a float on line %d", line);
    }
    cAddOperation(OP_PUSH_FLOAT);
    bcAddBytes(code, &value, sizeof(float));
}

static const char* cReadString() {
    int length;
    const char* literal = tReadString(&length);
    if(literal == NULL) {
        cError("literal without string on line %d", line);
    }
    return literal;
}

static DataType cExpression();

static void cCallFunctionArguments(Function* f) {
    while(!cConsumeTokenIf(T_CLOSE_BRACKET)) {
        DataType dt = cExpression();
        if(fAddArgument(f, dt, &structs)) {
            cTooMuchArguments();
        }
        if(cConsumeTokenIf(T_COMMA) && tPeekToken() == T_CLOSE_BRACKET) {
            cUnexpectedToken(tPeekToken());
        }
    }
}

static DataType cCallFunction(const char* name) {
    cAddOperation(OP_PUSH_INT);
    cAddInt(0);
    Function f;
    fInit(&f, name, line);
    cCallFunctionArguments(&f);
    cAddOperation(OP_GOSUB);
    Function* found = fsSearch(&functions, &f);
    if(found == NULL) {
        cError("unknown function");
    }
    if(found->address == -1) {
        f.returnType = found->returnType;
        f.address = cReserveInt();
        fsAdd(&functionQueue, &f);
    } else {
        cAddInt(found->address);
    }
    cAddInt(found->size);
    return found->returnType;
}

static void cWalkStruct(Variable* v) {
    int pointers;
    if(cConsumeTokenIf(T_ARROW)) {
        pointers = 1;
        cAddOperation(OP_REFERENCE);
    } else if(cConsumeTokenIf(T_POINT)) {
        pointers = 0;
    } else {
        return;
    }
    Struct* st = dtGetStruct(&structs, v->type);
    if(st == NULL || v->type.pointers != pointers) {
        DataType w = v->type;
        w.pointers = pointers;
        cError("%s is not %s but %s", v->name, cGetName(w), cGetName(v->type));
    }
    cConsumeToken(T_LITERAL);
    const char* name = cReadString();
    Variable inner;
    if(vSearchStruct(&inner, &structs, st, name)) {
        cError("%s has no member %s", v->name, name);
    }
    v->type = inner.type;
    cAddOperation(OP_PUSH_INT);
    cAddInt(inner.address);
    cAddOperation(OP_ADD_INT);
}

static void cReference(Variable* v, int dimension) {
    cAddOperation(OP_DEREFERENCE_VAR);
    cAddInt(v->address);
    while(dimension > 0) {
        if(!dtIsPointer(v->type)) {
            cError("too many *");
        }
        v->type = dtReference(v->type);
        dimension--;
        cAddOperation(OP_REFERENCE);
    }
    cWalkStruct(v);
}

static void cLoadRef(Variable* v) {
    switch(dtAsInt(v->type)) {
        case DT_INT: cAddOperation(OP_LOAD_INT); break;
        case DT_BOOL: cAddOperation(OP_LOAD_BOOL); break;
        case DT_FLOAT: cAddOperation(OP_LOAD_FLOAT); break;
        case DT_STRUCT:
            {
                Struct* st = dtGetStruct(&structs, v->type);
                if(st == NULL) {
                    cError("compiler struct error");
                }
                cAddOperation(OP_LOAD);
                cAddInt(dtGetSize(v->type, &structs));
                break;
            }
        default:
            if(dtIsPointer(v->type)) {
                cAddOperation(OP_LOAD_INT);
            } else {
                cError("cannot load type %s", cGetName(v->type));
            }
    }
}

static void cStore(Variable* v, DataType dt, const char* name) {
    if(!dtCompare(v->type, dt)) {
        cInvalidOperation(v->type, dt, name);
    }
    switch(dtAsInt(v->type)) {
        case DT_INT: cAddOperation(OP_STORE_INT); break;
        case DT_BOOL: cAddOperation(OP_STORE_BOOL); break;
        case DT_FLOAT: cAddOperation(OP_STORE_FLOAT); break;
        default:
            if(dtIsPointer(v->type)) {
                cAddOperation(OP_STORE_ARRAY);
            } else {
                cError("cannot store type %s", cGetName(v->type));
            }
    }
}

static DataType cPostChange(Variable* v, int change, const char* name) {
    if(!dtCompare(v->type, dtInt())) {
        cError("%s needs an int", name);
    }
    cAddOperation(OP_LOAD_INT);
    cReference(v, 0);
    cAddOperation(OP_DUPLICATE_REFERENCE);
    cAddOperation(OP_LOAD_INT);
    cAddOperation(OP_PUSH_INT);
    cAddInt(change);
    cAddOperation(OP_ADD_INT);
    cAddOperation(OP_STORE_INT);
    return dtInt();
}

static DataType cLiteral() {
    const char* literal = cReadString();
    if(cConsumeTokenIf(T_OPEN_BRACKET)) {
        DataType dt = cCallFunction(literal);
        if(dtCompare(dt, dtVoid())) {
            cError("function returns void");
        }
        return dt;
    }
    Variable v;
    if(vsSearch(&vars, &v, literal)) {
        cNotDeclared(literal);
    }
    cReference(&v, 0);
    if(cConsumeTokenIf(T_INCREMENT)) {
        return cPostChange(&v, 1, "++");
    } else if(cConsumeTokenIf(T_DECREMENT)) {
        return cPostChange(&v, -1, "--");
    }
    cLoadRef(&v);
    return v.type;
}

static DataType cBracketPrimary() {
    DataType result = cExpression();
    cConsumeToken(T_CLOSE_BRACKET);
    return result;
}

static DataType cAllocArray(DataType dt, Operation op) {
    cConsumeToken(T_OPEN_SQUARE_BRACKET);
    DataType index = cExpression();
    if(!dtCompare(index, dtInt())) {
        cError("array size must be an int");
    }
    cConsumeToken(T_CLOSE_SQUARE_BRACKET);
    cAddOperation(op);
    return dtToArray(dt, 1);
}

static DataType cPrimary() {
    Token t = cReadTokenAndLine();
    switch(t) {
        case T_CONST_INT: cConstantInt(); return dtInt();
        case T_CONST_FLOAT: cConstantFloat(); return dtFloat();
        case T_TRUE: cAddOperation(OP_PUSH_TRUE); return dtBool();
        case T_FALSE: cAddOperation(OP_PUSH_FALSE); return dtBool();
        case T_OPEN_BRACKET: return cBracketPrimary();
        case T_LITERAL: return cLiteral();
        case T_INT: return cAllocArray(dtInt(), OP_INT_ARRAY);
        default: cUnexpectedToken(t); return dtVoid();
    }
}

static DataType cPreChange(int change, const char* name) {
    cConsumeToken(T_LITERAL);
    const char* literal = cReadString();
    Variable v;
    if(vsSearch(&vars, &v, literal)) {
        cNotDeclared(literal);
    }
    cReference(&v, 0);
    if(!dtCompare(v.type, dtInt())) {
        cError("%s needs an int", name);
    }
    cAddOperation(OP_DUPLICATE_REFERENCE);
    cAddOperation(OP_DUPLICATE_REFERENCE);
    cAddOperation(OP_LOAD_INT);
    cAddOperation(OP_PUSH_INT);
    cAddInt(change);
    cAddOperation(OP_ADD_INT);
    cAddOperation(OP_STORE_INT);
    cAddOperation(OP_LOAD_INT);
    return dtInt();
}

static DataType cPreUnary() {
    if(cConsumeTokenIf(T_SUB)) {
        DataType result = cPrimary();
        switch(dtAsInt(result)) {
            case DT_INT: cAddOperation(OP_INVERT_SIGN_INT); break;
            case DT_FLOAT: cAddOperation(OP_INVERT_SIGN_FLOAT); break;
            default: cError("cannot invert sign of %s", cGetName(result));
        }
        return result;
    } else if(cConsumeTokenIf(T_INCREMENT)) {
        return cPreChange(1, "++");
    } else if(cConsumeTokenIf(T_DECREMENT)) {
        return cPreChange(-1, "--");
    } else if(cConsumeTokenIf(T_NOT)) {
        int counter = 1;
        while(cConsumeTokenIf(T_NOT)) {
            counter++;
        }
        DataType result = cPrimary();
        if(!dtCompare(result, dtBool())) {
            cError("! needs a bool not %s", cGetName(result));
        }
        cAddOperation(OP_NOT);
        if((counter & 1) == 0) {
            cAddOperation(OP_NOT);
        }
        return dtBool();
    } else if(cConsumeTokenIf(T_BIT_NOT)) {
        DataType result = cPrimary();
        if(dtCompare(result, dtInt())) {
            cAddOperation(OP_BIT_NOT);
        } else {
            cError("~ needs an int not %s", cGetName(result));
        }
        return result;
    } else if(cConsumeTokenIf(T_BIT_AND)) {
        cConsumeToken(T_LITERAL);
        const char* literal = cReadString();
        Variable v;
        if(vsSearch(&vars, &v, literal)) {
            cNotDeclared(literal);
        }
        cReference(&v, 0);
        return dtDereference(v.type);
    } else if(cConsumeTokenIf(T_MUL)) {
        int c = 1;
        while(cConsumeTokenIf(T_MUL)) {
            c++;
        }
        cConsumeToken(T_LITERAL);
        const char* literal = cReadString();
        Variable v;
        if(vsSearch(&vars, &v, literal)) {
            cNotDeclared(literal);
        }
        cReference(&v, c);
        cLoadRef(&v);
        return v.type;
    }
    return cPrimary();
}

static void cAddTypeOperation(DataType a, DataType b, const TypedOp* op) {
    if(dtCompare(a, dtInt()) && dtCompare(b, dtInt()) &&
       op->intOp != OP_NOTHING) {
        cAddOperation(op->intOp);
    } else if(dtCompare(a, dtFloat()) && dtCompare(b, dtFloat()) &&
              op->floatOp != OP_NOTHING) {
        cAddOperation(op->floatOp);
    } else if(dtCompare(a, dtBool()) && dtCompare(b, dtBool()) &&
              op->boolOp != OP_NOTHING) {
        cAddOperation(op->boolOp);
    } else {
        cInvalidOperation(a, b, op->name);
    }
}

static DataType cMul() {
    DataType a = cPreUnary();
    while(true) {
        if(cConsumeTokenIf(T_MUL)) {
            cAddTypeOperation(a, cPreUnary(), &TYPED_MUL);
        } else if(cConsumeTokenIf(T_DIV)) {
            cAddTypeOperation(a, cPreUnary(), &TYPED_DIV);
        } else if(cConsumeTokenIf(T_MOD)) {
            cAddTypeOperation(a, cPreUnary(), &TYPED_MOD);
        } else {
            break;
        }
    }
    return a;
}

static DataType cAdd() {
    DataType a = cMul();
    while(true) {
        if(cConsumeTokenIf(T_ADD)) {
            cAddTypeOperation(a, cMul(), &TYPED_ADD);
        } else if(cConsumeTokenIf(T_SUB)) {
            cAddTypeOperation(a, cMul(), &TYPED_SUB);
        } else {
            break;
        }
    }
    return a;
}

static DataType cShift() {
    DataType a = cAdd();
    while(true) {
        if(cConsumeTokenIf(T_LEFT_SHIFT)) {
            cAddTypeOperation(a, cAdd(), &TYPED_LEFT_SHIFT);
        } else if(cConsumeTokenIf(T_RIGHT_SHIFT)) {
            cAddTypeOperation(a, cAdd(), &TYPED_RIGHT_SHIFT);
        } else {
            break;
        }
    }
    return a;
}

static DataType cComparison() {
    DataType a = cShift();
    while(true) {
        if(cConsumeTokenIf(T_LESS)) {
            cAddTypeOperation(a, cShift(), &TYPED_LESS);
            a = dtBool();
        } else if(cConsumeTokenIf(T_LESS_EQUAL)) {
            cAddTypeOperation(a, cShift(), &TYPED_LESS_EQUAL);
            cAddOperation(OP_NOT);
            a = dtBool();
        } else if(cConsumeTokenIf(T_GREATER)) {
            cAddTypeOperation(a, cShift(), &TYPED_GREATER);
            a = dtBool();
        } else if(cConsumeTokenIf(T_GREATER_EQUAL)) {
            cAddTypeOperation(a, cShift(), &TYPED_GREATER_EQUAL);
            cAddOperation(OP_NOT);
            a = dtBool();
        } else {
            break;
        }
    }
    return a;
}

static DataType cEqual() {
    DataType a = cComparison();
    while(true) {
        if(cConsumeTokenIf(T_EQUAL)) {
            cAddTypeOperation(a, cComparison(), &TYPED_EQUAL);
            a = dtBool();
        } else if(cConsumeTokenIf(T_NOT_EQUAL)) {
            cAddTypeOperation(a, cComparison(), &TYPED_NOT_EQUAL);
            cAddOperation(OP_NOT);
            a = dtBool();
        } else {
            break;
        }
    }
    return a;
}

static DataType cBitAnd() {
    DataType a = cEqual();
    while(cConsumeTokenIf(T_BIT_AND)) {
        DataType b = cEqual();
        cAddTypeOperation(a, b, &TYPED_BIT_AND);
    }
    return a;
}

static DataType cBitXor() {
    DataType a = cBitAnd();
    while(cConsumeTokenIf(T_BIT_XOR)) {
        DataType b = cBitAnd();
        cAddTypeOperation(a, b, &TYPED_BIT_XOR);
    }
    return a;
}

static DataType cBitOr() {
    DataType a = cBitXor();
    while(cConsumeTokenIf(T_BIT_OR)) {
        DataType b = cBitXor();
        cAddTypeOperation(a, b, &TYPED_BIT_OR);
    }
    return a;
}

static DataType cAnd() {
    DataType a = cBitOr();
    while(cConsumeTokenIf(T_AND)) {
        cAddOperation(OP_PEEK_FALSE_GOTO);
        int p = cReserveInt();
        DataType b = cBitOr();
        if(!dtCompare(a, dtBool()) || !dtCompare(b, dtBool())) {
            cInvalidOperation(a, b, "&&");
        }
        cAddOperation(OP_AND);
        cSetInt(p, code->length);
    }
    return a;
}

static DataType cOr() {
    DataType a = cAnd();
    while(cConsumeTokenIf(T_OR)) {
        cAddOperation(OP_PEEK_TRUE_GOTO);
        int p = cReserveInt();
        DataType b = cAnd();
        if(!dtCompare(a, dtBool()) || !dtCompare(b, dtBool())) {
            cInvalidOperation(a, b, "||");
        }
        cAddOperation(OP_OR);
        cSetInt(p, code->length);
    }
    return a;
}

static DataType cExpression() {
    return cOr();
}

static void cOperationSet(Variable* v, const TypedOp* op) {
    cAddOperation(OP_DUPLICATE_REFERENCE);
    cLoadRef(v);
    DataType dt = cExpression();
    cAddTypeOperation(v->type, dt, op);
    cStore(v, dt, "=");
}

static void cAddPostLineChange(Variable* v, int change, const char* name) {
    if(!dtCompare(v->type, dtInt())) {
        cError("%s needs an int", name);
    }
    cAddOperation(OP_DUPLICATE_REFERENCE);
    cAddOperation(OP_LOAD_INT);
    cAddOperation(OP_PUSH_INT);
    cAddInt(change);
    cAddOperation(OP_ADD_INT);
    cAddOperation(OP_STORE_INT);
}

static DataType cExtendType(DataType dt) {
    while(cConsumeTokenIf(T_MUL)) {
        dt = dtDereference(dt);
    }
    return dt;
}

static void cDeclareStruct(Struct* st) {
    DataType dt = cExtendType(dtStruct(st));
    cConsumeToken(T_LITERAL);
    const char* var = cReadString();
    if(vsInScope(&vars, var)) {
        cDeclared(var);
    }
    Variable* vp = vsAdd(&vars, var, dt, &structs);
    if(dtIsPointer(dt)) {
        cConsumeToken(T_SET);
        cReference(vp, 0);
        cStore(vp, cExpression(), "=");
    }
}

static void cLineVariable(const char* name, int dimension) {
    Variable v;
    if(vsSearch(&vars, &v, name)) {
        cNotDeclared(name);
    }
    cReference(&v, dimension);
    Token t = cReadTokenAndLine();
    switch(t) {
        case T_SET: cStore(&v, cExpression(), "="); break;
        case T_ADD_SET: cOperationSet(&v, &TYPED_ADD); break;
        case T_SUB_SET: cOperationSet(&v, &TYPED_SUB); break;
        case T_MUL_SET: cOperationSet(&v, &TYPED_MUL); break;
        case T_DIV_SET: cOperationSet(&v, &TYPED_DIV); break;
        case T_MOD_SET: cOperationSet(&v, &TYPED_MOD); break;
        case T_BIT_AND_SET: cOperationSet(&v, &TYPED_BIT_AND); break;
        case T_BIT_OR_SET: cOperationSet(&v, &TYPED_BIT_OR); break;
        case T_BIT_XOR_SET: cOperationSet(&v, &TYPED_BIT_XOR); break;
        case T_LEFT_SHIFT_SET: cOperationSet(&v, &TYPED_LEFT_SHIFT); break;
        case T_RIGHT_SHIFT_SET: cOperationSet(&v, &TYPED_RIGHT_SHIFT); break;
        case T_INCREMENT: cAddPostLineChange(&v, 1, "++"); break;
        case T_DECREMENT: cAddPostLineChange(&v, -1, "--"); break;
        default: cUnexpectedToken(t);
    }
}

static void cLineLiteral() {
    const char* literal = cReadString();
    if(cConsumeTokenIf(T_OPEN_BRACKET)) {
        DataType dt = cCallFunction(literal);
        if(!dtCompare(dt, dtVoid())) {
            cError("function returns %s not void", cGetName(dt));
        }
        return;
    }
    Struct* st = stsSearch(&structs, literal);
    if(st != NULL) {
        cDeclareStruct(st);
        return;
    }
    cLineVariable(literal, 0);
}

static void cLine(Token t);

static void cConsumeBody() {
    int oldLine = line;
    while(!cConsumeTokenIf(T_CLOSE_CURVED_BRACKET)) {
        Token t = cReadTokenAndLine();
        if(t == T_END) {
            line = oldLine;
            cError("unexpected end of file: non closed curved bracket");
        }
        cLine(t);
    }
}

static void cConsumeScope() {
    Scope scope;
    vsEnterScope(&vars, &scope);
    cConsumeBody();
    vsLeaveScope(&vars, &scope);
}

static void cAddReturn(Operation op) {
    cAddOperation(op);
    returns[returnIndex++] = cReserveInt();
}

static void cReturn() {
    if(returnIndex >= RETURN_BUFFER) {
        cError("too much returns in function");
    }
    hasReturn = true;
    if(dtCompare(returnType, dtVoid())) {
        cConsumeToken(T_SEMICOLON);
        cAddReturn(OP_RETURN);
        return;
    }
    DataType dt = cExpression();
    if(!dtCompare(dt, returnType)) {
        cError("wrong return type, should be %s", cGetName(returnType));
    }
    switch(dtAsInt(dt)) {
        case DT_INT: cAddReturn(OP_RETURN_INT); break;
        case DT_BOOL: cAddReturn(OP_RETURN_BOOL); break;
        case DT_FLOAT: cAddReturn(OP_RETURN_FLOAT); break;
        default: cError("cannot return %s", cGetName(dt));
    }
    cConsumeToken(T_SEMICOLON);
}

static void cPrint() {
    DataType dt = cExpression();
    switch(dtAsInt(dt)) {
        case DT_INT: cAddOperation(OP_PRINT_INT); break;
        case DT_FLOAT: cAddOperation(OP_PRINT_FLOAT); break;
        case DT_BOOL: cAddOperation(OP_PRINT_BOOL); break;
        default: cError("cannot print type %s", cGetName(dt));
    }
    cConsumeToken(T_SEMICOLON);
}

static void cIf() {
    cConsumeToken(T_OPEN_BRACKET);
    DataType dt = cExpression();
    if(!dtCompare(dt, dtBool())) {
        cError("if expects a bool not %s", cGetName(dt));
    }
    cConsumeToken(T_CLOSE_BRACKET);
    cAddOperation(OP_IF_GOTO);
    int ifP = cReserveInt();
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    cConsumeScope();
    cSetInt(ifP, code->length);

    if(cConsumeTokenIf(T_ELSE)) {
        cAddOperation(OP_GOTO);
        int elseP = cReserveInt();
        cSetInt(ifP, code->length);
        if(cConsumeTokenIf(T_IF)) {
            cIf();
        } else {
            cConsumeToken(T_OPEN_CURVED_BRACKET);
            cConsumeScope();
        }
        cSetInt(elseP, code->length);
    }
}

static void cConsumeBreaks(int start, int address) {
    for(int i = start; i < breakIndex; i++) {
        cSetInt(breaks[i], address);
    }
    breakIndex = start;
}

static void cWhile() {
    int start = code->length;
    cConsumeToken(T_OPEN_BRACKET);
    DataType dt = cExpression();
    if(!dtCompare(dt, dtBool())) {
        cError("while expects a bool not %s", cGetName(dt));
    }
    cConsumeToken(T_CLOSE_BRACKET);
    cAddOperation(OP_IF_GOTO);
    int ifP = cReserveInt();
    int breakStart = breakIndex;
    forWhileStack++;
    int oldContinue = continueAt;
    continueAt = start;
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    cConsumeScope();
    continueAt = oldContinue;
    forWhileStack--;
    cAddOperation(OP_GOTO);
    cAddInt(start);
    cSetInt(ifP, code->length);
    cConsumeBreaks(breakStart, code->length);
}

static void cDeclare(DataType dt) {
    dt = cExtendType(dt);
    cConsumeToken(T_LITERAL);
    const char* var = cReadString();
    if(vsInScope(&vars, var)) {
        cDeclared(var);
    }
    Variable* vp = vsAdd(&vars, var, dt, &structs);
    cConsumeToken(T_SET);
    cReference(vp, 0);
    cStore(vp, cExpression(), "=");
}

static void cAddPreLineChange(int change, const char* name) {
    cConsumeToken(T_LITERAL);
    const char* literal = cReadString();
    Variable v;
    if(vsSearch(&vars, &v, literal)) {
        cNotDeclared(literal);
    }
    cReference(&v, 0);
    cAddPostLineChange(&v, change, name);
}

static void cLineExpression(Token t) {
    switch(t) {
        case T_LITERAL: cLineLiteral(); break;
        case T_INT: cDeclare(dtInt()); break;
        case T_BOOL: cDeclare(dtBool()); break;
        case T_FLOAT: cDeclare(dtFloat()); break;
        case T_INCREMENT: cAddPreLineChange(1, "++"); break;
        case T_DECREMENT: cAddPreLineChange(-1, "--"); break;
        case T_MUL:
            {
                int c = 1;
                while(cConsumeTokenIf(T_MUL)) {
                    c++;
                }
                cConsumeToken(T_LITERAL);
                cLineVariable(cReadString(), c);
                break;
            }
        default: cUnexpectedToken(t);
    }
}

static void cFor() {
    Scope scope;
    vsEnterScope(&vars, &scope);

    cConsumeToken(T_OPEN_BRACKET);
    cLineExpression(cReadTokenAndLine());
    cConsumeToken(T_SEMICOLON);
    int startCheck = code->length;
    DataType dt = cExpression();
    if(!dtCompare(dt, dtBool())) {
        cError("for expects a bool not %s", cGetName(dt));
    }
    cConsumeToken(T_SEMICOLON);
    cAddOperation(OP_IF_GOTO);
    int end = cReserveInt();
    cAddOperation(OP_GOTO);
    int beginBody = cReserveInt();
    int startPerLoop = code->length;
    cLineExpression(cReadTokenAndLine());
    cAddOperation(OP_GOTO);
    cAddInt(startCheck);
    cConsumeToken(T_CLOSE_BRACKET);
    cSetInt(beginBody, code->length);
    int breakStart = breakIndex;
    forWhileStack++;
    int oldContinue = continueAt;
    continueAt = startPerLoop;
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    cConsumeBody();
    continueAt = oldContinue;
    forWhileStack--;
    cAddOperation(OP_GOTO);
    cAddInt(startPerLoop);
    cSetInt(end, code->length);
    cConsumeBreaks(breakStart, code->length);

    vsLeaveScope(&vars, &scope);
}

static void cBreak() {
    if(forWhileStack == 0) {
        cError("break without for or while on line %d", line);
    } else if(breakIndex >= BREAK_BUFFER) {
        cError("too much breaks around line %d", line);
    }
    cAddOperation(OP_GOTO);
    breaks[breakIndex++] = cReserveInt();
    cConsumeToken(T_SEMICOLON);
}

static void cContinue() {
    if(forWhileStack == 0) {
        cError("continue without for or while on line %d", line);
    }
    cAddOperation(OP_GOTO);
    cAddInt(continueAt);
    cConsumeToken(T_SEMICOLON);
}

static void cLine(Token t) {
    hasReturn = false;
    cAddOperation(OP_LINE);
    cAddInt16(line);
    switch(t) {
        case T_OPEN_CURVED_BRACKET: cConsumeScope(); break;
        case T_PRINT: cPrint(); break;
        case T_RETURN: cReturn(); break;
        case T_IF: cIf(); break;
        case T_WHILE: cWhile(); break;
        case T_FOR: cFor(); break;
        case T_BREAK: cBreak(); break;
        case T_CONTINUE: cContinue(); break;
        default: cLineExpression(t); cConsumeToken(T_SEMICOLON);
    }
}

static void cFunctionArgument(Function* f);

static void cFunctionCommaOrEnd(Function* f) {
    if(cConsumeTokenIf(T_CLOSE_BRACKET)) {
        return;
    }
    cConsumeToken(T_COMMA);
    cFunctionArgument(f);
}

static void cFunctionAddArgument(Function* f, DataType dt) {
    dt = cExtendType(dt);
    cConsumeToken(T_LITERAL);
    const char* name = cReadString();
    if(vsInScope(&vars, name)) {
        cDeclared(name);
    }
    vsAdd(&vars, name, dt, &structs);
    if(fAddArgument(f, dt, &structs)) {
        cTooMuchArguments();
    }
    cFunctionCommaOrEnd(f);
}

static void cFunctionArgument(Function* f) {
    Token t = cReadTokenAndLine();
    switch(t) {
        case T_INT: cFunctionAddArgument(f, dtInt()); break;
        case T_FLOAT: cFunctionAddArgument(f, dtFloat()); break;
        case T_BOOL: cFunctionAddArgument(f, dtBool()); break;
        case T_LITERAL:
            {
                const char* structName = cReadString();
                Struct* st = stsSearch(&structs, structName);
                if(st == NULL) {
                    cError("struct %s does not exist");
                }
                cFunctionAddArgument(f, dtStruct(st));
                break;
            }
        default: cUnexpectedToken(t);
    }
}

static void cFunctionArguments(Function* f) {
    cConsumeToken(T_OPEN_BRACKET);
    if(!cConsumeTokenIf(T_CLOSE_BRACKET)) {
        cFunctionArgument(f);
    }
}

static int cReserve(int offset) {
    cAddOperation(OP_RESERVE);
    int p = cReserveInt();
    cAddInt(offset);
    return p;
}

static void cFree(int p, int bytes) {
    cAddOperation(OP_RETURN);
    cAddInt(bytes);
    cSetInt(p, bytes);
}

static void cLinkReturns(int bytes) {
    for(int i = 0; i < returnIndex; i++) {
        cSetInt(returns[i], bytes);
    }
    returnIndex = 0;
}

static void cInnerFunction(Function* f) {
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    int p = cReserve(f->size);
    returnIndex = 0;
    hasReturn = false;
    cConsumeScope();
    if(!dtCompare(returnType, dtVoid()) && !hasReturn) {
        cError("missing return");
    }
    cFree(p, vars.maxAddress);
    cLinkReturns(vars.maxAddress);
}

static bool cForwardFunction(Function* found, Function* f) {
    if(!cConsumeTokenIf(T_SEMICOLON)) {
        return false;
    } else if(found != NULL) {
        cError("function registered twice");
    }
    f->address = -1;
    fsAdd(&functions, f);
    return true;
}

static void cBuildFunction(Function* f, DataType rType) {
    cConsumeToken(T_LITERAL);
    fInit(f, cReadString(), line);
    f->returnType = rType;
    vsReset(&vars);
    cFunctionArguments(f);
}

static void cFunction(DataType rType) {
    Function f;
    cBuildFunction(&f, rType);
    Function* found = fsSearch(&functions, &f);
    if(cForwardFunction(found, &f)) {
        return;
    }
    cAddOperation(OP_LINE);
    cAddInt16(line);
    cAddOperation(OP_GOTO);
    int end = cReserveInt();
    f.address = code->length;
    if(found != NULL) {
        if(found->address == -1) {
            found->address = f.address;
        } else {
            cError("function registered twice");
        }
    } else {
        fsAdd(&functions, &f);
    }
    returnType = rType;
    cInnerFunction(&f);
    cSetInt(end, code->length);
}

static void cStruct() {
    cConsumeToken(T_LITERAL);
    const char* name = cReadString();
    if(stsSearch(&structs, name) != NULL) {
        cError("struct registered twice");
    }
    Struct* st = stsAdd(&structs, name);
    cConsumeToken(T_OPEN_CURVED_BRACKET);
    while(!cConsumeTokenIf(T_CLOSE_CURVED_BRACKET)) {
        Token t = cReadTokenAndLine();
        DataType dt = dtVoid();
        switch(t) {
            case T_INT: dt = dtInt(); break;
            case T_BOOL: dt = dtBool(); break;
            case T_FLOAT: dt = dtFloat(); break;
            default: cUnexpectedToken(t);
        }
        dt = cExtendType(dt);
        cConsumeToken(T_LITERAL);
        const char* name = cReadString();
        stAddVariable(st, name, dt);
        cConsumeToken(T_SEMICOLON);
    }
    cConsumeToken(T_SEMICOLON);
}

static void cGlobalScope(Token t) {
    switch(t) {
        case T_VOID: cFunction(dtVoid()); break;
        case T_INT: cFunction(dtInt()); break;
        case T_BOOL: cFunction(dtBool()); break;
        case T_FLOAT: cFunction(dtFloat()); break;
        case T_STRUCT: cStruct(); break;
        default: cUnexpectedToken(t);
    }
}

static void cCallMain() {
    Function f;
    fInit(&f, "main", line);
    Function* found = fsSearch(&functions, &f);
    if(found != NULL && dtCompare(found->returnType, dtVoid())) {
        cAddOperation(OP_PUSH_INT);
        cAddInt(0);
        cAddOperation(OP_GOSUB);
        cAddInt(found->address);
        cAddInt(found->size);
    }
}

static void cForEachLine() {
    Token t = cReadTokenAndLine();
    while(t != T_END) {
        cGlobalScope(t);
        t = cReadTokenAndLine();
    }
    cCallMain();
}

static void cLinkQueuedFunctions() {
    for(int i = 0; i < functionQueue.entries; i++) {
        Function* f = functionQueue.data + i;
        Function* found = fsSearch(&functions, f);
        if(found == NULL) {
            line = f->line;
            cError("unknown function");
        } else if(!dtCompare(f->returnType, found->returnType)) {
            line = f->line;
            cError("function return type is not %s", cGetName(f->returnType));
        }
        cSetInt(f->address, found->address);
    }
}

static void cAllocAndCompile() {
    forWhileStack = 0;
    breakIndex = 0;
    returnType = dtVoid();
    vsInit(&vars);
    fsInit(&functions);
    fsInit(&functionQueue);
    stsInit(&structs);
    if(!setjmp(errorJump)) {
        cForEachLine();
        cLinkQueuedFunctions();
    }
    stsDelete(&structs);
    fsDelete(&functionQueue);
    fsDelete(&functions);
    vsDelete(&vars);
}

ByteCode* cCompile() {
    error[0] = '\0';
    code = bcInit();
    cAllocAndCompile();
    if(error[0] != '\0') {
        bcDelete(code);
        return NULL;
    }
    return code;
}

const char* cGetError() {
    return error;
}

int cGetLine() {
    return line;
}