#include <ui.h>
#include <emu.h>
#include <ppu.h>
#include <gamepad.h>
#include <bus.h>
#include <audio.h>
#include <string.h>
#include <lcd.h>
#include <cpu.h>
#include <cart.h>
#include <state.h>
#include <debug.h>

#include <SDL.h>
#include <SDL_ttf.h>

SDL_Window *sdlWindow;
SDL_Renderer *sdlRenderer;
SDL_Texture *sdlTexture;
SDL_Surface *screen;

SDL_Window *sdlVramWindow;
SDL_Renderer *sdlVramRenderer;
SDL_Texture *sdlVramTexture;
SDL_Surface *vramScreen;

SDL_Window *sdlAudioWindow;
SDL_Renderer *sdlAudioRenderer;
SDL_Texture *sdlAudioTexture;
SDL_Surface *audioScreen;
TTF_Font *sans;

static int scale = 4;
static int screenWidth;
static int screenHeight;

void ui_init(){
    screenWidth = XRES*scale;
    screenHeight = YRES*scale;
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
    printf("SDL INIT\n");
    TTF_Init();
    printf("TTF INIT\n");

    //SDL_JoystickEventState(SDL_ENABLE);
    //SDL_JoystickOpen(0);
    SDL_GameControllerOpen(0);

    //char buffer[1024];
    //strcpy(buffer, emu_get_context()->app_path);
    //*strrchr(buffer, '\\') = 0;
    //strcat(buffer,"/Sans.ttf");
    sans = TTF_OpenFont("Sans.ttf", 24);
    if ( !sans ) {
       printf("Failed to load font: %s\n", TTF_GetError());
    }
    SDL_CreateWindowAndRenderer(screenWidth, screenHeight, 0, &sdlWindow, &sdlRenderer);

    screen = SDL_CreateRGBSurface(0, screenWidth,
                                    screenHeight, 32,
                                    0x00FF0000,
                                    0x0000FF00,
                                    0x000000FF,
                                    0xFF000000);
    sdlTexture = SDL_CreateTexture(sdlRenderer,
                                            SDL_PIXELFORMAT_ARGB8888,
                                            SDL_TEXTUREACCESS_STREAMING,
                                            screenWidth,
                                            screenHeight);

    SDL_CreateWindowAndRenderer(16 * 9 * scale, 24 * 9 * scale, SDL_WINDOW_HIDDEN, &sdlVramWindow, &sdlVramRenderer);
    vramScreen = SDL_CreateRGBSurface(0, (16 * 9 * scale),
                                            (24 * 9 * scale), 32,
                                            0x00FF0000,
                                            0x0000FF00,
                                            0x000000FF,
                                            0xFF000000);
    sdlVramTexture = SDL_CreateTexture(sdlVramRenderer,
                                            SDL_PIXELFORMAT_ARGB8888,
                                            SDL_TEXTUREACCESS_STREAMING,
                                            (16 * 9 * scale),
                                            (24 * 9 * scale));

    SDL_CreateWindowAndRenderer(384, 730, SDL_WINDOW_HIDDEN, &sdlAudioWindow, &sdlAudioRenderer);
    audioScreen = SDL_CreateRGBSurface(0, 384,
                                            730, 32,
                                            0x00FF0000,
                                            0x0000FF00,
                                            0x000000FF,
                                            0xFF000000);
    sdlAudioTexture = SDL_CreateTexture(sdlAudioRenderer,
                                            SDL_PIXELFORMAT_ARGB8888,
                                            SDL_TEXTUREACCESS_STREAMING,
                                            384,
                                            730);
}

static unsigned long tile_colors[4] = {0xFFFFFFFF, 0xFFAAAAAA, 0xFF555555, 0xFF000000};

void display_tile(SDL_Surface *surface, u16 startLocation, u16 tileNum, int x, int y) {
    SDL_Rect rc;
    for(int tileY = 0; tileY < 16; tileY += 2) {
        u8 b1 = bus_read(startLocation+(tileNum * 16) + tileY);
        u8 b2 = bus_read(startLocation+(tileNum * 16) + tileY + 1);

        for (int bit=7; bit >= 0; bit--) {
            u8 hi = !!(b1 & (1 << bit)) << 1;
            u8 lo = !!(b2 & (1 << bit));

            u8 color = hi | lo;
            rc.x = x + ((7 - bit) * scale);
            rc.y = y + (tileY/2 * scale);
            rc.w = scale;
            rc.h = scale;
            SDL_FillRect(surface, &rc, tile_colors[color]);
        }
    }
}

