#include <SDL2/SDL.h>
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include "util/font.h"
#include "assembler/assembler.h"
#include "util/texteditor.h"
#include "util/hexdump.h"
#include "util/cpustatusui.h"
#include "peripherals/peripheraldata.h"

//Screen dimension constants
const int SCREEN_WIDTH = 1280;
const int SCREEN_HEIGHT = 720;
const int targetFPS = 60;
const int delayNeeded = 1000 / targetFPS;

#define TARGET_CYCLE_TIME_NS 15625  // 15.625 µs per cycle for 64 kHz

//The window we'll be rendering to
SDL_Window *window = NULL;
volatile bool running = true;

//The surface contained by the window
SDL_Renderer *renderer = NULL;

#define biggerFont fonts[0]
#define smallFont fonts[1]
#define smallerFont fonts[2]

#define fontCount 3

BitmapFont fonts[fontCount];

CPU cpu;

SDL_Texture *cpuStatsTexture;

SDL_Texture *cpuStateTexture;

#define codeEditor editors[0]
#define memoryViewer editors[1]
#define editorCount 2
#define activeEditor editors[activeEditorIndex]

int activeEditorIndex = 0;
TextEditor editors[editorCount];

unsigned long frames = 0;
bool cursor = true;

pthread_t cpu_thread;
pthread_mutex_t cpu_mutex = PTHREAD_MUTEX_INITIALIZER;

void msleep(unsigned int milliseconds) {
    struct timespec ts;
    ts.tv_sec = milliseconds / 1000;
    ts.tv_nsec = (milliseconds % 1000) * 1000000;
    nanosleep(&ts, NULL);
}

void *cpu_loop(void *arg) {
    uint64_t last_tick = SDL_GetTicks64();
    while (running) {
        pthread_mutex_lock(&cpu_mutex);

        if (!(cpu.mode & (CPU_MODE_PAUSED | CPU_MODE_HALTED | CPU_MODE_ERROR))) {
            step(&cpu);
        } else {
            pthread_mutex_unlock(&cpu_mutex);
            msleep(10);
        }

        if (cpu.mode & CPU_MODE_SECOND) {
            pthread_mutex_unlock(&cpu_mutex);
            msleep(1000);
            continue;
        } else if (cpu.mode & CPU_MODE_FRAME) {
            uint64_t frame_duration = SDL_GetTicks64() - last_tick;
            if (frame_duration < delayNeeded) {
                pthread_mutex_unlock(&cpu_mutex);
                msleep(delayNeeded - frame_duration);
                continue;
            }
            last_tick = SDL_GetTicks64();
        }
        pthread_mutex_unlock(&cpu_mutex);
    }
    return NULL;
}

char *read_file_as_string(const char *filename) {
    FILE *file = fopen(filename, "rb"); // Open file in binary mode
    if (file == NULL) {
        perror("Error opening file");
        return NULL;
    }

    // Seek to the end of the file to determine size
    fseek(file, 0, SEEK_END);
    long filesize = ftell(file);
    rewind(file); // Go back to the beginning

    // Allocate memory for file content (+1 for null terminator)
    char *buffer = malloc(filesize + 1);
    if (buffer == NULL) {
        perror("Memory allocation failed");
        fclose(file);
        return NULL;
    }

    // Read entire file into buffer
    fread(buffer, 1, filesize, file);
    buffer[filesize] = '\0'; // Null-terminate the string

    fclose(file);
    return buffer; // Caller must free the memory
}

void updateState(bool full) {
    renderVals(&cpu, &smallFont, &smallerFont, renderer, &cpuStatsTexture);
    renderState(&cpu, &biggerFont, renderer, &cpuStateTexture);
    char *dump = hexdump_to_string(cpu.memory, full ? 0 : memoryViewer.cursor_line_offset * 16,
                                   full ? MEM_SIZE : (memoryViewer.cursor_line_offset * 16) +
                                                     (memoryViewer.displayLineCount * 16));
    fill_editor_from_string(&memoryViewer, dump, full ? 0 : memoryViewer.cursor_line_offset, full, renderer);
    free(dump);
}

