#include "on_screen_keyboard.h"
// #include <iostream>

// Constructor
OnScreenKeyboard::OnScreenKeyboard(SDL_Renderer *renderer, Input *input, string bitmapFile, string textFile, options_t *options, int width, int height, string output, color_t color)
{
    // Inicializa variables
    this->renderer = renderer;
    this->options = options;
    this->input = input;
    this->width = width;
    this->height = height;

    // Crea objetos
    text = new Text(textFile, bitmapFile, renderer);
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height);
    textureCursor = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, text->getCharacterSize() * 2, text->getCharacterSize() * 2);
    SDL_SetTextureBlendMode(textureCursor, SDL_BLENDMODE_BLEND);

    // Inicializa variables
    this->output = output;
    char_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char_lower = "abcdefghijklmnopqrstuvwxyz";
    char_numbers = "0123456789";
    char_symbol = " !\"#$%&'()*+,-./:;<=>?@[]";
    use_char_upper = true;
    use_char_lower = false;
    use_char_numbers = false;
    use_char_symbol = false;
    setLayout();
    caption = "";
    dest = {(options->screen.nativeWidth - width) / 2, (options->screen.nativeHeight - height) / 2, width, height};
    cursor.col = 0;
    cursor.row = 0;
    cursor.dest = {0, 0, 16, 16};

    // Rellena la textura de fondo con el color y el texto
    fillTexture();

    // Rellena la textura del cursor
    SDL_SetRenderTarget(renderer, textureCursor);
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 128);
    SDL_RenderClear(renderer);
    SDL_SetRenderTarget(renderer, nullptr);
}

// Destructor
OnScreenKeyboard::~OnScreenKeyboard()
{
    // Libera la memoria de los objetos
    if (text != nullptr)
    {
        delete text;
    }

    if (texture != nullptr)
    {
        SDL_DestroyTexture(texture);
    }
}

// Rellena la textura de fondo con el color y el texto
void OnScreenKeyboard::fillTexture()
{
    // Destruye la textura si la hubiera
    if (texture)
        SDL_DestroyTexture(texture);

    // Crea la textura
    height = (text->getCharacterSize() * 4) + (text->getCharacterSize() * rows * 2);
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height);
    dest = {(options->screen.nativeWidth - width) / 2, (options->screen.nativeHeight - height) / 2, width, height};

    // Empieza a dibujar en la textura
    SDL_SetRenderTarget(renderer, texture);

    // Establece el color de fondo
    SDL_SetRenderDrawColor(renderer, bgColor.r, bgColor.g, bgColor.b, 255);
    SDL_RenderClear(renderer);

    // Escribe el texto que aparece en el objeto
    text->write(text->getCharacterSize(), text->getCharacterSize(), caption);

    // Dibuja el cuadro donde va el texto que se escribirá
    const color_t darkColor = darkenColor(bgColor, 10);
    SDL_SetRenderDrawColor(renderer, darkColor.r, darkColor.g, darkColor.b, 255);
    const int x_rect = (text->getCharacterSize() * 2) + text->lenght(caption);
    const int y_rect = text->getCharacterSize();
    const int w_rect = width - text->getCharacterSize() - x_rect;
    const int h_rect = text->getCharacterSize();
    outputDest = {x_rect, y_rect, w_rect, h_rect};
    SDL_RenderFillRect(renderer, &outputDest);

    // Dibuja los caracteres que conformaran el teclado
    const int dist = text->getCharacterSize() * 2;
    const int offset_x = text->getCharacterSize();
    const int offset_y = text->getCharacterSize() * 3;
    for (int j = 0; j < rows; ++j)
        for (int i = 0; i < columns; ++i)
        {
            const int pos = i + (j * columns);
            if (pos < (int)layout.size())
            {
                // color_t color = randColor();
                // SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 255);
                // SDL_Rect rect = {offset_x + (layout.at(pos).col * dist), offset_y + (layout.at(pos).row * dist), dist, dist};
                // SDL_RenderFillRect(renderer, &rect);
                text->write(offset_x + (layout.at(pos).col * dist) + 4, offset_y + (layout.at(pos).row * dist) + 4, layout.at(pos).caption);
            }
        }

    // Deja de dibujar en la textura
    SDL_SetRenderTarget(renderer, nullptr);
}

// Calcula cuantos caracteres se utilizaran para crear el teclado
int OnScreenKeyboard::getTotalChars()
{
    int total = 0;
    total += use_char_upper ? char_upper.length() : 0;
    total += use_char_lower ? char_lower.length() : 0;
    total += use_char_numbers ? char_numbers.length() : 0;
    total += use_char_symbol ? char_symbol.length() : 0;
    return total;
}