void update_vram_window() {
    int xDraw = 0;
    int yDraw = 0;
    int tileNum = 0;

    SDL_Rect rc;
    rc.x = 0;
    rc.y = 0;
    rc.w = vramScreen->w;
    rc.h = vramScreen->h;
    SDL_FillRect(vramScreen, &rc, 0xFF111111);

    u16 addr = 0x8000;

    //384 tiles, 24 x 16

    for(int y = 0; y < 24; y++) {
        for(int x = 0; x < 16; x++) {
            display_tile(vramScreen, addr, tileNum, xDraw + (x * scale), yDraw + (y * scale));
            xDraw += (8 * scale);
            tileNum++;
        }
        yDraw += (8 * scale);
        xDraw = 0;
    }

    SDL_UpdateTexture(sdlVramTexture, NULL, vramScreen->pixels, vramScreen->pitch);
    SDL_RenderClear(sdlVramRenderer);
    SDL_RenderCopy(sdlVramRenderer, sdlVramTexture, NULL, NULL);
    SDL_RenderPresent(sdlVramRenderer);
}

void update_audio_window() {
    int xDraw = 0;
    int yDraw = 0;
    int tileNum = 0;

    SDL_Rect rc;
    rc.x = 0;
    rc.y = 0;
    rc.w = audioScreen->w;
    rc.h = audioScreen->h;
    SDL_FillRect(audioScreen, &rc, 0xFF111111);
    SDL_UpdateTexture(sdlAudioTexture, NULL, audioScreen->pixels, audioScreen->pitch);
    SDL_RenderClear(sdlAudioRenderer);
    SDL_RenderCopy(sdlAudioRenderer, sdlAudioTexture, NULL, NULL);
    SDL_Color text_color = {255,255,255};
    int yOffset = 70;
    int lastY = (yOffset) - (audio_get_context()->sq1_history[audio_get_context()->sq1_index] * 50);
    int xOffset = 0;
    SDL_Surface* message = TTF_RenderText_Solid(sans, "Square 1", text_color);
    SDL_Texture* messageTexture = SDL_CreateTextureFromSurface(sdlAudioRenderer, message);
    SDL_Rect messageRect;
    messageRect.x = xOffset;
    messageRect.y = yOffset-70;
    messageRect.w = 50;
    messageRect.h = 20;
    SDL_RenderCopy(sdlAudioRenderer, messageTexture, NULL, &messageRect);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_FreeSurface(message);
    SDL_DestroyTexture(messageTexture);
    for(int x = 1; x < 384; x++){
        int y = (yOffset) - (audio_get_context()->sq1_history[(x + audio_get_context()->sq1_index) % 384] * 50);
        SDL_RenderDrawLine(sdlAudioRenderer, x-1+xOffset, lastY, x+xOffset, y);
        lastY = y;
        if(x+xOffset == screenWidth) {
            break;
        }
    }

    yOffset = 190;
    lastY = (yOffset) - (audio_get_context()->sq2_history[audio_get_context()->sq2_index] * 50);
    message = TTF_RenderText_Solid(sans, "Square 2", text_color);
    messageTexture = SDL_CreateTextureFromSurface(sdlAudioRenderer, message);
    messageRect.x = xOffset;
    messageRect.y = yOffset-70;
    messageRect.w = 50;
    messageRect.h = 20;
    SDL_RenderCopy(sdlAudioRenderer, messageTexture, NULL, &messageRect);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_FreeSurface(message);
    SDL_DestroyTexture(messageTexture);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    for(int x = 1; x < 384; x++){
        int y = (yOffset) - (audio_get_context()->sq2_history[(x + audio_get_context()->sq2_index) % 384] * 50);
        SDL_RenderDrawLine(sdlAudioRenderer, x-1+xOffset, lastY, x+xOffset, y);
        lastY = y;
        if(x+xOffset == screenWidth) {
            break;
        }
    }

    yOffset = 310;
    lastY = (yOffset) - (audio_get_context()->ch3_history[audio_get_context()->ch3_index] * 50);
    message = TTF_RenderText_Solid(sans, "Wave", text_color);
    messageTexture = SDL_CreateTextureFromSurface(sdlAudioRenderer, message);
    messageRect.x = xOffset;
    messageRect.y = yOffset-70;
    messageRect.w = 50;
    messageRect.h = 20;
    SDL_RenderCopy(sdlAudioRenderer, messageTexture, NULL, &messageRect);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_FreeSurface(message);
    SDL_DestroyTexture(messageTexture);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    for(int x = 1; x < 384; x++){
        int y = (yOffset) - (audio_get_context()->ch3_history[(x + audio_get_context()->ch3_index) % 384] * 50);
        SDL_RenderDrawLine(sdlAudioRenderer, x-1+xOffset, lastY, x+xOffset, y);
        lastY = y;
        if(x+xOffset == screenWidth) {
            break;
        }
    }

    yOffset = 430;
    lastY = (yOffset) - (audio_get_context()->ch4_history[audio_get_context()->ch4_index] * 50);
    message = TTF_RenderText_Solid(sans, "Noise", text_color);
    messageTexture = SDL_CreateTextureFromSurface(sdlAudioRenderer, message);
    messageRect.x = xOffset;
    messageRect.y = yOffset-70;
    messageRect.w = 50;
    messageRect.h = 20;
    SDL_RenderCopy(sdlAudioRenderer, messageTexture, NULL, &messageRect);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_FreeSurface(message);
    SDL_DestroyTexture(messageTexture);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    for(int x = 1; x < 384; x++){
        int y = (yOffset) - (audio_get_context()->ch4_history[(x + audio_get_context()->ch4_index) % 384] * 50);
        SDL_RenderDrawLine(sdlAudioRenderer, x-1+xOffset, lastY, x+xOffset, y);
        lastY = y;
        if(x+xOffset == screenWidth) {
            break;
        }
    }

    yOffset = 550;
    lastY = (yOffset) - (audio_get_context()->left_history[audio_get_context()->left_index] * 50);
    message = TTF_RenderText_Solid(sans, "Left Out", text_color);
    messageTexture = SDL_CreateTextureFromSurface(sdlAudioRenderer, message);
    messageRect.x = xOffset;
    messageRect.y = yOffset-70;
    messageRect.w = 50;
    messageRect.h = 20;
    SDL_RenderCopy(sdlAudioRenderer, messageTexture, NULL, &messageRect);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_FreeSurface(message);
    SDL_DestroyTexture(messageTexture);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 0, 255, 255);
    for(int x = 1; x < 384; x++){
        int y = (yOffset) - (audio_get_context()->left_history[(x + audio_get_context()->left_index) % 384] * 50);
        SDL_RenderDrawLine(sdlAudioRenderer, x-1+xOffset, lastY, x+xOffset, y);
        lastY = y;
        if(x+xOffset == screenWidth) {
            break;
        }
    }

    yOffset = 680;
    lastY = (yOffset) - (audio_get_context()->right_history[audio_get_context()->right_index] * 50);
    message = TTF_RenderText_Solid(sans, "Right Out", text_color);
    messageTexture = SDL_CreateTextureFromSurface(sdlAudioRenderer, message);
    messageRect.x = xOffset;
    messageRect.y = yOffset-70;
    messageRect.w = 50;
    messageRect.h = 20;
    SDL_RenderCopy(sdlAudioRenderer, messageTexture, NULL, &messageRect);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_FreeSurface(message);
    SDL_DestroyTexture(messageTexture);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 255, 0, 255);
    SDL_SetRenderDrawColor(sdlAudioRenderer, 0, 0, 255, 255);
    for(int x = 1; x < 384; x++){
        int y = (yOffset) - (audio_get_context()->right_history[(x + audio_get_context()->right_index) % 384] * 50);
        SDL_RenderDrawLine(sdlAudioRenderer, x-1+xOffset, lastY, x+xOffset, y);
        lastY = y;
        if(x+xOffset == screenWidth) {
            break;
        }
    }

    SDL_RenderPresent(sdlAudioRenderer);
}