void compile(bool erase) {
    generate_string(&codeEditor);
    completePass(codeEditor.outputString, &cpu, erase);
    updateState(true);
}


int init() {
    //Initialize SDL
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, NULL);
    SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1");
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
        printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
        return 1;
    }

    //Initialize SDL_ttf
    if (TTF_Init() == -1) {
        printf("SDL_ttf could not initialize! SDL_ttf Error: %s\n", TTF_GetError());
        return 1;
    }

    //Create window
    window = SDL_CreateWindow("RISC-B simulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH,
                              SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
    if (window == NULL) {
        printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
        return 1;
    }
    //Get window surface
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (renderer == NULL) {
        printf("Renderer could not be created SDL_Error: %s\n", SDL_GetError());
        return 1;
    }

    // Create OpenGL context
    SDL_GLContext glContext = SDL_GL_CreateContext(window);
    if (!glContext) {
        fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
        exit(1);
    }

// Use OpenGL context
    SDL_GL_MakeCurrent(window, glContext);  // Make sure OpenGL context is current before any OpenGL rendering

    memset(&audioData, 0, sizeof(AudioData));

    SDL_AudioSpec spec = {0};
    spec.freq = SAMPLE_RATE;
    spec.format = AUDIO_F32SYS;
    spec.channels = 1;
    spec.samples = 4096;
    spec.callback = audio_callback;
    spec.userdata = &audioData;

    SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
    if (dev == 0) {
        printf("Failed to open audio: %s\n", SDL_GetError());
        SDL_Quit();
    }

    SDL_PauseAudioDevice(dev, 0);

    init_seven_segment(&displayA, renderer, 480, 240, 60, 120);
    init_seven_segment(&displayB, renderer, 545, 240, 60, 120);
    init_seven_segment(&displayC, renderer, 610, 240, 60, 120);
    init_seven_segment(&displayD, renderer, 675, 240, 60, 120);

    init_seven_segment(&displayE, renderer, 480, 375, 60, 120);
    init_seven_segment(&displayF, renderer, 545, 375, 60, 120);
    init_seven_segment(&displayG, renderer, 610, 375, 60, 120);
    init_seven_segment(&displayH, renderer, 675, 375, 60, 120);

    init_switches(&switchesA, renderer, 500, 500, 100, 100);
    init_switches(&switchesB, renderer, 625, 500, 100, 100);
    init_switches(&switchesC, renderer, 500, 610, 100, 100);
    init_switches(&switchesD, renderer, 625, 610, 100, 100);


    gpuSurf = SDL_CreateRGBSurfaceWithFormat(0, 160, 160, 32, SDL_PIXELFORMAT_RGBA8888);
    gpuTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, 160, 160);

    SDL_SetRenderTarget(renderer, gpuTex);
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);
    SDL_SetRenderTarget(renderer, NULL);

    SDL_Rect viewport = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
    SDL_RenderSetViewport(renderer, &viewport);

    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    biggerFont = prepText(renderer, 16, "PublicPixel.ttf", 255, 255, 255, 255);
    smallFont = prepText(renderer, 12, "PublicPixel.ttf", 255, 255, 255, 255);
    smallerFont = prepText(renderer, 8, "PublicPixel.ttf", 255, 255, 255, 255);
    init_editor(&codeEditor, &smallFont, 10, 80, renderer, 35, 1000, 48, false);
    init_editor(&memoryViewer, &smallerFont, 738, 80, renderer, 59, MEM_SIZE / 16 + 2, 70, true);
    SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT);
    for (int i = 0; i < editorCount; i++) {
        generate_string_display(&codeEditor, renderer);
    }
    init_cpu(&cpu, renderer);
    compile(true);

    return 0;
}