// Calcula cuantas columnas necesita el teclado
int OnScreenKeyboard::getColumns()
{
    const int keyWidth = text->getCharacterSize() * 2;
    int rowWidth = width - text->getCharacterSize();

    return rowWidth / keyWidth;
}

// Calcula cuantas filas necesita el teclado
int OnScreenKeyboard::getRows()
{
    return (totalChars / getColumns()) + 1;
}

// Establece la disposición del teclado
void OnScreenKeyboard::setLayout()
{
    totalChars = getTotalChars();
    columns = getColumns();
    rows = getRows();
    int index_col = 0;
    int index_row = 0;

    // Establece los caracteres a escribir
    string allChars = "";
    allChars = use_char_upper ? allChars + char_upper : allChars;
    allChars = use_char_lower ? allChars + char_lower : allChars;
    allChars = use_char_numbers ? allChars + char_numbers : allChars;
    allChars = use_char_symbol ? allChars + char_symbol : allChars;

    layout.clear();
    for (int i = 0; i < allChars.length(); ++i)
    {
        key_t key;
        key.col = index_col;
        key.row = index_row;
        key.caption = allChars.substr(i, 1);
        layout.push_back(key);

        // std::cout << key.caption << " " << key.col << " " << key.row << " " << std::endl;

        ++index_col;
        if (index_col == columns)
        {
            index_col = 0;
            ++index_row;
        }
    }
}

// Comprueba las entradas
void OnScreenKeyboard::checkInput()
{
    if (input->checkInput(INPUT_LEFT, REPEAT_FALSE))
        decCursorCol();

    if (input->checkInput(INPUT_RIGHT, REPEAT_FALSE))
        incCursorCol();

    if (input->checkInput(INPUT_UP, REPEAT_FALSE))
        decCursorRow();

    if (input->checkInput(INPUT_DOWN, REPEAT_FALSE))
        incCursorRow();

    if (input->checkInput(INPUT_ACCEPT, REPEAT_FALSE))
        addChar();

    if (input->checkInput(INPUT_CANCEL, REPEAT_FALSE))
        removeChar();
}

// Disminuye en una columna la posición del cursor
void OnScreenKeyboard::decCursorCol()
{
    cursor.col = cursor.col > 0 ? --cursor.col : columns - 1;
}

// Incrementa en una columna la posición del cursor
void OnScreenKeyboard::incCursorCol()
{
    ++cursor.col %= columns;
}

// Disminuye en una fila la posición del cursor
void OnScreenKeyboard::decCursorRow()
{
    cursor.row = cursor.row > 0 ? --cursor.row : rows - 1;
}

// Aumenta en una fila la posición del cursor
void OnScreenKeyboard::incCursorRow()
{
    ++cursor.row %= rows;
}

// Actualiza la posición de la textura del cursor
void OnScreenKeyboard::updateCursor()
{
    const int offset_x = text->getCharacterSize();
    const int offset_y = text->getCharacterSize() * 3;

    cursor.dest.x = dest.x + offset_x + (cursor.col * cursor.dest.w);
    cursor.dest.y = dest.y + offset_y + (cursor.row * cursor.dest.h);
}

// Añade un caracter a la salida
void OnScreenKeyboard::addChar()
{
    const int pos = cursor.col + cursor.row * columns;
    if (pos < layout.size())
        output = output + layout.at(pos).caption;
}

// Borra el último caracter de la salida
void OnScreenKeyboard::removeChar()
{
    if (output.length() > 0)
        output = output.substr(0, output.length() - 1);
}

// Actualiza la lógica del objeto
void OnScreenKeyboard::update()
{
    // Comprueba las entradas
    checkInput();

    // Actualiza la posición de la textura del cursor
    updateCursor();
}

// Dibuja el objeto en pantalla
void OnScreenKeyboard::render()
{
    SDL_RenderCopy(renderer, texture, nullptr, &dest);
    SDL_RenderCopy(renderer, textureCursor, nullptr, &cursor.dest);
    text->write(dest.x + outputDest.x, dest.y + outputDest.y, output);
}

// Establece el color de fondo
void OnScreenKeyboard::setBgColor(color_t color)
{
    bgColor = color;
    fillTexture();
}

// Establece el texto a mostrar junto al texto a introducir
void OnScreenKeyboard::setCaption(string text)
{
    caption = text;
    fillTexture();
}

// Establece qué caracteres ofrecerá el objeto
void OnScreenKeyboard::setChars(Uint8 mode)
{
    use_char_upper = ((mode & USE_UPPER) == USE_UPPER);
    use_char_lower = ((mode & USE_LOWER) == USE_LOWER);
    use_char_numbers = ((mode & USE_NUMBER) == USE_NUMBER);
    use_char_symbol = ((mode & USE_SYMBOL) == USE_SYMBOL);
    setLayout();
    fillTexture();
}