#include <SDL_image.h>
#include <SDL_ttf.h>
#include <stdio.h>
#include <sys\timeb.h>

#include "PokeUi.h"
#include "Memory.h"
#include "Timing.h"
#include "Log.h"

#define _ADDR_BUTTON_AREA_LEFT 1
#define _VAL_BUTTON_AREA_LEFT 274
#define _BUTTON_SIDE_LENGTH 8
#define _POKE_UI_HEIGHT (_BUTTON_SIDE_LENGTH+1)    // at zoom 1
#define _BUTTON_SPACING_FACTOR (1.3f)

#define POKEUI_BUTTON_REPEATS_PER_SECOND 10
#define POKEUI_BUTTON_REPEATS_FAST_AMOUNT 100

SDL_Rect _pokeui_rectangle;
TTF_Font* _pokeui_font;
Uint8 _pokeui_zoom;
Uint8 _pokeui_initialized = 0;

Uint64 _pokeui_frame_counter = 0;
Uint64 _pokeui_last_repeated_frame = 0;


#define _DEBUG_BUFFER_SIZE 256
static char _debugBuffer[_DEBUG_BUFFER_SIZE];

#define _MESSAGE_BUFFER_SIZE 256
static char _messageBuffer[_MESSAGE_BUFFER_SIZE];

// these govern rendering cooldown of this UI
//     unless forced (e.g. mouse click), this UI will not render every frame
#define _POKEUI_RENDER_COOLDOWN_MS 500
static Uint8 _pokeui_forceRender;
struct timeb _pokeui_last_render_time;

Uint16 _pokeui_address = 32768;
Uint8 _pokeui_value = 128;

struct pokeuiButton {
    SDL_Rect location;
    SDL_Texture* textureEnabled;
    SDL_Texture* textureDisabled;
    Uint8 isEnabled;
    SDL_RendererFlip flip;
    void (*onClick)();
    void (*onMouseDown)();
};

#define _BUTTON_COUNT 6
struct pokeuiButton* _pokeui_button__fast;
struct pokeuiButton _pokeui_buttons[_BUTTON_COUNT];

void _pokeui_renderButton(SDL_Renderer* renderer, struct pokeuiButton* button) {
    SDL_Texture* texture = button->isEnabled ? button->textureEnabled : button->textureDisabled;
    SDL_RenderCopyEx(renderer, texture, NULL, &button->location, 0.0f, NULL, button->flip);
}

void pokeui_set_address(Uint16 address) {
    _pokeui_address = address;
    if (_pokeui_address < 16 * 1024) {
        _pokeui_address = 16 * 1024;
    }
}

void pokeui_set_value(Uint8 value) {
    _pokeui_value = value;
}

SDL_Texture* _pokeui_loadImage(char* file, SDL_Renderer* renderer) {
    SDL_Surface* loadedImage;
    SDL_Texture* texture;
    loadedImage = IMG_Load(file);

    if (loadedImage != NULL)
    {
        texture = SDL_CreateTextureFromSurface(renderer, loadedImage);
        if (texture == NULL) {
            sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Error: unable to create texture from file %s", file);
            log_write(_debugBuffer);
        }
        SDL_FreeSurface(loadedImage);
    }
    else {
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Error: unable to load surface from image file %s", file);
        log_write(_debugBuffer);
        return NULL;
    }

    return texture;
}

Uint16 pokeui_get_UI_height(Uint8 zoom) {
    return _POKE_UI_HEIGHT * zoom;
}

void _pokeui_render_text(SDL_Renderer* renderer, char* text) {
    SDL_Color textColour = { 128, 128, 128 };
    SDL_Surface* messageSurface = TTF_RenderText_Solid(_pokeui_font, text, textColour);
    SDL_Texture* messageTexture = SDL_CreateTextureFromSurface(renderer, messageSurface);

    SDL_FreeSurface(messageSurface);

    SDL_Rect textureRectangle;
    textureRectangle.x = _pokeui_rectangle.x;
    textureRectangle.y = _pokeui_rectangle.y;

    TTF_SizeText(_pokeui_font, (const char*)text, &textureRectangle.w, &textureRectangle.h);

    SDL_RenderCopy(renderer, messageTexture, NULL, &textureRectangle);

    SDL_DestroyTexture(messageTexture);
}

void pokeui_mousedown() {
    if (!_pokeui_initialized) {
        return;
    }

    SDL_Point mousePointer;
    SDL_GetMouseState(&mousePointer.x, &mousePointer.y);
    if (!SDL_PointInRect(&mousePointer, &_pokeui_rectangle)) {
        // click was outside our UI
        return;
    }

    _pokeui_forceRender = 1;

    // check buttons
    for (int i = 0; i < _BUTTON_COUNT; i++) {
        if (SDL_PointInRect(&mousePointer, &_pokeui_buttons[i].location)) {
            if (_pokeui_buttons[i].onMouseDown != NULL) {
                _pokeui_buttons[i].onMouseDown();
            }
        }
    }
}