int render() {
    SDL_SetRenderDrawColor(renderer, 32, 32, 32, 255);
    SDL_RenderClear(renderer);

    for (int i = 0; i < editorCount; i++) {
        editor_render(&editors[i], renderer, &cpu, i, activeEditorIndex == i, cursor);
    }

    SDL_Rect rect2;
    rect2.x = 9;
    rect2.y = 0;
    rect2.w = 0;
    rect2.h = 0;

    if (cpuStatsTexture) {
        SDL_QueryTexture(cpuStatsTexture, NULL, NULL, &rect2.w, &rect2.h);

        SDL_RenderCopy(renderer, cpuStatsTexture, NULL, &rect2);
    }

    rect2.x = 100;
    rect2.y = 46;
    rect2.w = 0;
    rect2.h = 0;

    if (cpuStateTexture) {
        SDL_QueryTexture(cpuStateTexture, NULL, NULL, &rect2.w, &rect2.h);

        SDL_RenderCopy(renderer, cpuStateTexture, NULL, &rect2);
    }

    rect2.x = 530;
    rect2.y = 50;
    rect2.w = 0;
    rect2.h = 0;

    if (gpuSurfDirty) {
        SDL_UpdateTexture(gpuTex, NULL, gpuSurf->pixels, gpuSurf->pitch);
        gpuSurfDirty = false;
    }

    if (gpuTex) {
        SDL_QueryTexture(gpuTex, NULL, NULL, &rect2.w, &rect2.h);

        SDL_RenderCopy(renderer, gpuTex, NULL, &rect2);
    }

    render_seven_segment(&displayA, renderer);
    render_seven_segment(&displayB, renderer);
    render_seven_segment(&displayC, renderer);
    render_seven_segment(&displayD, renderer);

    render_seven_segment(&displayE, renderer);
    render_seven_segment(&displayF, renderer);
    render_seven_segment(&displayG, renderer);
    render_seven_segment(&displayH, renderer);

    render_switches_segment(&switchesA, renderer);
    render_switches_segment(&switchesB, renderer);
    render_switches_segment(&switchesC, renderer);
    render_switches_segment(&switchesD, renderer);

    SDL_RenderPresent(renderer);
    frames++;
    if (!(frames % 60)) {
        cursor = !cursor;
    }
    return 0;
}

SDL_Keycode ConvertKPToNonKP(SDL_Keycode keycode) {
    switch (keycode) {
        case SDLK_KP_0:
            return SDLK_0;
        case SDLK_KP_1:
            return SDLK_1;
        case SDLK_KP_2:
            return SDLK_2;
        case SDLK_KP_3:
            return SDLK_3;
        case SDLK_KP_4:
            return SDLK_4;
        case SDLK_KP_5:
            return SDLK_5;
        case SDLK_KP_6:
            return SDLK_6;
        case SDLK_KP_7:
            return SDLK_7;
        case SDLK_KP_8:
            return SDLK_8;
        case SDLK_KP_9:
            return SDLK_9;
        case SDLK_KP_PERIOD:
            return SDLK_PERIOD;
        case SDLK_KP_COMMA:
            return SDLK_COMMA;
        case SDLK_KP_DIVIDE:
            return SDLK_SLASH;
        case SDLK_KP_MULTIPLY:
            return SDLK_ASTERISK;
        case SDLK_KP_MINUS:
            return SDLK_MINUS;
        case SDLK_KP_PLUS:
            return SDLK_PLUS;
        case SDLK_KP_ENTER:
            return SDLK_RETURN;
        case SDLK_KP_EQUALS:
            return SDLK_EQUALS;
        case SDLK_KP_LEFTPAREN:
            return SDLK_LEFTPAREN;
        case SDLK_KP_RIGHTPAREN:
            return SDLK_RIGHTPAREN;
        case SDLK_KP_LEFTBRACE:
            return SDLK_LEFTBRACKET;
        case SDLK_KP_RIGHTBRACE:
            return SDLK_RIGHTBRACKET;
        case SDLK_KP_TAB:
            return SDLK_TAB;
        case SDLK_KP_BACKSPACE:
            return SDLK_BACKSPACE;
        case SDLK_KP_A:
            return SDLK_a;
        case SDLK_KP_B:
            return SDLK_b;
        case SDLK_KP_C:
            return SDLK_c;
        case SDLK_KP_D:
            return SDLK_d;
        case SDLK_KP_E:
            return SDLK_e;
        case SDLK_KP_F:
            return SDLK_f;
        case SDLK_KP_XOR:
            return SDLK_CARET;
        case SDLK_KP_PERCENT:
            return SDLK_PERCENT;
        case SDLK_KP_LESS:
            return SDLK_LESS;
        case SDLK_KP_GREATER:
            return SDLK_GREATER;
        case SDLK_KP_AMPERSAND:
        case SDLK_KP_DBLAMPERSAND:
            return SDLK_AMPERSAND;  // No direct match, best alternative
        case SDLK_KP_VERTICALBAR:
        case SDLK_KP_DBLVERTICALBAR:
            return SDLK_BACKSLASH;  // No direct match
        case SDLK_KP_COLON:
            return SDLK_COLON;
        case SDLK_KP_HASH:
            return SDLK_HASH;
        case SDLK_KP_SPACE:
            return SDLK_SPACE;
        case SDLK_KP_AT:
            return SDLK_AT;
        case SDLK_KP_EXCLAM:
            return SDLK_EXCLAIM;
        case SDLK_KP_MEMADD:
            return SDLK_PLUS;
        case SDLK_KP_MEMSUBTRACT:
            return SDLK_MINUS;
        case SDLK_KP_MEMMULTIPLY:
            return SDLK_ASTERISK;
        case SDLK_KP_MEMDIVIDE:
            return SDLK_SLASH;
        case SDLK_KP_MEMSTORE:
        case SDLK_KP_MEMRECALL:
        case SDLK_KP_MEMCLEAR:
        case SDLK_KP_PLUSMINUS:
        case SDLK_KP_CLEAR:
        case SDLK_KP_CLEARENTRY:
        case SDLK_KP_BINARY:
        case SDLK_KP_OCTAL:
        case SDLK_KP_DECIMAL:
        case SDLK_KP_HEXADECIMAL:
            return SDLK_UNKNOWN;
        default:
            return keycode; // If it's not a KP key, return it unchanged
    }
}

