#include "utils.h"
#include <SDL2/SDL_rwops.h> // Para SDL_RWFromFile, SDL_RWclose, SDL_RWread
#include <algorithm>        // Para min, clamp, find_if_not, find, transform
#include <cctype>           // Para tolower, isspace
#include <cmath>            // Para cos, pow, M_PI
#include <compare>          // Para operator<
#include <filesystem>       // Para path
#include <iostream>         // Para basic_ostream, cout, basic_ios, endl, ios
#include <stdexcept>        // Para runtime_error
#include <string>           // Para basic_string, string, char_traits, opera...

// Variables
Overrides overrides = Overrides();

// Colores
const Color bg_color = Color(0x27, 0x27, 0x36);
const Color no_color = Color(0xFF, 0xFF, 0xFF);
const Color shdw_txt_color = Color(0x43, 0x43, 0x4F);
const Color separator_color = Color(0x0D, 0x1A, 0x2B);
const Color scoreboard_easy_color = Color(0x4B, 0x69, 0x2F);
const Color scoreboard_normal_color = Color(0x2E, 0x3F, 0x47);
const Color scoreboard_hard_color = Color(0x76, 0x42, 0x8A);
const Color flash_color = Color(0xFF, 0xFF, 0xFF);
const Color fade_color = Color(0x27, 0x27, 0x36);
const Color orange_color = Color(0xFF, 0x7A, 0x00);

// Calcula el cuadrado de la distancia entre dos puntos
double distanceSquared(int x1, int y1, int x2, int y2)
{
    const int delta_x = x2 - x1;
    const int delta_y = y2 - y1;
    return delta_x * delta_x + delta_y * delta_y;
}

// Detector de colisiones entre dos circulos
bool checkCollision(const Circle &a, const Circle &b)
{
    // Calcula el radio total al cuadrado
    int total_radius_squared = (a.r + b.r) * (a.r + b.r);

    // Comprueba si la distancia entre los centros de los círculos es inferior a la suma de sus radios
    return distanceSquared(a.x, a.y, b.x, b.y) < total_radius_squared;
}

// Detector de colisiones entre un circulo y un rectangulo
bool checkCollision(const Circle &a, const SDL_Rect &b)
{
    // Encuentra el punto más cercano en el rectángulo
    int cX = std::clamp(a.x, b.x, b.x + b.w);
    int cY = std::clamp(a.y, b.y, b.y + b.h);

    // Si el punto más cercano está dentro del círculo
    return distanceSquared(a.x, a.y, cX, cY) < a.r * a.r;
}

// Detector de colisiones entre dos rectangulos
bool checkCollision(const SDL_Rect &a, const SDL_Rect &b)
{
    const int leftA = a.x, rightA = a.x + a.w, topA = a.y, bottomA = a.y + a.h;
    const int leftB = b.x, rightB = b.x + b.w, topB = b.y, bottomB = b.y + b.h;

    if (bottomA <= topB)
        return false;
    if (topA >= bottomB)
        return false;
    if (rightA <= leftB)
        return false;
    if (leftA >= rightB)
        return false;

    return true;
}

// Detector de colisiones entre un punto y un rectangulo
bool checkCollision(const SDL_Point &p, const SDL_Rect &r)
{
    if (p.x < r.x || p.x > r.x + r.w)
        return false;
    if (p.y < r.y || p.y > r.y + r.h)
        return false;
    return true;
}

// Convierte una cadena en un valor booleano
bool stringToBool(const std::string &str)
{
    return str == "true";
}

// Convierte un valor booleano en una cadena
std::string boolToString(bool value)
{
    return value ? "true" : "false";
}

// Convierte un valor booleano en una cadena "on" o "off"
std::string boolToOnOff(bool value)
{
    return value ? "on" : "off";
}

// Convierte una cadena a minusculas
std::string toLower(const std::string &str)
{
    std::string result = str;
    std::transform(result.begin(), result.end(), result.begin(),
                   [](unsigned char c)
                   { return std::tolower(c); });
    return result;
}

// Dibuja un circulo
void DrawCircle(SDL_Renderer *renderer, int32_t centerX, int32_t centerY, int32_t radius)
{
    const int32_t diameter = (radius * 2);

    int32_t x = (radius - 1);
    int32_t y = 0;
    int32_t tx = 1;
    int32_t ty = 1;
    int32_t error = (tx - diameter);

    while (x >= y)
    {
        //  Each of the following renders an octant of the circle
        SDL_RenderDrawPoint(renderer, centerX + x, centerY - y);
        SDL_RenderDrawPoint(renderer, centerX + x, centerY + y);
        SDL_RenderDrawPoint(renderer, centerX - x, centerY - y);
        SDL_RenderDrawPoint(renderer, centerX - x, centerY + y);
        SDL_RenderDrawPoint(renderer, centerX + y, centerY - x);
        SDL_RenderDrawPoint(renderer, centerX + y, centerY + x);
        SDL_RenderDrawPoint(renderer, centerX - y, centerY - x);
        SDL_RenderDrawPoint(renderer, centerX - y, centerY + x);

        if (error <= 0)
        {
            ++y;
            error += ty;
            ty += 2;
        }

        if (error > 0)
        {
            --x;
            tx += 2;
            error += (tx - diameter);
        }
    }
}