void pokeui_mouseclick() {
    if (!_pokeui_initialized) {
        return;
    }

    SDL_Point mousePointer;
    SDL_GetMouseState(&mousePointer.x, &mousePointer.y);
    if (!SDL_PointInRect(&mousePointer, &_pokeui_rectangle)) {
        // click was outside our UI
        return;
    }

    _pokeui_forceRender = 1;

    // check buttons
    for (int i = 0; i < _BUTTON_COUNT; i++) {
        if (SDL_PointInRect(&mousePointer, &_pokeui_buttons[i].location)) {
            if (_pokeui_buttons[i].onClick != NULL) {
                _pokeui_buttons[i].onClick();
            }
        }
    }
}

Uint8 pokeui_render(SDL_Renderer* renderer, Uint8 forcedRenderByConsumer) {
    if (!_pokeui_initialized) {
        return 0;
    }

    _pokeui_frame_counter++;

    struct timeb now;
    ftime(&now);
    Uint64 timeSinceLastRenderMs = (Uint64)(1000.0 * (now.time - _pokeui_last_render_time.time)
        + (now.millitm - _pokeui_last_render_time.millitm));

    if (!forcedRenderByConsumer && !_pokeui_forceRender && timeSinceLastRenderMs <= _POKEUI_RENDER_COOLDOWN_MS) {
        // NOOP if we're still cooling down and we're not forced to render
        return 0;
    }

    _pokeui_forceRender = 0;
    _pokeui_last_render_time = now;

    // erase everything
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderFillRect(renderer, &_pokeui_rectangle);

    // render buttons
    for (int i = 0; i < _BUTTON_COUNT; i++) {
        _pokeui_renderButton(renderer, &_pokeui_buttons[i]);
    }

    // render text
    
    sprintf_s(_messageBuffer, _MESSAGE_BUFFER_SIZE - 10, "    Memory[%5u]=%3u     Poke:%3u",
        _pokeui_address,
        memory_read8(_pokeui_address),
        _pokeui_value
        );
    _pokeui_render_text(renderer, _messageBuffer);
    return 1;
}

Uint16 _pokeui_get_frame_repeat_interval() {
    double fps = 1000.0f / (double)timing_get_target_milliseconds_per_video_frame();
    Uint16 framesPerRepeat = (Uint16)fps / POKEUI_BUTTON_REPEATS_PER_SECOND;
    return framesPerRepeat;
}

void _pokeui__onmousedown_addr_down() {
    if (_pokeui_button__fast->isEnabled) {
        _pokeui_address -= POKEUI_BUTTON_REPEATS_FAST_AMOUNT;
    }
    else {
        Uint16 framesPerRepeat = _pokeui_get_frame_repeat_interval();
        if (_pokeui_frame_counter - _pokeui_last_repeated_frame > framesPerRepeat) {
            _pokeui_address--;
            _pokeui_last_repeated_frame = _pokeui_frame_counter;
        }
    }

    if (_pokeui_address < 16 * 1024) {
        _pokeui_address = 16 * 1024;
    }
}

void _pokeui__onmousedown_addr_up() {
    if (_pokeui_button__fast->isEnabled) {
        _pokeui_address += POKEUI_BUTTON_REPEATS_FAST_AMOUNT;
    }
    else {
        Uint16 framesPerRepeat = _pokeui_get_frame_repeat_interval();
        if (_pokeui_frame_counter - _pokeui_last_repeated_frame > framesPerRepeat) {
            _pokeui_address++;
            _pokeui_last_repeated_frame = _pokeui_frame_counter;
        }
    }

    if (_pokeui_address < 16 * 1024) {
        _pokeui_address = 65535;
    }
}

void _pokeui__onmousedown_val_down() {
    Uint16 framesPerRepeat = _pokeui_get_frame_repeat_interval();
    if (_pokeui_frame_counter - _pokeui_last_repeated_frame > framesPerRepeat) {
        _pokeui_value--;
        _pokeui_last_repeated_frame = _pokeui_frame_counter;
    }
}

void _pokeui__onclick_apply_poke() {
    memory_write8(_pokeui_address, _pokeui_value);
}

void _pokeui__onmousedown_val_up() {
    Uint16 framesPerRepeat = _pokeui_get_frame_repeat_interval();
    if (_pokeui_frame_counter - _pokeui_last_repeated_frame > framesPerRepeat) {
        _pokeui_value++;
        _pokeui_last_repeated_frame = _pokeui_frame_counter;
    }
}

