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

#include "Timing.h"
#include "TapUi.h"
#include "TapLoader.h"
#include "Log.h"

#define _ADDR_BUTTON_AREA_LEFT 248
#define _BUTTON_SIDE_LENGTH 8
#define _TAP_UI_HEIGHT (_BUTTON_SIDE_LENGTH+1)    // at zoom 1
#define _BUTTON_SPACING_FACTOR (1.2f)

#define _TURBO_INITIAL_VALUE 1

SDL_Rect _tapui_window_rectangle;
TTF_Font* _tapui_font;
Uint8 _tapui_zoom;
Uint8 _tapui_initialized = 0;

Uint64 _tapui_frame_counter = 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 _TAPUI_RENDER_COOLDOWN_MS 500
static Uint8 _tapui_forceRender;
struct timeb _tapui_last_render_time;


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

#define _BUTTON_COUNT 6
struct tapuiButton* _tapui_button__play;
struct tapuiButton* _tapui_button__stop;
struct tapuiButton* _tapui_button__seek_previous;
struct tapuiButton* _tapui_button__seek_next;
struct tapuiButton* _tapui_button__turbo;
struct tapuiButton* _tapui_button__play_one;
struct tapuiButton _tapui_buttons[_BUTTON_COUNT];

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

SDL_Texture* _tapui_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 tapui_get_UI_height(Uint8 zoom) {
	return _TAP_UI_HEIGHT * zoom;
}

void _tapui_handle_stop() {
    _tapui_button__stop->isEnabled = 0;
    _tapui_button__seek_next->isEnabled = 1;
    _tapui_button__seek_previous->isEnabled = 1;
    _tapui_button__play->isEnabled = 1;
    _tapui_button__play_one->isEnabled = 1;
}

void _tapui_on_stop_handler() {
    _tapui_handle_stop();
}

void _tapui_handle_play() {
    _tapui_button__stop->isEnabled = 1;
    _tapui_button__seek_next->isEnabled = 0;
    _tapui_button__seek_previous->isEnabled = 0;
    _tapui_button__play->isEnabled = 0;
    _tapui_button__play_one->isEnabled = 0;
}

void _tapui_on_play_handler() {
    _tapui_handle_play();
}

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

    SDL_FreeSurface(messageSurface);

    SDL_Rect textureRectangle;
    textureRectangle.x = _tapui_window_rectangle.x;
    textureRectangle.y = _tapui_window_rectangle.y;

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

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

    SDL_DestroyTexture(messageTexture);
}

