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

#include "DataType.h"

#define ARRAY_NAME 256

static int typeNameIndex = 0;
static int typeNameSwap = 0;
static char typeName[2][ARRAY_NAME];

static void dtAppend(const char* s) {
    int index = 0;
    while(typeNameIndex < (ARRAY_NAME - 1) && s[index] != '\0') {
        typeName[typeNameSwap][typeNameIndex] = s[index];
        index++;
        typeNameIndex++;
    }
    typeName[typeNameSwap][typeNameIndex] = '\0';
}

const char* dtGetName(Structs* sts, DataType dt) {
    typeNameSwap = !typeNameSwap;
    typeNameIndex = 0;
    switch(dt.type) {
        case DT_INT: dtAppend("int"); break;
        case DT_FLOAT: dtAppend("float"); break;
        case DT_BOOL: dtAppend("bool"); break;
        case DT_STRUCT: dtAppend(sts->data[dt.structId].name); break;
        case DT_VOID: dtAppend("void"); break;
        default: dtAppend("unknown");
    }
    for(unsigned int i = 0; i < dt.pointers; i++) {
        dtAppend("*");
    }
    return typeName[typeNameSwap];
}

int dtGetSize(DataType dt, Structs* sts) {
    if(dt.pointers > 0) {
        return sizeof(Pointer);
    }
    switch(dt.type) {
        case DT_INT: return sizeof(int);
        case DT_FLOAT: return sizeof(float);
        case DT_BOOL: return sizeof(bool);
        case DT_STRUCT:
            {
                int size = 0;
                Struct* st = sts->data + dt.structId;
                for(int i = 0; i < st->amount; i++) {
                    size += dtGetSize(st->vars[i].type, sts);
                }
                return size;
            }
        default: return 0;
    }
}

DataType dtInt() {
    DataType dt = {DT_INT, 0, 0};
    return dt;
}

DataType dtFloat() {
    DataType dt = {DT_FLOAT, 0, 0};
    return dt;
}

DataType dtBool() {
    DataType dt = {DT_BOOL, 0, 0};
    return dt;
}

DataType dtVoid() {
    DataType dt = {DT_VOID, 0, 0};
    return dt;
}

DataType dtStruct(Struct* st) {
    DataType dt = {DT_STRUCT, 0, st->id};
    return dt;
}

DataType dtReference(DataType dt) {
    dt.pointers--;
    return dt;
}

bool dtDereference(DataType* dt) {
    if(dt->pointers == 15) {
        return true;
    }
    dt->pointers++;
    return false;
}

bool dtCompare(DataType a, DataType b) {
    return a.pointers == b.pointers && a.structId == b.structId &&
           a.type == b.type;
}

bool dtIsInt(DataType dt) {
    return dtCompare(dt, dtInt());
}

bool dtIsFloat(DataType dt) {
    return dtCompare(dt, dtFloat());
}

bool dtIsBool(DataType dt) {
    return dtCompare(dt, dtBool());
}

bool dtIsVoid(DataType dt) {
    return dtCompare(dt, dtVoid());
}

bool dtIsPointer(DataType dt) {
    return dt.pointers > 0;
}

bool dtIsVariable(DataType dt) {
    return dt.type & 8;
}

Struct* dtGetStruct(Structs* sts, DataType dt) {
    if(dt.type != DT_STRUCT) {
        return NULL;
    }
    return sts->data + dt.structId;
}

DataType dtToVariable(DataType dt) {
    dt.type |= 8;
    return dt;
}

bool dtRemoveVariable(DataType* dt) {
    if(dtIsVariable(*dt)) {
        dt->type &= 7;
        return true;
    }
    return false;
}

void stAddVariable(Struct* st, const char* name, DataType type) {
    int index = st->amount;
    st->amount++;
    st->vars = realloc(st->vars, sizeof(StructVariable) * st->amount);
    st->vars[index].name = name;
    st->vars[index].type = type;
}

void stsInit(Structs* sts) {
    sts->capacity = 4;
    sts->entries = 0;
    sts->data = malloc(sizeof(Struct) * sts->capacity);
}

void stsDelete(Structs* sts) {
    for(int i = 0; i < sts->entries; i++) {
        free(sts->data[i].vars);
    }
    free(sts->data);
}

Struct* stsSearch(Structs* sts, const char* name) {
    for(int i = 0; i < sts->entries; i++) {
        if(strcmp(sts->data[i].name, name) == 0) {
            return sts->data + i;
        }
    }
    return NULL;
}

Struct* stsAdd(Structs* sts, const char* name) {
    if(sts->entries >= sts->capacity) {
        sts->capacity *= 2;
        sts->data = realloc(sts->data, sizeof(Struct) * sts->capacity);
    }
    int index = sts->entries++;
    sts->data[index].id = index;
    sts->data[index].amount = 0;
    sts->data[index].name = name;
    sts->data[index].vars = NULL;
    return sts->data + index;
}