uint8_t cpuSpeedTemp = 0;

int processEvent(SDL_Event e) {
    if (e.type == SDL_QUIT) { return 0; }
    else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_RESIZED) {
        int newWidth = e.window.data1;
        int newHeight = e.window.data2;

        for (int editorIndex = 0; editorIndex < editorCount; editorIndex++) {
            generate_string_display(&editors[editorIndex], renderer);
        }
        updateState(true);

        // Adjust the viewport to match the new window size;
        SDL_Rect viewport = {0, 0, newWidth, newHeight};
        SDL_RenderSetViewport(renderer, &viewport);
    } else if (e.type == SDL_KEYDOWN) {
        int keySym = ConvertKPToNonKP(e.key.keysym.sym);
        int keyMod = e.key.keysym.mod;
        cursor = true;
        bool moved = false;
        switch (keySym) {
            case SDLK_UP:
                move_cursor_relative(&activeEditor, -1, 0, false, renderer);
                moved = true;
                break;
            case SDLK_PAGEUP:
                move_cursor_relative(&activeEditor, -activeEditor.displayLineCount, -1, true, renderer);
                moved = true;
                break;
            case SDLK_DOWN:
                move_cursor_relative(&activeEditor, 1, 0, false, renderer);
                moved = true;
                break;
            case SDLK_PAGEDOWN:
                move_cursor_relative(&activeEditor, activeEditor.displayLineCount, 0, true, renderer);
                moved = true;
                break;
            case SDLK_LEFT:
                move_cursor_relative(&activeEditor, 0, -1, false, renderer);
                moved = true;
                break;
            case SDLK_HOME:
                if (keyMod & KMOD_CTRL) {
                    move_cursor(&activeEditor, 0, 0, false, renderer);
                    moved = true;
                    break;
                }
                move_cursor(&activeEditor, activeEditor.cursor_line, 0, false, renderer);
                moved = true;
                break;
            case SDLK_RIGHT:
                move_cursor_relative(&activeEditor, 0, 1, false, renderer);
                moved = true;
                break;
            case SDLK_END:
                int lineLen = strlen(activeEditor.lines[activeEditor.cursor_line].text);
                if (keyMod & KMOD_CTRL) {
                    move_cursor(&activeEditor, activeEditor.maxLines - 1, lineLen, false, renderer);
                    moved = true;
                    break;
                }
                move_cursor(&activeEditor, activeEditor.cursor_line, lineLen, false, renderer);
                moved = true;
                break;
            case SDLK_d:
                if (keyMod & KMOD_CTRL && !activeEditor.readOnly) {
                    insert_line_rel(&activeEditor, renderer);
                    memset(activeEditor.lines[activeEditor.cursor_line].text, 0, activeEditor.max_line_width);
                    strcpy(activeEditor.lines[activeEditor.cursor_line].text,
                           activeEditor.lines[activeEditor.cursor_line - 1].text);
                    activeEditor.lines[activeEditor.cursor_line].active = 1;
                    move_cursor_relative(&activeEditor, 0, activeEditor.max_line_width, false, renderer);
                    generate_string_display(&activeEditor, renderer);
                }
                break;
            case SDLK_F9:
                cpu.mode ^= CPU_MODE_LOOP;
                updateState(false);
                return 1;
            case SDLK_F8:
                if (cpu.mode & CPU_MODE_STEP) {
                    cpu.mode &= ~CPU_MODE_STEP;
                    cpu.mode |= CPU_MODE_SECOND;
                } else if (cpu.mode & CPU_MODE_SECOND) {
                    cpu.mode &= ~CPU_MODE_SECOND;
                    cpu.mode |= CPU_MODE_FRAME;
                } else if (cpu.mode & CPU_MODE_FRAME) {
                    //cpu.mode &= ~CPU_MODE_FRAME;
                    // No specific mode set, defaults to none
                    cpu.mode |= CPU_MODE_STEP; // Restart cycle
                } else {
                    cpu.mode |= CPU_MODE_STEP; // Restart cycle
                }
                updateState(false);
                return 1;
            case SDLK_F5:
                compile(!(keyMod & KMOD_CTRL));
            case SDLK_F7:
                if (cpu.mode & (CPU_MODE_HALTED | CPU_MODE_ERROR) || (keyMod & KMOD_SHIFT)) {
                    cpu.pc = 0;
                }
                cpu.mode &= ~(CPU_MODE_HALTED | CPU_MODE_PAUSED | CPU_MODE_ERROR);
                updateState(false);
                break;

            case SDLK_ESCAPE:
                cpu.mode |= CPU_MODE_PAUSED;
                if (keyMod & (KMOD_CTRL | KMOD_SHIFT)) {
                    cpu.mode |= CPU_MODE_HALTED;
                    cpu.pc = 0;
                    init_cpu(&cpu, renderer);
                }
                updateState(false);
                break;

            case SDLK_s:
                if (keyMod & KMOD_CTRL) {
                    FILE *fptr;
                    char fname[20];
                    snprintf(fname, sizeof(fname), "riscb%lu.bsm", time(NULL));
                    fptr = fopen(fname, "wb");
                    generate_string(&editors[0]);
                    fputs(editors[0].outputString, fptr);
                    fclose(fptr);
                    if (keyMod & KMOD_SHIFT) {
                        snprintf(fname, sizeof(fname), "riscb%lu.mem", time(NULL));
                        fptr = fopen(fname, "wb");
                        fwrite(cpu.memory, sizeof(cpu.memory[0]), MEM_SIZE, fptr);
                        fclose(fptr);
                    }
                    return 1;
                }
                break;

            case SDLK_l:
                if (keyMod & KMOD_CTRL) {
                    FILE *fptr;
                    char fname[20];
                    sscanf(editors[0].lines[editors[0].cursor_line].text, "%s", fname);
                    toLowerCase(fname);
                    strcat(fname, ".bsm");
                    fptr = fopen(fname, "rb");
                    if (fptr) {
                        char *prog = read_file_as_string(fname);
                        toUpperCase(prog);
                        fill_editor_from_string(&editors[0], prog, 0, true, renderer);
                        free(prog);
                        fclose(fptr);
                    }
                    return 1;
                }
                break;
            case SDLK_BACKSPACE:
                if (!activeEditor.readOnly) {
                    remove_character(&activeEditor, false, renderer);
                    moved = true;
                }
                break;
            case SDLK_DELETE:
                if (!activeEditor.readOnly) {
                    remove_character(&activeEditor, true, renderer);
                    moved = true;
                }
                break;
            case SDLK_RETURN:
            case SDLK_RETURN2:
                if (keyMod & KMOD_CTRL && activeEditorIndex == 0) {
                    compile(!(keyMod & KMOD_SHIFT));
                    break;
                }
                if (!activeEditor.readOnly) {
                    insert_line_rel(&activeEditor, renderer);
                    moved = true;
                }
                break;
            case SDLK_TAB:
                activeEditorIndex++;
                if (activeEditorIndex >= editorCount) {
                    activeEditorIndex = 0;
                }
                activeEditor = editors[activeEditorIndex];
                break;
            default:
                break;
        }
        if (moved && &activeEditor == &memoryViewer) {
            updateState(false);
        }
    } else if (e.type == SDL_TEXTINPUT) {
        for (int i = 0; e.text.text[i] != '\0'; i++) {  // Iterate over the input string
            char keySym = e.text.text[i];

            if (!activeEditor.readOnly && activeEditor.cursor_pos <= activeEditor.max_line_width) {
                if (keySym >= 32 && keySym <= 126) {  // Printable ASCII range
                    if (keySym > 0x60 && keySym < 0x7b) {  // Convert lowercase to uppercase
                        keySym -= 0x20;
                    }
                    if (activeEditor.cursor_pos < activeEditor.max_line_width) {
                        insert_character(&activeEditor, keySym, renderer);
                    }
                }
            }
        }
    } else if (e.type == SDL_MOUSEBUTTONDOWN) {
        SDL_Rect mouse;
        mouse.w = 1;
        mouse.h = 1;
        SDL_GetMouseState(&mouse.x, &mouse.y);
        toggle_switch(mouse, &switchesA);
        toggle_switch(mouse, &switchesB);
        toggle_switch(mouse, &switchesC);
        toggle_switch(mouse, &switchesD);
    }
    return 1;
}