// Aclara el color
Color lightenColor(const Color &color, int amount)
{
    Color newColor;
    newColor.r = std::min(255, color.r + amount);
    newColor.g = std::min(255, color.g + amount);
    newColor.b = std::min(255, color.b + amount);
    return newColor;
}

// Oscurece el color
Color DarkenColor(const Color &color, int amount)
{
    Color newColor;
    newColor.r = std::min(255, color.r - +amount);
    newColor.g = std::min(255, color.g - +amount);
    newColor.b = std::min(255, color.b - +amount);
    return newColor;
}

// Quita los espacioes en un string
std::string trim(const std::string &str)
{
    auto start = std::find_if_not(str.begin(), str.end(), ::isspace);
    auto end = std::find_if_not(str.rbegin(), str.rend(), ::isspace).base();
    return (start < end ? std::string(start, end) : std::string());
}

// Función de suavizado
double easeOutQuint(double t)
{
    return 1 - std::pow(1 - t, 5);
}

// Función de suavizado
double easeInQuint(double t)
{
    return pow(t, 5);
}

// Función de suavizado
double easeInOutQuint(double t)
{
    return t < 0.5 ? 16 * pow(t, 5) : 1 - pow(-2 * t + 2, 5) / 2;
}

// Función de suavizado
double easeInQuad(double t)
{
    return t * t;
}

// Función de suavizado
double easeOutQuad(double t)
{
    return 1 - (1 - t) * (1 - t);
}

// Función de suavizado
double easeInOutSine(double t)
{
    return -0.5 * (std::cos(M_PI * t) - 1);
}

// Comprueba si una vector contiene una cadena
bool stringInVector(const std::vector<std::string> &vec, const std::string &str)
{
    return std::find(vec.begin(), vec.end(), str) != vec.end();
}

// Imprime por pantalla una linea de texto de tamaño fijo rellena con puntos
void printWithDots(const std::string &text1, const std::string &text2, const std::string &text3)
{
    std::cout.setf(std::ios::left, std::ios::adjustfield);
    std::cout << text1;

    std::cout.width(50 - text1.length() - text3.length());
    std::cout.fill('.');
    std::cout << text2;

    std::cout << text3 << std::endl;
}

// Carga el fichero de datos para la demo
DemoData loadDemoDataFromFile(const std::string &file_path)
{
    DemoData dd;

    // Indicador de éxito en la carga
    auto file = SDL_RWFromFile(file_path.c_str(), "r+b");
    if (!file)
    {
        std::cerr << "Error: Fichero no encontrado " << file_path << std::endl;
        throw std::runtime_error("Fichero no encontrado: " + file_path);
    }
    else
    {
        printWithDots("DemoData : ", getFileName(file_path), "[ LOADED ]");

        // Lee todos los datos del fichero y los deja en el destino
        for (int i = 0; i < TOTAL_DEMO_DATA; ++i)
        {
            DemoKeys dk = DemoKeys();
            SDL_RWread(file, &dk, sizeof(DemoKeys), 1);
            dd.push_back(dk);
        }

        // Cierra el fichero
        SDL_RWclose(file);
    }

    return dd;
}

#ifdef RECORDING
// Guarda el fichero de datos para la demo
bool saveDemoFile(const std::string &file_path, const DemoData &dd)
{
    auto success = true;
    auto file = SDL_RWFromFile(file_path.c_str(), "w+b");

    if (file)
    {
        // Guarda los datos
        for (const auto &data : dd)
        {
            if (SDL_RWwrite(file, &data, sizeof(DemoKeys), 1) != 1)
            {
                std::cerr << "Error al escribir el fichero " << getFileName(file_path) << std::endl;
                success = false;
                break;
            }
        }
        if (success)
        {
            std::cout << "Writing file " << getFileName(file_path).c_str() << std::endl;
        }
        // Cierra el fichero
        SDL_RWclose(file);
    }
    else
    {
        std::cout << "Error: Unable to save " << getFileName(file_path).c_str() << " file! " << SDL_GetError() << std::endl;
        success = false;
    }

    return success;
}
#endif // RECORDING

// Obtiene el nombre de un fichero a partir de una ruta completa
std::string getFileName(const std::string &path)
{
    return std::filesystem::path(path).filename().string();
}

// Obtiene la ruta eliminando el nombre del fichero
std::string getPath(const std::string &full_path)
{
    std::filesystem::path path(full_path);
    return path.parent_path().string();
}