void ui_update() {
    SDL_Rect rc;
    rc.x = rc.y = 0;
    rc.w = rc.h = 2048;

    u32 *video_buffer = ppu_get_context()->video_buffer;

    for(int line_num = 0; line_num < YRES; line_num++) {
        for(int x = 0; x < XRES; x++) {
            rc.x = x * scale;
            rc.y = line_num * scale;
            rc.w = scale;
            rc.h = scale;

            SDL_FillRect(screen, &rc, video_buffer[x + (line_num * XRES)]);
        }
    }
    SDL_UpdateTexture(sdlTexture, NULL, screen->pixels, screen->pitch);
    SDL_RenderClear(sdlRenderer);
    SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);

    SDL_RenderPresent(sdlRenderer);
    update_vram_window();
    update_audio_window();
}

void ui_on_key(bool down, u32 key_code) {
    if(key_code == SDLK_SPACE && down == true) {
        emu_get_context()->fast_forward = !emu_get_context()->fast_forward;
        if(emu_get_context()->fast_forward){
            ppu_get_context()->target_frame_time = 1000/(60 * FAST_FORWARD_SPEED);
        } else {
            ppu_get_context()->target_frame_time = 1000/60;
        }
        printf("target frame time: %d\n", ppu_get_context()->target_frame_time);
    }
    if(ppu_get_context()->paused) {
        if(key_code == SDLK_PERIOD && down == true){
            ppu_get_context()->frame = true;
        }
    }
    if(key_code == SDLK_p && down == true) {
        ppu_get_context()->paused = !ppu_get_context()->paused;
        if(ppu_get_context()->paused) {
            printf("Paused Emulation\n");
        } else {
            printf("Resumed Emulation\n");
        }
    } 
    if(key_code == SDLK_d && down == true) {
        emu_get_context()->debug = true;
        debug_get_context()->state = DS_MAIN;
        emu_get_context()->paused = true;
    } 
    if(key_code == SDLK_r && down == true) {
        emu_stop();
        emu_reset();
        emu_start();
    }
    if(key_code == SDLK_s && down == true) {
        emu_stop();
        state_save_file(0);
        emu_start();
    }
    if(key_code == SDLK_l && down == true) {
        emu_stop();
        emu_reset();
        state_load_file(0);
        emu_start();
    }
    if(key_code == SDLK_v && down == true) {
        if(SDL_GetWindowFlags(sdlVramWindow) & SDL_WINDOW_HIDDEN) {
            SDL_ShowWindow(sdlVramWindow);
        } else {
            SDL_HideWindow(sdlVramWindow);
        }
    }
    if(key_code == SDLK_a && down == true) {
        if(SDL_GetWindowFlags(sdlAudioWindow) & SDL_WINDOW_HIDDEN) {
            SDL_ShowWindow(sdlAudioWindow);
        } else {
            SDL_HideWindow(sdlAudioWindow);
        }
    }
    switch(key_code){
        case SDLK_z: gamepad_get_state()->b = down; break;
        case SDLK_x: gamepad_get_state()->a = down; break;
        case SDLK_RETURN: gamepad_get_state()->start = down; break;
        case SDLK_TAB: gamepad_get_state()->select = down; break;

        case SDLK_UP: gamepad_get_state()->up = down; break;
        case SDLK_DOWN: gamepad_get_state()->down = down; break;
        case SDLK_LEFT: gamepad_get_state()->left = down; break;
        case SDLK_RIGHT: gamepad_get_state()->right = down; break;
    }
    gamepad_int_update();
}