int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[]) {
    int status = init();
    if (status) {
        return status;
    }

    //Hack to get window to stay up
    SDL_Event e;
    Uint64 start;
    Uint64 end;
    pthread_create(&cpu_thread, NULL, cpu_loop, NULL);
    while (running) {
        start = SDL_GetTicks64();
        pthread_mutex_lock(&cpu_mutex);
        while (SDL_PollEvent(&e)) {
            running = processEvent(e);
        }

        status = render();
        if (status) {
            return status;
        }

        if (!(cpu.mode & (CPU_MODE_HALTED | CPU_MODE_PAUSED | CPU_MODE_ERROR))) {
            if (cpu.mode & CPU_MODE_SECOND) {
                if (!(frames % 60)) {
                    step(&cpu);
                    updateState(false);
                }
            } else {
                step(&cpu);
                updateState(false);
            }

            if (cpu.mode & CPU_MODE_STEP) {
                cpu.mode |= CPU_MODE_PAUSED;
                updateState(false);
            }
        }
        pthread_mutex_unlock(&cpu_mutex);
        end = SDL_GetTicks64();
        const unsigned long timeNeeded = end - start;
        if (timeNeeded < delayNeeded) {
            SDL_Delay(delayNeeded - timeNeeded);
        }
    }

    for (uint8_t i = 0; i < editorCount; i++) {
        destroy_editor(&editors[i]);
    }

    for (uint8_t i = 0; i < fontCount; i++) {
        destroyFont(&fonts[i]);
    }

    puts(SDL_GetError());

    if (cpuStatsTexture) SDL_DestroyTexture(cpuStatsTexture);
    if (cpuStateTexture) SDL_DestroyTexture(cpuStateTexture);

    destroy_switches_segment(&switchesA);
    destroy_switches_segment(&switchesB);
    destroy_switches_segment(&switchesC);
    destroy_switches_segment(&switchesD);

    destroy_seven_segment(&displayA);
    destroy_seven_segment(&displayB);
    destroy_seven_segment(&displayC);
    destroy_seven_segment(&displayD);

    destroy_seven_segment(&displayE);
    destroy_seven_segment(&displayF);
    destroy_seven_segment(&displayG);
    destroy_seven_segment(&displayH);

    pthread_join(cpu_thread, NULL);
    if (renderer) SDL_DestroyRenderer(renderer);
    if (window) SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}