void tapui_mouseclick(Uint8 clicks) {
    if (!_tapui_initialized) {
        return;
    }

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

    _tapui_forceRender = 1;

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

void _tapui_render_file_not_loaded(SDL_Renderer* renderer) {
    _tapui_render_text(renderer, "Tape file not loaded");
}

Uint8 tapui_render(SDL_Renderer* renderer, Uint8 forcedRenderByConsumer) {
    if (!_tapui_initialized) {
        return 0;
    }

    _tapui_frame_counter++;

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

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

    _tapui_forceRender = 0;
    _tapui_last_render_time = now;

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

    if (!taploader_is_file_loaded()) {
        // shortcircuit when tape file is not loaded at all
        _tapui_render_file_not_loaded(renderer);
        return 0;
    }

    // render progress indicator through current TAP block as a solid background colour
    struct tapRunState tapeState = taploader_get_state();
    if (tapeState.type == DataBytes) {
        Uint64 currentDataByteOffset = tapeState.dataBytePointer - tapeState.block->tapBlockBytes;
        Uint64 currentBlockSize = tapeState.block->blockDataSize;
        double percent = (double)currentDataByteOffset / (double)currentBlockSize;
        if (percent > 1.0f) {
            percent = 1.0f;
        }

        int progressHeadWidth = _tapui_zoom * 2;
        Uint8 red = 50, green = 0, blue = 100;

        SDL_SetRenderDrawColor(renderer, red, green, blue, 255);
        SDL_Rect progressRect = _tapui_window_rectangle;
        progressRect.w = (int)((double)_tapui_window_rectangle.w * percent);
        SDL_RenderFillRect(renderer, &progressRect);

        // front of the progress bar is a bit more visible, to stand out to the user..
        SDL_SetRenderDrawColor(renderer, red + 150, 150, blue + 150, 255);
        SDL_Rect progressHeadRect = progressRect;
        progressHeadRect.x = progressHeadRect.w + progressHeadRect.x - progressHeadWidth;
        progressHeadRect.w = progressHeadWidth;
        SDL_RenderFillRect(renderer, &progressHeadRect);
        // .. and has a tail
        /*SDL_SetRenderDrawColor(renderer, red + 100, 0, blue + 100, 255);
        progressHeadRect.x -= progressHeadWidth;
        SDL_RenderFillRect(renderer, &progressHeadRect);*/
        // .. more tail
        /*SDL_SetRenderDrawColor(renderer, red + 50, 0, blue + 50, 255);
        progressHeadRect.x -= progressHeadWidth;
        SDL_RenderFillRect(renderer, &progressHeadRect);*/
    }

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

    // render text
    Uint16 blocksCount = taploader_get_block_count()-1;
    if (tapeState.block->type == Header) {
        sprintf_s(_messageBuffer, _MESSAGE_BUFFER_SIZE - 10, "%03d/%03d %s %s %ub",
            tapeState.block->blockIndex,
            blocksCount,
            tapeState.block->flagByteHumanReadable,
            tapeState.block->headerInfo->filename,
            tapeState.block->blockRawDataSize);
    }
    else {
        // don't display filename for non-header blocks
        sprintf_s(_messageBuffer, _MESSAGE_BUFFER_SIZE - 10, "%03d/%03d %s            %ub",
            tapeState.block->blockIndex,
            blocksCount,
            tapeState.block->flagByteHumanReadable,
            tapeState.block->blockRawDataSize);
    }
    
    _tapui_render_text(renderer, _messageBuffer);
    return 1;
}

void _tapui__onclick_turbo() {
    _tapui_button__turbo->isEnabled ^= 1;
    taploader_set_desired_turbo(_tapui_button__turbo->isEnabled);
}

void _tapui__onclick_seek_previous() {
    if (!_tapui_button__seek_previous->isEnabled) {
        return;
    }

    if (taploader_get_state().type != Stopped) {
        return;
    }

    _tapui_button__stop->isEnabled = 0;
    _tapui_button__seek_next->isEnabled = 1;
    _tapui_button__seek_previous->isEnabled = 1;
    _tapui_button__play->isEnabled = 1;
    taploader_seek_previous();
}

void _tapui__onclick_seek_next() {
    if (!_tapui_button__seek_next->isEnabled) {
        return;
    }

    if (taploader_get_state().type != Stopped) {
        return;
    }

    _tapui_button__stop->isEnabled = 0;
    _tapui_button__seek_next->isEnabled = 1;
    _tapui_button__seek_previous->isEnabled = 1;
    _tapui_button__play->isEnabled = 1;
    taploader_seek_next();
}

void _tapui__onclick_play() {
    if (!_tapui_button__play->isEnabled) {
        return;
    }

    if (taploader_get_state().type != Stopped) {
        return;
    }

    taploader_press_play(1);
    // button state will be taken care of by the "play handler", invoked by the taploader
}

void _tapui__onclick_play_one() {
    if (!_tapui_button__play_one->isEnabled) {
        return;
    }

    if (taploader_get_state().type != Stopped) {
        return;
    }

    taploader_press_play(0);
    // button state will be taken care of by the "play handler", invoked by the taploader
}

void _tapui__onclick_stop() {
    if (!_tapui_button__stop->isEnabled) {
        return;
    }
    taploader_press_stop();
    // button state will be taken care of by the "stop handler", invoked by the taploader
}

void _tapui_create_buttons(SDL_Renderer* renderer) {
    SDL_Rect location = _tapui_window_rectangle;
    location.x = _tapui_window_rectangle.x + _ADDR_BUTTON_AREA_LEFT * _tapui_zoom;
    location.y = _tapui_window_rectangle.y;
    location.w = _BUTTON_SIDE_LENGTH * _tapui_zoom;
    location.h = _BUTTON_SIDE_LENGTH * _tapui_zoom;

    struct tapuiButton turbo;
    turbo.flip = SDL_FLIP_NONE;
    turbo.isEnabled = _TURBO_INITIAL_VALUE;
    turbo.location = location;
    turbo.onClick = _tapui__onclick_turbo;
    turbo.textureDisabled = _tapui_loadImage("data\\turbo_off.png", renderer);
    turbo.textureEnabled = _tapui_loadImage("data\\turbo_on.png", renderer);
    _tapui_buttons[0] = turbo;
    _tapui_button__turbo = &_tapui_buttons[0];

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _tapui_zoom);
    struct tapuiButton seek_previous;
    seek_previous.flip = SDL_FLIP_HORIZONTAL;
    seek_previous.isEnabled = 1;
    seek_previous.location = location;
    seek_previous.onClick = _tapui__onclick_seek_previous;
    seek_previous.textureDisabled = _tapui_loadImage("data\\seek_next_disabled.png", renderer);
    seek_previous.textureEnabled = _tapui_loadImage("data\\seek_next.png", renderer);
    _tapui_buttons[1] = seek_previous;
    _tapui_button__seek_previous = &_tapui_buttons[1];

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _tapui_zoom);
    struct tapuiButton play;
    play.flip = SDL_FLIP_NONE;
    play.isEnabled = 1;
    play.location = location;
    play.onClick = _tapui__onclick_play;
    play.textureDisabled = _tapui_loadImage("data\\play_disabled.png", renderer);
    play.textureEnabled = _tapui_loadImage("data\\play.png", renderer);
    _tapui_buttons[2] = play;
    _tapui_button__play = &_tapui_buttons[2];

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _tapui_zoom);
    struct tapuiButton playOne;
    playOne.flip = SDL_FLIP_NONE;
    playOne.isEnabled = 1;
    playOne.location = location;
    playOne.onClick = _tapui__onclick_play_one;
    playOne.textureDisabled = _tapui_loadImage("data\\play_one_disabled.png", renderer);
    playOne.textureEnabled = _tapui_loadImage("data\\play_one.png", renderer);
    _tapui_buttons[3] = playOne;
    _tapui_button__play_one = &_tapui_buttons[3];

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _tapui_zoom);
    struct tapuiButton stop;
    stop.flip = SDL_FLIP_NONE;
    stop.isEnabled = 0;
    stop.location = location;
    stop.onClick = _tapui__onclick_stop;
    stop.textureDisabled = _tapui_loadImage("data\\stop_disabled.png", renderer);
    stop.textureEnabled = _tapui_loadImage("data\\stop.png", renderer);
    _tapui_buttons[4] = stop;
    _tapui_button__stop = &_tapui_buttons[4];

    location.x += (int)(_BUTTON_SPACING_FACTOR * _BUTTON_SIDE_LENGTH * _tapui_zoom);
    struct tapuiButton seek_next;
    seek_next.flip = SDL_FLIP_NONE;
    seek_next.isEnabled = 1;
    seek_next.location = location;
    seek_next.onClick = _tapui__onclick_seek_next;
    seek_next.textureDisabled = _tapui_loadImage("data\\seek_next_disabled.png", renderer);
    seek_next.textureEnabled = _tapui_loadImage("data\\seek_next.png", renderer);
    _tapui_buttons[5] = seek_next;
    _tapui_button__seek_next = &_tapui_buttons[5];
}

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

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

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

    _tapui_create_buttons(renderer);
    taploader_set_desired_turbo(_TURBO_INITIAL_VALUE);
    taploader_set_on_stop_handler(_tapui_on_stop_handler);
    taploader_set_on_play_handler(_tapui_on_play_handler);

    _tapui_initialized = 1;
}

void tapui_destroy() {
    if (!_tapui_initialized) {
        return;
    }

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

    TTF_CloseFont(_tapui_font);
}
