#include "jail_shader.h"
#include <SDL3/SDL_log.h>  // Para SDL_LogCategory, SDL_LogError, SDL_LogWarn
#include <SDL3/SDL_rect.h> // Para SDL_FPoint, SDL_Point
#include <cstring>         // Para strncmp
#include <stdexcept>       // Para runtime_error
#include <vector>          // Para vector

#ifdef __APPLE__
#include "CoreFoundation/CoreFoundation.h" // Para Core Foundation en macOS
#include <OpenGL/OpenGL.h>                 // Para OpenGL en macOS
#if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
#include <OpenGL/gl3.h>      // Para OpenGL 3 en macOS
#else                        // NO ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
#include <OpenGL/gl.h>       // Para OpenGL (compatibilidad) en macOS
#endif                       // ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
#else                        // SI NO ES __APPLE__
#include <SDL3/SDL_opengl.h> // Para GLuint, glTexCoord2f, glVertex2f, GLfloat
#endif                       // __APPLE__

namespace shader
{
    SDL_Window *win = nullptr;
    SDL_Renderer *renderer = nullptr;
    GLuint programId = 0;
    SDL_Texture *backBuffer = nullptr;
    SDL_Point win_size = {320 * 4, 256 * 4};
    SDL_FPoint tex_size = {320, 256};
    bool usingOpenGL = false;

#ifndef __APPLE__
    // Declaración de funciones de extensión de OpenGL (evitando GLEW)
    PFNGLCREATESHADERPROC glCreateShader;
    PFNGLSHADERSOURCEPROC glShaderSource;
    PFNGLCOMPILESHADERPROC glCompileShader;
    PFNGLGETSHADERIVPROC glGetShaderiv;
    PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
    PFNGLDELETESHADERPROC glDeleteShader;
    PFNGLATTACHSHADERPROC glAttachShader;
    PFNGLCREATEPROGRAMPROC glCreateProgram;
    PFNGLLINKPROGRAMPROC glLinkProgram;
    PFNGLVALIDATEPROGRAMPROC glValidateProgram;
    PFNGLGETPROGRAMIVPROC glGetProgramiv;
    PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
    PFNGLUSEPROGRAMPROC glUseProgram;

    bool initGLExtensions()
    {
        glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
        glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource");
        glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
        glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv");
        glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog");
        glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader");
        glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
        glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
        glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
        glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)SDL_GL_GetProcAddress("glValidateProgram");
        glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv");
        glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog");
        glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");

        return glCreateShader && glShaderSource && glCompileShader && glGetShaderiv &&
               glGetShaderInfoLog && glDeleteShader && glAttachShader && glCreateProgram &&
               glLinkProgram && glValidateProgram && glGetProgramiv && glGetProgramInfoLog &&
               glUseProgram;
    }
