#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <locale.h>
#include <time.h>

extern size_t urcl_main();


#include <SDL2/SDL.h>
#include <stdlib.h>
#include <stdio.h>
//#include "urcl.h"
#include <unistd.h>

#define URCL_PIXEL_SIZE 4
#define URCL_SCREEN_WIDTH 128
#define URCL_SCREEN_HEIGHT 128
#define URCL_COLOR_RED   0b1111100000000000
#define URCL_COLOR_GREEN 0b0000011111100000
#define URCL_COLOR_BLUE  0b0000000000011111
#define URCL_COLOR_WHITE URCL_COLOR_RED | URCL_COLOR_GREEN | URCL_COLOR_BLUE

static SDL_Window* window = NULL;
static SDL_Renderer* renderer = NULL;
static int global_x_coord = 0;
static int global_y_coord = 0;
static int global_color = 0;
static int global_sleep_time_deciseconds = 10;
static int global_current_buffer_state = 0;
static int global_graphics_are_init = 0;
static int global_port_supported_check = 0;
static int* global_data_pointer = 0;
static SDL_Cursor *global_cursor;
static int global_mouse_x = 0;
static int global_mouse_y = 0;
static int global_mouse_button_state = 0;


const int SUPPORTED_PORT_NUMBERS[] = {1, 2, 5, 8, 9, 10, 11, 16, 17, 19, 20, 24, 25, 26, 27, 28, 29, 32, 33, 40, 44};

void urcl_io_init() {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "could not initialize sdl2: %s\n", SDL_GetError());
    }

    SDL_CreateWindowAndRenderer(URCL_SCREEN_WIDTH * URCL_PIXEL_SIZE, URCL_SCREEN_HEIGHT * URCL_PIXEL_SIZE, 0, &window, &renderer);
    if (window == NULL) {
        fprintf(stderr, "could not create window: %s\n", SDL_GetError());
    }

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);
    global_cursor = SDL_GetCursor();
}

void urcl_io_close() {
    SDL_DestroyWindow(window);
    SDL_Quit();
}

void poll_for_events() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if(event.type == SDL_QUIT) {
            exit(0);
        } else if(event.type == SDL_MOUSEBUTTONDOWN) {
            if(event.button.button == SDL_BUTTON_LEFT) {
                global_mouse_button_state |= 1;
            } else if(event.button.button == SDL_BUTTON_RIGHT) {
                global_mouse_button_state |= 2;
            } else {
                global_mouse_button_state |= 4;
            }
        }
        else if(event.type == SDL_MOUSEBUTTONUP) {
            global_mouse_button_state = 0;
        }
        
    }
}
// GENERAL

// 1
void urcl_port_text_out(int symbol) {
    putchar(symbol);
}
int urcl_port_text_in() {
    return getchar();
}

// 2
void urcl_port_numb_out(int number) {
    printf("%d", number);
}
int urcl_port_numb_in() {
    int result;
    scanf("%d", &result);
    return result;
}

// 5
void urcl_port_supported_out(int port) {
    global_port_supported_check = port;
}
int urcl_port_supported_in() {
    
    for(int i = 0; i < 21; i++) {
        if(SUPPORTED_PORT_NUMBERS[i] == global_port_supported_check) {
            return 0;
        }
    }
    return global_port_supported_check;
}

// GRAPHICS

// 8
void urcl_port_x_out(int x) {
    global_x_coord = x;
}
int urcl_port_x_in() {
    return URCL_SCREEN_WIDTH;
}

// 9
void urcl_port_y_out(int y) {
    global_y_coord = y;
}
int urcl_port_y_in() {
    return URCL_SCREEN_HEIGHT;
}

