#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "Compiler.h"
#include "Script.h"
#include "Tokenizer.h"

static int doneTests = 0;
static int allTests = 0;
static char path[PATH_MAX] = {'\0'};
static int pathIndex = 0;

#define TEST_BUFFER_LENGTH (1024 * 2)
static char testBuffer[TEST_BUFFER_LENGTH];
static int testBufferIndex = 0;

static void tsPrintToBuffer(const char* format, ...) {
    va_list args;
    va_start(args, format);
    int leftBytes = TEST_BUFFER_LENGTH - testBufferIndex;
    testBufferIndex += vsnprintf(testBuffer + testBufferIndex, leftBytes, format, args);
    if(testBufferIndex > TEST_BUFFER_LENGTH) {
        testBufferIndex = TEST_BUFFER_LENGTH;
    }
    va_end(args);
}

static bool tsPrinter(Object* o) {
    if(testBufferIndex >= TEST_BUFFER_LENGTH) {
        return true;
    }
    switch(o->type) {
        case OT_INT: tsPrintToBuffer("%d\n", o->data.intValue); return false;
        case OT_FLOAT: tsPrintToBuffer("%.2f\n", o->data.floatValue); return false;
        case OT_NULL: tsPrintToBuffer("null\n"); return false;
        case OT_BOOL: tsPrintToBuffer(o->data.intValue ? "true\n" : "false\n"); return false;
    }
    return true;
}

static void tsAppend(const char* s) {
    for(int i = 0; pathIndex < (PATH_MAX - 1) && s[i] != '\0'; i++) {
        path[pathIndex++] = s[i];
    }
    path[pathIndex] = '\0';
}

static int tsEnter(const char* s) {
    int marker = pathIndex;
    tsAppend("/");
    tsAppend(s);
    return marker;
}

static void tsReset(int marker) {
    path[marker] = '\0';
    pathIndex = marker;
}

static void tsCompareResults(FILE* file) {
    for(int i = 0; i < testBufferIndex; i++) {
        char a = fgetc(file);
        char b = testBuffer[i];
        if(a != b) {
            printf("error in '%s': expected %c, got:\n%s", path, a, testBuffer + i);
            return;
        }
    }
    if(fgetc(file) != EOF) {
        printf("error in '%s': no full read\n", path);
        return;
    }
    doneTests++;
}

static void tsCheckScript(Script* sc) {
    testBufferIndex = 0;
    sRun(sc);

    tsAppend(".out");
    FILE* file = fopen(path, "r");
    if(file == NULL) {
        printf("cannot open result file '%s'\n", path);
        return;
    }
    tsCompareResults(file);
    fclose(file);
}

static void tsCheckFile() {
    allTests++;
    if(tTokenize(path)) {
        puts(path);
        puts(tGetError());
        return;
    }
    ByteCode* bc = cCompile();
    if(bc == NULL) {
        puts(path);
        puts(cGetError());
        return;
    }
    Script* sc = sInit(bc);
    tsCheckScript(sc);
    sDelete(sc);
}

static void tsScanDirectory() {
    DIR* dir = opendir(path);
    if(dir == NULL) {
        printf("cannot open '%s': %s\n", path, strerror(errno));
        return;
    }
    while(true) {
        struct dirent* e = readdir(dir);
        if(e == NULL) {
            return;
        } else if(strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) {
            continue;
        } else if(e->d_type == DT_DIR) {
            int marker = tsEnter(e->d_name);
            tsScanDirectory();
            tsReset(marker);
        } else if(e->d_type == DT_REG && strchr(e->d_name, '.') == NULL) {
            int marker = tsEnter(e->d_name);
            tsCheckFile();
            tsReset(marker);
        }
    }
    if(closedir(dir)) {
        printf("cannot close '%s': %s\n", path, strerror(errno));
    }
}

void tsStart(const char* path) {
    sSetPrinter(tsPrinter);
    doneTests = 0;
    allTests = 0;
    tsAppend(path);
    tsScanDirectory();
    printf("%d / %d tests succeeded\n", doneTests, allTests);
}