#endif

    // Función para compilar un shader a partir de un std::string
    GLuint compileShader(const std::string &source, GLuint shaderType)
    {
        if (source.empty())
        {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR FATAL: El código fuente del shader está vacío.");
            throw std::runtime_error("ERROR FATAL: El código fuente del shader está vacío.");
        }

        // Crear identificador del shader
        GLuint resultado = glCreateShader(shaderType);

        // Agregar una directiva según el tipo de shader
        std::string directiva = (shaderType == GL_VERTEX_SHADER)
                                    ? "#define VERTEX\n"
                                    : "#define FRAGMENT\n";

        const char *sources[2] = {directiva.c_str(), source.c_str()};

        // Especificar el código fuente del shader
        glShaderSource(resultado, 2, sources, nullptr);

        // Compilar el shader
        glCompileShader(resultado);

        // Verificar si la compilación fue exitosa
        GLint compiladoCorrectamente = GL_FALSE;
        glGetShaderiv(resultado, GL_COMPILE_STATUS, &compiladoCorrectamente);
        if (compiladoCorrectamente != GL_TRUE)
        {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error en la compilación del shader (%d)!", resultado);
            GLint longitudLog;
            glGetShaderiv(resultado, GL_INFO_LOG_LENGTH, &longitudLog);
            if (longitudLog > 0)
            {
                std::vector<GLchar> log(longitudLog);
                glGetShaderInfoLog(resultado, longitudLog, &longitudLog, log.data());
                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Registro de compilación del shader: %s", log.data());
            }
            glDeleteShader(resultado);
            resultado = 0;
        }
        return resultado;
    }

    // Función para compilar un programa de shaders (vertex y fragment) a partir de std::string
    GLuint compileProgram(const std::string &vertexShaderSource, const std::string &fragmentShaderSource)
    {
        GLuint idPrograma = glCreateProgram();

        // Si el fragment shader está vacío, reutilizamos el código del vertex shader
        GLuint idShaderVertice = compileShader(vertexShaderSource, GL_VERTEX_SHADER);
        GLuint idShaderFragmento = compileShader(fragmentShaderSource.empty() ? vertexShaderSource : fragmentShaderSource, GL_FRAGMENT_SHADER);

        if (idShaderVertice && idShaderFragmento)
        {
            // Asociar los shaders al programa
            glAttachShader(idPrograma, idShaderVertice);
            glAttachShader(idPrograma, idShaderFragmento);
            glLinkProgram(idPrograma);
            glValidateProgram(idPrograma);

            // Verificar el estado del enlace
            GLint longitudLog;
            glGetProgramiv(idPrograma, GL_INFO_LOG_LENGTH, &longitudLog);
            if (longitudLog > 0)
            {
                std::vector<char> log(longitudLog);
                glGetProgramInfoLog(idPrograma, longitudLog, &longitudLog, log.data());
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registro de información del programa:\n%s", log.data());
            }
        }
        if (idShaderVertice)
        {
            glDeleteShader(idShaderVertice);
        }
        if (idShaderFragmento)
        {
            glDeleteShader(idShaderFragmento);
        }
        return idPrograma;
    }

    bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &vertexShader, const std::string &fragmentShader)
    {
        shader::win = ventana;
        shader::renderer = SDL_GetRenderer(ventana);
        shader::backBuffer = texturaBackBuffer;
        SDL_GetWindowSize(ventana, &win_size.x, &win_size.y);

        SDL_GetTextureSize(texturaBackBuffer, &tex_size.x, &tex_size.y);

        const auto RENDER_NAME = SDL_GetRendererName(renderer);

        // Verificar que el renderer sea OpenGL
        if (!strncmp(RENDER_NAME, "opengl", 6))
        {
#ifndef __APPLE__
            if (!initGLExtensions())
            {
                SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ADVERTENCIA: No se han podido inicializar las extensiones de OpenGL.");
                usingOpenGL = false;
                return false;
            }
#endif
            // Compilar el programa de shaders utilizando std::string
            programId = compileProgram(vertexShader, fragmentShader);
        }
        else
        {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ADVERTENCIA: El driver del renderer no es OpenGL.");
            usingOpenGL = false;
            return false;
        }
        usingOpenGL = true;
        return true;
    }

    void render()
    {
        GLint oldProgramId;
        // Establece el color de fondo
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_SetRenderTarget(renderer, nullptr);
        SDL_RenderClear(renderer);

        if (usingOpenGL)
        {
            SDL_GetTextureProperties(backBuffer);
            // SDL_GL_BindTexture(backBuffer, nullptr, nullptr);
            if (programId != 0)
            {
                glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgramId);
                glUseProgram(programId);
            }

            // Recupera el tamaño lógico configurado con SDL_RenderSetLogicalSize
            int logicalW, logicalH;
            SDL_RendererLogicalPresentation mode;
            SDL_GetRenderLogicalPresentation(renderer, &logicalW, &logicalH, &mode);
            if (logicalW == 0 || logicalH == 0)
            {
                logicalW = win_size.x;
                logicalH = win_size.y;
            }

            // Cálculo del viewport
            int viewportX = 0, viewportY = 0, viewportW = win_size.x, viewportH = win_size.y;
            const bool USE_INTEGER_SCALE = mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE;
            if (USE_INTEGER_SCALE)
            {
                // Calcula el factor de escalado entero máximo que se puede aplicar
                int scaleX = win_size.x / logicalW;
                int scaleY = win_size.y / logicalH;
                int scale = (scaleX < scaleY ? scaleX : scaleY);
                if (scale < 1)
                {
                    scale = 1;
                }
                viewportW = logicalW * scale;
                viewportH = logicalH * scale;
                viewportX = (win_size.x - viewportW) / 2;
                viewportY = (win_size.y - viewportH) / 2;
            }
            else
            {
                // Letterboxing: preserva la relación de aspecto usando una escala flotante
                float windowAspect = static_cast<float>(win_size.x) / win_size.y;
                float logicalAspect = static_cast<float>(logicalW) / logicalH;
                if (windowAspect > logicalAspect)
                {
                    viewportW = static_cast<int>(logicalAspect * win_size.y);
                    viewportX = (win_size.x - viewportW) / 2;
                }
                else
                {
                    viewportH = static_cast<int>(win_size.x / logicalAspect);
                    viewportY = (win_size.y - viewportH) / 2;
                }
            }
            glViewport(viewportX, viewportY, viewportW, viewportH);

            // Configurar la proyección ortográfica usando el espacio lógico
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();

            // Queremos que el origen esté en la esquina superior izquierda del espacio lógico.
            glOrtho(0, static_cast<GLdouble>(logicalW), static_cast<GLdouble>(logicalH), 0, -1, 1);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();

            // Dibuja el quad con las coordenadas ajustadas.
            // Se asignan las coordenadas de textura "normales" para que no quede espejado horizontalmente,
            // y se mantiene el flip vertical para que la imagen no aparezca volteada.
            glBegin(GL_TRIANGLE_STRIP);
            // Vértice superior izquierdo
            glTexCoord2f(0.0f, 1.0f);
            glVertex2f(0.0f, 0.0f);
            // Vértice superior derecho
            glTexCoord2f(1.0f, 1.0f);
            glVertex2f(static_cast<GLfloat>(logicalW), 0.0f);
            // Vértice inferior izquierdo
            glTexCoord2f(0.0f, 0.0f);
            glVertex2f(0.0f, static_cast<GLfloat>(logicalH));
            // Vértice inferior derecho
            glTexCoord2f(1.0f, 0.0f);
            glVertex2f(static_cast<GLfloat>(logicalW), static_cast<GLfloat>(logicalH));
            glEnd();

            SDL_GL_SwapWindow(win);

            if (programId != 0)
            {
                glUseProgram(oldProgramId);
            }
        }
        else
        {
            SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
            SDL_RenderPresent(renderer);
        }
    }
}