void ui_on_joy_button(u8 button, bool down) {
    switch(button){
        case 0: gamepad_get_state()->b = down; break;
        case 1: gamepad_get_state()->a = down; break;
        case 7: gamepad_get_state()->start = down; break;
        case 6: gamepad_get_state()->select = down; break;
    }
}

void ui_on_controller_button(u8 button, bool down) {
    switch(button){
        case SDL_CONTROLLER_BUTTON_A: gamepad_get_state()->b = down; break;
        case SDL_CONTROLLER_BUTTON_B: gamepad_get_state()->a = down; break;
        case SDL_CONTROLLER_BUTTON_START: gamepad_get_state()->start = down; break;
        case SDL_CONTROLLER_BUTTON_BACK: gamepad_get_state()->select = down; break;
        case SDL_CONTROLLER_BUTTON_DPAD_DOWN: gamepad_get_state()->down = down; break;
        case SDL_CONTROLLER_BUTTON_DPAD_UP: gamepad_get_state()->up = down; break;
        case SDL_CONTROLLER_BUTTON_DPAD_LEFT: gamepad_get_state()->left = down; break;
        case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: gamepad_get_state()->right = down; break;
    }
}

void ui_on_joy_hat(u8 hat, u8 value) {
    if(hat == 0){
        gamepad_get_state()->up = value & SDL_HAT_UP;
        gamepad_get_state()->down = value & SDL_HAT_DOWN;
        gamepad_get_state()->left = value & SDL_HAT_LEFT;
        gamepad_get_state()->right = value & SDL_HAT_RIGHT;
    }
}