// 10
void urcl_port_color_out(int color) {
    global_color = color;
    int alpha = (color & 0b0000000000000000) >> 24; // Unused for now
    int red = (color &   0b1111100000000000) >> 11;
    int green = (color & 0b0000011111100000) >> 5;
    int blue = color &   0b0000000000011111;
    SDL_Rect rect = {global_x_coord * URCL_PIXEL_SIZE, global_y_coord * URCL_PIXEL_SIZE, URCL_PIXEL_SIZE, URCL_PIXEL_SIZE};
    SDL_SetRenderDrawColor(renderer, red << 3, green << 2, blue << 3, 255);
    SDL_RenderFillRect(renderer, &rect);
}
int urcl_port_color_in() {
    return 0;
}

// 11
void urcl_port_buffer_out(int buffer_state) {

    if(global_graphics_are_init == 0) {
        urcl_io_init();
        global_graphics_are_init = 1;
    }
    if(buffer_state == 0) {
        SDL_RenderPresent(renderer);
        SDL_Rect rect = {0, 0, URCL_SCREEN_WIDTH * URCL_PIXEL_SIZE, URCL_SCREEN_HEIGHT * URCL_PIXEL_SIZE};
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderFillRect(renderer, &rect);
    }
}
int urcl_port_buffer_in() {
    return global_current_buffer_state;
}

// 16
void urcl_port_ascii8_out(int symbol) {putchar(symbol);}
int urcl_port_ascii8_in() {return getchar();}
// 17
void urcl_port_char5_out(int symbol) {putchar(symbol);}
int urcl_port_char5_in() {return getchar();}
// 18
void urcl_port_char6_out(int symbol) {putchar(symbol);}
int urcl_port_char6_in() {return getchar();}
// 19
void urcl_port_char7_out(int symbol) {putchar(symbol);}
int urcl_port_char7_in() {return getchar();}
// 20
void urcl_port_utf8_out(int symbol) {putchar(symbol);}
int urcl_port_utf8_in() {return getchar();}

// NUMBERS

// 24
void urcl_port_int_out(int number) {
    printf("%d", number);
};
int urcl_port_int_in() {
    int result;
    scanf("%d", &result);
    return result;
};
// 25
void urcl_port_uint_out(int number) {
    printf("%i", number);
};
int urcl_port_uint_in() {
    int result;
    scanf("%d", &result);
    return result;
};
// 26
void urcl_port_bin_out(int number);
int urcl_port_bin_in();
// 27
void urcl_port_hex_out(int number) {
    printf("%x", number);
}
int urcl_port_hex_in() {
    int result;
    scanf("%x", &result);
    return result;
}
// 28
void urcl_port_float_out(int number) {
    printf("%f", (float*)number);
}
int urcl_port_float_in() {
    float result;
    scanf("%f", &result);
    return (int)result;
};
// 29
void urcl_port_fixed_out(int number);
int urcl_port_fixed_in();

// STORAGE

// 32
void urcl_port_addr_out(int address) {
    global_data_pointer = address;
}
int urcl_port_addr_in() {
    return (int)global_data_pointer;
}
// 33
void urcl_port_bus_out(int data) {
    *global_data_pointer = data;
};
int urcl_port_bus_in() {
    return *global_data_pointer;
};

// MISC

// 40
void urcl_port_rng_out(int unused) {
    return;
};
int urcl_port_rng_in() {
    return rand();
};
// 44
void urcl_port_wait_out(int deciseconds) {
    global_sleep_time_deciseconds = deciseconds;
}
int urcl_port_wait_in() {
    SDL_Delay((double)global_sleep_time_deciseconds * 100.0);
    return 0;
}

// CUSTOM / BRAM

//68
void urcl_port_mouse_x_out(int x) {
    global_mouse_x = x;
    SDL_WarpMouseInWindow(window, global_mouse_x * URCL_PIXEL_SIZE, global_mouse_y * URCL_PIXEL_SIZE);
    return;
}
int urcl_port_mouse_x_in() {
    int x, y;
    SDL_GetMouseState(&x, &y);
    global_mouse_x = x / URCL_PIXEL_SIZE;
    return global_mouse_x;
}

