#include "on_screen_help.h"
#include <SDL2/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL2/SDL_pixels.h>    // Para SDL_PIXELFORMAT_RGBA8888
#include <memory>               // Para make_unique, unique_ptr
#include "lang.h"               // Para getText
#include "param.h"              // Para Param, ParamGame, param
#include "resource.h"           // Para Resource
#include "screen.h"             // Para Screen
#include "sprite.h"             // Para Sprite
#include "text.h"               // Para Text
#include "utils.h"              // Para easeInOutSine

// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
OnScreenHelp *OnScreenHelp::onScreenHelp = nullptr;

// Constantes evaluables en tiempo de compilación
constexpr int ICONSIZE = 16;
constexpr SDL_Point PADDING = {8, 8};
constexpr SDL_Point DESP = {ICONSIZE + 4, 5};

constexpr SDL_Rect CONTROLLER_UP = {16, 0, ICONSIZE, ICONSIZE};
constexpr SDL_Rect CONTROLLER_DOWN = {48, 0, ICONSIZE, ICONSIZE};
constexpr SDL_Rect CONTROLLER_LEFT = {64, 0, ICONSIZE, ICONSIZE};
constexpr SDL_Rect LEFT_BUTTON = {112, 0, ICONSIZE, ICONSIZE};
constexpr SDL_Rect TOP_BUTTON = {80, 0, ICONSIZE, ICONSIZE};
constexpr SDL_Rect RIGHT_BUTTON = {96, 0, ICONSIZE, ICONSIZE};
constexpr SDL_Rect START_BUTTON = {0, 0, ICONSIZE, ICONSIZE};

constexpr SDL_Rect CONTROLLER_UP_POS = {PADDING.x, PADDING.y + 18 * 0, ICONSIZE, ICONSIZE};
constexpr SDL_Rect CONTROLLER_DOWN_POS = {PADDING.x, PADDING.y + 18 * 1, ICONSIZE, ICONSIZE};
constexpr SDL_Rect CONTROLLER_LEFT_POS = {PADDING.x, PADDING.y + 18 * 2, ICONSIZE, ICONSIZE};
constexpr SDL_Rect LEFT_BUTTON_POS = {PADDING.x, PADDING.y + 18 * 3, ICONSIZE, ICONSIZE};
constexpr SDL_Rect TOP_BUTTON_POS = {PADDING.x, PADDING.y + 18 * 4, ICONSIZE, ICONSIZE};
constexpr SDL_Rect RIGHT_BUTTON_POS = {PADDING.x, PADDING.y + 18 * 5, ICONSIZE, ICONSIZE};
constexpr SDL_Rect START_BUTTON_POS = {PADDING.x, PADDING.y + 18 * 6, ICONSIZE, ICONSIZE};

// [SINGLETON] Crearemos el objeto onScreenHelp con esta función estática
void OnScreenHelp::init()
{
    OnScreenHelp::onScreenHelp = new OnScreenHelp();
}

// [SINGLETON] Destruiremos el objeto onScreenHelp con esta función estática
void OnScreenHelp::destroy()
{
    delete OnScreenHelp::onScreenHelp;
}

// [SINGLETON] Con este método obtenemos el objeto onScreenHelp y podemos trabajar con él
OnScreenHelp *OnScreenHelp::get()
{
    return OnScreenHelp::onScreenHelp;
}

// Constructor
OnScreenHelp::OnScreenHelp() : state(OnScreenHelpStatus::hidden), index(0)
{
    setSize();

    texture = SDL_CreateTexture(Screen::get()->getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, dest.w, dest.h);
    SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);

    fillTexture();
    precalculatePath(hiddenPos, showingPos, 60);
}

// Destructor
OnScreenHelp::~OnScreenHelp()
{
    SDL_DestroyTexture(texture);
}

// Actualiza la lógica interna
void OnScreenHelp::update()
{
    // Actualiza la posición
    updatePosition();
}

// Muestra el objeto en pantalla
void OnScreenHelp::render()
{
    if (state != OnScreenHelpStatus::hidden)
    {
        SDL_RenderCopy(Screen::get()->getRenderer(), texture, nullptr, &dest);
    }
}