#define DEADZONE 10000

void ui_on_Joy_axis(u8 axis, int16_t value) {
    if(axis == 1) {
        gamepad_get_state()->up = false;
        gamepad_get_state()->down = false;
        if(value < -DEADZONE) {
            gamepad_get_state()->up = true;
        }
        if(value > DEADZONE) {
            gamepad_get_state()->down = true;
        }
    }
    if(axis == 0) {
        gamepad_get_state()->left = false;
        gamepad_get_state()->right = false;
        if(value < -DEADZONE) {
            gamepad_get_state()->left = true;
        }
        if(value > DEADZONE) {
            gamepad_get_state()->right = true;
        }
    }
}

void ui_on_controller_axis(u8 axis, int16_t value) {
    if(axis == SDL_CONTROLLER_AXIS_LEFTY) {
        gamepad_get_state()->up = false;
        gamepad_get_state()->down = false;
        if(value < -DEADZONE) {
            gamepad_get_state()->up = true;
        }
        if(value > DEADZONE) {
            gamepad_get_state()->down = true;
        }
    }
    if(axis == SDL_CONTROLLER_AXIS_LEFTX) {
        gamepad_get_state()->left = false;
        gamepad_get_state()->right = false;
        if(value < -DEADZONE) {
            gamepad_get_state()->left = true;
        }
        if(value > DEADZONE) {
            gamepad_get_state()->right = true;
        }
    }
}

void ui_handle_events(){
    SDL_Event e;
    while(SDL_PollEvent(&e) > 0) {

        if(e.type == SDL_KEYDOWN) {
            ui_on_key(true, e.key.keysym.sym);
        }

        if(e.type == SDL_KEYUP) {
            ui_on_key(false, e.key.keysym.sym);
        }

        if(e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_CLOSE) {
            if(e.window.windowID == SDL_GetWindowID(sdlWindow)){
                emu_get_context()->die = true;
            } else {
                SDL_Window *w = SDL_GetWindowFromID(e.window.windowID);
                SDL_HideWindow(w);
            }
        }

        if(e.type == SDL_JOYBUTTONDOWN) {
            //ui_on_joy_button(e.jbutton.button, true);
        }

        if(e.type == SDL_JOYBUTTONUP) {
            //ui_on_joy_button(e.jbutton.button, false);
        }

        if(e.type == SDL_JOYHATMOTION) {
           // ui_on_joy_hat(e.jhat.hat, e.jhat.value);
        }

        if(e.type == SDL_JOYAXISMOTION) {
            //ui_on_Joy_axis(e.jaxis.axis, e.jaxis.value);
        }

        if(e.type == SDL_CONTROLLERBUTTONDOWN) {
            ui_on_controller_button(e.cbutton.button, true);
        }

        if(e.type == SDL_CONTROLLERBUTTONUP) {
            ui_on_controller_button(e.cbutton.button, false);
        }

        if(e.type == SDL_CONTROLLERAXISMOTION) {
            ui_on_controller_axis(e.caxis.axis, e.caxis.value);
        }
        
        //if(e.type == sdl_controllerhat)

    }
}

void delay(u32 ms) {
    SDL_Delay(ms);
}

u32 get_ticks() {
    return SDL_GetTicks();
}