void _pokeui__onclick_fast() {
    _pokeui_button__fast->isEnabled ^= 1;
}

void _pokeui_create_buttons(SDL_Renderer* renderer) {
    SDL_Rect location = _pokeui_rectangle;
    location.x = _pokeui_rectangle.x + _ADDR_BUTTON_AREA_LEFT * _pokeui_zoom;
    location.y = _pokeui_rectangle.y;
    location.w = _BUTTON_SIDE_LENGTH * _pokeui_zoom;
    location.h = _BUTTON_SIDE_LENGTH * _pokeui_zoom;

    struct pokeuiButton fast;
    fast.flip = SDL_FLIP_HORIZONTAL;
    fast.isEnabled = 1;
    fast.location = location;
    fast.onClick = _pokeui__onclick_fast;
    fast.onMouseDown = NULL;
    fast.textureDisabled = _pokeui_loadImage("data\\turbo_off.png", renderer);
    fast.textureEnabled = _pokeui_loadImage("data\\turbo_on.png", renderer);
    _pokeui_buttons[0] = fast;
    _pokeui_button__fast = &_pokeui_buttons[0];

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _pokeui_zoom);
    struct pokeuiButton addrDown;
    addrDown.flip = SDL_FLIP_HORIZONTAL;
    addrDown.isEnabled = 1;
    addrDown.location = location;
    addrDown.onClick = NULL;
    addrDown.onMouseDown = _pokeui__onmousedown_addr_down;
    addrDown.textureDisabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    addrDown.textureEnabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    _pokeui_buttons[1] = addrDown;

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _pokeui_zoom);
    struct pokeuiButton addrUp;
    addrUp.flip = SDL_FLIP_NONE;
    addrUp.isEnabled = 1;
    addrUp.location = location;
    addrUp.onClick = NULL;
    addrUp.onMouseDown = _pokeui__onmousedown_addr_up;
    addrUp.textureDisabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    addrUp.textureEnabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    _pokeui_buttons[2] = addrUp;

    // now the buttons to the right
    location.x = _pokeui_rectangle.x + _VAL_BUTTON_AREA_LEFT * _pokeui_zoom;
    struct pokeuiButton valDown;
    valDown.flip = SDL_FLIP_HORIZONTAL;
    valDown.isEnabled = 1;
    valDown.location = location;
    valDown.onClick = NULL;
    valDown.onMouseDown = _pokeui__onmousedown_val_down;
    valDown.textureDisabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    valDown.textureEnabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    _pokeui_buttons[3] = valDown;

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _pokeui_zoom);
    struct pokeuiButton valUp;
    valUp.flip = SDL_FLIP_NONE;
    valUp.isEnabled = 1;
    valUp.location = location;
    valUp.onClick = NULL;
    valUp.onMouseDown = _pokeui__onmousedown_val_up;
    valUp.textureDisabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    valUp.textureEnabled = _pokeui_loadImage("data\\addr_up.png", renderer);
    _pokeui_buttons[4] = valUp;

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _pokeui_zoom);
    struct pokeuiButton applyPoke;
    applyPoke.flip = SDL_FLIP_NONE;
    applyPoke.isEnabled = 1;
    applyPoke.location = location;
    applyPoke.onClick = _pokeui__onclick_apply_poke;
    applyPoke.onMouseDown = NULL;
    applyPoke.textureDisabled = _pokeui_loadImage("data\\apply_poke.png", renderer);
    applyPoke.textureEnabled = _pokeui_loadImage("data\\apply_poke.png", renderer);
    _pokeui_buttons[5] = applyPoke;
}

void pokeui_start(SDL_Window* window, SDL_Renderer* renderer, Uint16 width, Uint16 height, Uint16 top, Uint16 left, Uint8 zoom) {
    _pokeui_zoom = zoom;
    _pokeui_rectangle.x = left;
    _pokeui_rectangle.y = top;
    _pokeui_rectangle.w = width;
    _pokeui_rectangle.h = height;

    if (TTF_Init() < 0) {
        log_write("Error: could not initialize fonts");
        return;
    }

    _pokeui_font = TTF_OpenFont("data\\zx-spectrum.ttf", 7 * _pokeui_zoom);   // this gives the same size as the ZX's font
    if (_pokeui_font == NULL) {
        log_write("Error: unable to open font file");
        return;
    }

    _pokeui_create_buttons(renderer);
    _pokeui_initialized = 1;
}

void pokeui_destroy() {
    if (!_pokeui_initialized) {
        return;
    }

    for (int i = 0; i < _BUTTON_COUNT; i++) {
        SDL_DestroyTexture(_pokeui_buttons[i].textureDisabled);
        SDL_DestroyTexture(_pokeui_buttons[i].textureEnabled);
    }

    TTF_CloseFont(_pokeui_font);
}