// 69 (nice)
void urcl_port_mouse_y_out(int y) {
    global_mouse_y = y;
    SDL_WarpMouseInWindow(window, global_mouse_x * URCL_PIXEL_SIZE, global_mouse_y * URCL_PIXEL_SIZE);
    return;
}
int urcl_port_mouse_y_in() {
    int x, y;
    SDL_GetMouseState(&x, &y);
    global_mouse_y = y / URCL_PIXEL_SIZE;
    return global_mouse_y;
}

// 73
void urcl_port_mouse_buttons_out(int unused) {
    return;
}
int urcl_port_mouse_buttons_in() {
    poll_for_events();
    return global_mouse_button_state;
}

void urcl_out(long port, long value) {
    switch(port) {
        case 1: urcl_port_text_out(value); break;
        case 2: urcl_port_numb_out(value); break;
        case 5: urcl_port_supported_out(value); break;
        case 8: urcl_port_x_out(value); break;
        case 9: urcl_port_y_out(value); break;
        case 10: urcl_port_color_out(value); break;
        case 11: urcl_port_buffer_out(value); break;
        case 16: urcl_port_ascii8_out(value); break;
        case 17: urcl_port_char5_out(value); break;
        case 18: urcl_port_char6_out(value); break;
        case 19: urcl_port_char7_out(value); break;
        case 20: urcl_port_utf8_out(value); break;
        case 24: urcl_port_int_out(value); break;
        case 25: urcl_port_uint_out(value); break;
        case 27: urcl_port_hex_out(value); break;
        case 28: urcl_port_float_out(value); break;
        case 32: urcl_port_addr_out(value); break;
        case 33: urcl_port_bus_out(value); break;
        case 40: urcl_port_rng_out(value); break;
        case 44: urcl_port_wait_out(value); break;
        case 68: urcl_port_mouse_x_out(value); break;
        case 69: urcl_port_mouse_y_out(value); break;
        case 73: urcl_port_mouse_buttons_out(value); break;
        printf("\n\x1b[1;33mW:\x1b[0m unknown port %%%lu was written to with %lu\n", port, value);
        default: break;
    }
}

int urcl_in(long port) {
    switch(port) {
        case 1: return urcl_port_text_in();
        case 2: return urcl_port_numb_in();
        case 5: return urcl_port_supported_in();
        case 8: return urcl_port_x_in();
        case 9: return urcl_port_y_in();
        case 10: return urcl_port_color_in();
        case 11: return urcl_port_buffer_in();
        case 16: return urcl_port_ascii8_in();
        case 17: return urcl_port_char5_in();
        case 18: return urcl_port_char6_in();
        case 19: return urcl_port_char7_in();
        case 20: return urcl_port_utf8_in();
        case 24: return urcl_port_int_in();
        case 25: return urcl_port_uint_in();
        case 27: return urcl_port_hex_in();
        case 28: return urcl_port_float_in();
        case 32: return urcl_port_addr_in();
        case 33: return urcl_port_bus_in();
        case 40: return urcl_port_rng_in();
        case 44: return urcl_port_wait_in();
        case 68: return urcl_port_mouse_x_in();
        case 69: return urcl_port_mouse_y_in();
        case 73: return urcl_port_mouse_buttons_in();
        printf("\n\x1b[1;33mW:\x1b[0m unknown port %%%lu was read\n", port);
        default: break;
    }
}

int main() {
    setlocale(LC_ALL, "");
    struct timespec start, end;
    clock_gettime(CLOCK_REALTIME, &start);

    size_t inst = urcl_main();

    clock_gettime(CLOCK_REALTIME, &end);

    double time = (double)(end.tv_sec - start.tv_sec) + (double)(end.tv_nsec - start.tv_nsec) * 1e-9;

    printf("\n\x1b[1;32mI:\x1b[0m ran %'lu instructions in %'.1fs (%'.0f Hz)\n", inst, time, ((double) inst) / time);
    return 0;
}