// Rellena la textura con los gráficos y texto
void OnScreenHelp::fillTexture()
{
    // Cambia el renderizador a la textura
    auto temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
    SDL_SetRenderTarget(Screen::get()->getRenderer(), texture);

    // Crea el objeto para el texto
    auto text = std::make_unique<Text>(Resource::get()->getTexture("8bithud.png"), Resource::get()->getTextFile("8bithud.txt"));

    // Crea la textura con los gráficos
    auto controllersTexture = Resource::get()->getTexture("controllers.png");

    // Crea el sprite para dibujar los gráficos
    auto sprite = std::make_unique<Sprite>(controllersTexture, (SDL_Rect){0, 0, 16, 16});

    // Borra la textura
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
    SDL_RenderClear(Screen::get()->getRenderer());

    // Pon el color de fondo con el bisel
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0x55, 0x5D, 0x77, 255);
    SDL_Rect rect;
    rect = {4, 0, dest.w - (4 * 2), dest.h};
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect);

    rect = {4 / 2, 1, dest.w - 4, dest.h - 2};
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect);

    rect = {1, 4 / 2, dest.w - 2, dest.h - 4};
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect);

    rect = {0, 4, dest.w, dest.h - (4 * 2)};
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect);

    rect = {0, 0, dest.w / 2, dest.h};
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect);

    // Renderiza los botones y el texto
    renderButton(sprite.get(), text.get(), CONTROLLER_UP, CONTROLLER_UP_POS, 107);
    renderButton(sprite.get(), text.get(), CONTROLLER_DOWN, CONTROLLER_DOWN_POS, 108);
    renderButton(sprite.get(), text.get(), CONTROLLER_LEFT, CONTROLLER_LEFT_POS, 109);
    renderButton(sprite.get(), text.get(), LEFT_BUTTON, LEFT_BUTTON_POS, 110);
    renderButton(sprite.get(), text.get(), TOP_BUTTON, TOP_BUTTON_POS, 111);
    renderButton(sprite.get(), text.get(), RIGHT_BUTTON, RIGHT_BUTTON_POS, 112);
    renderButton(sprite.get(), text.get(), START_BUTTON, START_BUTTON_POS, 113);

    // Restaura el destino de renderizado
    SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
}

// Define el ancho y alto de la textura
void OnScreenHelp::setSize()
{
    const auto textSize = getLargestStringSize();
    const auto width = PADDING.x + DESP.x + textSize + PADDING.x;
    const auto height = PADDING.y + (7 * 18) + PADDING.y;
    const auto x = 0;
    const auto y = (param.game.height - height) / 2;

    dest = (SDL_Rect){x, y, width, height};

    hiddenPos = -width;
    showingPos = 0;
}

// Activa o desactiva el objeto
void OnScreenHelp::toggleState()
{
    if (state == OnScreenHelpStatus::showing || state == OnScreenHelpStatus::entering)
    {
        state = OnScreenHelpStatus::exitting;
    }

    else if (state == OnScreenHelpStatus::hidden || state == OnScreenHelpStatus::exitting)
    {
        state = OnScreenHelpStatus::entering;
    }
}

// Calcula la longitud en pixels del texto más largo
auto OnScreenHelp::getLargestStringSize() -> int const
{
    auto text = std::make_unique<Text>(Resource::get()->getTexture("8bithud.png"), Resource::get()->getTextFile("8bithud.txt"));
    auto size = 0;

    for (int i = 107; i <= 113; ++i)
    {
        const auto textSize = text->lenght(lang::getText(i));
        size = textSize > size ? textSize : size;
    }

    return size;
}

// Renderizara el boton y el texto
void OnScreenHelp::renderButton(Sprite *sprite, Text *text, const SDL_Rect &buttonClip, const SDL_Rect &buttonPos, int textId)
{
    sprite->setSpriteClip(buttonClip);
    sprite->setPosition(buttonPos);
    sprite->render();
    text->write(buttonPos.x + DESP.x, buttonPos.y + DESP.y, lang::getText(textId));
}

// Actualiza la posición
void OnScreenHelp::updatePosition()
{
    switch (state)
    {
    case OnScreenHelpStatus::hidden:
    {
        index = 0;
        break;
    }

    case OnScreenHelpStatus::showing:
    {
        index = path.size() - 1;
        break;
    }

    case OnScreenHelpStatus::exitting:
    {
        if (index > 0)
        {
            index--;
        }
        else
        {
            state = OnScreenHelpStatus::hidden;
        }
        break;
    }

    case OnScreenHelpStatus::entering:
    {
        if (index < (int)path.size() - 1)
        {
            index++;
        }
        else
        {
            state = OnScreenHelpStatus::showing;
        }
        break;
    }

    default:
        break;
    }

    dest.x = path[index];
}

// Rellena los puntos por donde pasa la animación
void OnScreenHelp::precalculatePath(double start, double end, int steps)
{
    path.reserve(steps);

    for (int i = 0; i <= steps; ++i)
    {
        double t = static_cast<double>(i) / steps;
        double value = start + (end - start) * easeInOutSine(t);
        path.push_back(static_cast<int>(value));
    }
}