#include "core/rendering/screen.hpp"

#include <cstdio>
#include <iostream>

#include "core/rendering/overlay.hpp"
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
#include "game/defines.hpp"
#include "game/options.hpp"
#include "utils/utils.hpp"

Screen* Screen::instance_ = nullptr;

void Screen::init() {
    instance_ = new Screen();
}

void Screen::destroy() {
    delete instance_;
    instance_ = nullptr;
}

auto Screen::get() -> Screen* {
    return instance_;
}

Screen::Screen() {
    // Carrega opcions guardades
    zoom_ = Options::window.zoom;
    fullscreen_ = Options::window.fullscreen;

    calculateMaxZoom();

    if (zoom_ < 1) zoom_ = 1;
    if (zoom_ > max_zoom_) zoom_ = max_zoom_;

    int w = GAME_WIDTH * zoom_;
    int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;

    window_ = SDL_CreateWindow(Texts::WINDOW_TITLE, w, h, fullscreen_ ? SDL_WINDOW_FULLSCREEN : 0);
    renderer_ = SDL_CreateRenderer(window_, nullptr);
    SDL_SetRenderLogicalPresentation(renderer_, GAME_WIDTH, GAME_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);

    texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, GAME_WIDTH, GAME_HEIGHT);
    SDL_SetTextureScaleMode(texture_, SDL_SCALEMODE_NEAREST);

    // Inicialitza backend GPU si l'acceleració està activada
    initShaders();

    std::cout << "Screen initialized: " << w << "x" << h << " (zoom " << zoom_ << ", max " << max_zoom_ << ")\n";
}

Screen::~Screen() {
    // Guarda opcions abans de destruir
    Options::window.zoom = zoom_;
    Options::window.fullscreen = fullscreen_;

    // Destrueix el backend GPU
    if (shader_backend_) {
        auto* gpu = dynamic_cast<Rendering::SDL3GPUShader*>(shader_backend_.get());
        if (gpu) gpu->destroy();
        shader_backend_.reset();
    }

    if (texture_) SDL_DestroyTexture(texture_);
    if (renderer_) SDL_DestroyRenderer(renderer_);
    if (window_) SDL_DestroyWindow(window_);
}

void Screen::initShaders() {
    if (!Options::video.gpu_acceleration) return;

    shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();

    const std::string FALLBACK_DRIVER = "none";
    shader_backend_->setPreferredDriver(
        Options::video.gpu_acceleration ? "" : FALLBACK_DRIVER);

    // init() rep la finestra i la textura (la textura s'usa com a referència, el GPU fa uploadPixels)
    if (!shader_backend_->init(window_, texture_, "", "")) {
        std::cerr << "GPU shader backend initialization failed, using SDL_Renderer fallback\n";
        shader_backend_.reset();
        return;
    }

    gpu_driver_ = shader_backend_->getDriverName();
    std::cout << "GPU driver: " << gpu_driver_ << '\n';

    // Aplica opcions de vídeo
    shader_backend_->setScaleMode(Options::video.integer_scale);
    shader_backend_->setStretchFilter(Options::video.stretch_filter_linear);
    shader_backend_->setStretch4_3(Options::video.aspect_ratio_4_3);
    shader_backend_->setLinearUpscale(Options::video.linear_upscale);
    shader_backend_->setDownscaleAlgo(Options::video.downscale_algo);

    if (Options::video.supersampling) {
        shader_backend_->setOversample(3);
    }

    // Resol el shader actiu des del config
    if (Options::video.current_shader == "crtpi") {
        shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
    } else {
        shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
    }

    // Resol presets per nom
    for (int i = 0; i < static_cast<int>(Options::postfx_presets.size()); i++) {
        if (Options::postfx_presets[i].name == Options::video.current_postfx_preset) {
            Options::current_postfx_preset = i;
            break;
        }
    }
    for (int i = 0; i < static_cast<int>(Options::crtpi_presets.size()); i++) {
        if (Options::crtpi_presets[i].name == Options::video.current_crtpi_preset) {
            Options::current_crtpi_preset = i;
            break;
        }
    }

    applyCurrentPostFXPreset();
    applyCurrentCrtPiPreset();
}

void Screen::present(Uint32* pixel_data) {
    fps_.increment();
    fps_.calculate(SDL_GetTicks());
    updateRenderInfo();
    Overlay::render(pixel_data);

    if (shader_backend_ && shader_backend_->isHardwareAccelerated() && Options::video.shader_enabled) {
        // Path GPU: puja els píxels i renderitza amb shaders
        shader_backend_->uploadPixels(pixel_data, GAME_WIDTH, GAME_HEIGHT);
        shader_backend_->render();
    } else if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
        // GPU activa però shaders desactivats: renderitza net (sense efectes)
        Rendering::PostFXParams clean{};
        shader_backend_->setPostFXParams(clean);
        shader_backend_->uploadPixels(pixel_data, GAME_WIDTH, GAME_HEIGHT);
        shader_backend_->render();
    } else {
        // Fallback SDL_Renderer
        SDL_UpdateTexture(texture_, nullptr, pixel_data, GAME_WIDTH * sizeof(Uint32));
        SDL_RenderClear(renderer_);
        SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);
        SDL_RenderPresent(renderer_);
    }
}

void Screen::toggleFullscreen() {
    fullscreen_ = !fullscreen_;
    SDL_SetWindowFullscreen(window_, fullscreen_);
    if (!fullscreen_) {
        adjustWindowSize();
    }
}

void Screen::incZoom() {
    if (fullscreen_ || zoom_ >= max_zoom_) return;
    zoom_++;
    adjustWindowSize();
}

void Screen::decZoom() {
    if (fullscreen_ || zoom_ <= 1) return;
    zoom_--;
    adjustWindowSize();
}

void Screen::setZoom(int zoom) {
    if (zoom < 1 || zoom > max_zoom_ || fullscreen_) return;
    zoom_ = zoom;
    adjustWindowSize();
}

void Screen::toggleShaders() {
    Options::video.shader_enabled = !Options::video.shader_enabled;
    if (Options::video.shader_enabled) {
        applyCurrentPostFXPreset();
    }
}

void Screen::toggleSupersampling() {
    if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;
    Options::video.supersampling = !Options::video.supersampling;
    shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
}

void Screen::toggleAspectRatio() {
    Options::video.aspect_ratio_4_3 = !Options::video.aspect_ratio_4_3;
    if (shader_backend_) {
        shader_backend_->setStretch4_3(Options::video.aspect_ratio_4_3);
    }
    if (!fullscreen_) {
        adjustWindowSize();
    }
}

void Screen::toggleIntegerScale() {
    Options::video.integer_scale = !Options::video.integer_scale;
    if (shader_backend_) {
        shader_backend_->setScaleMode(Options::video.integer_scale);
    }
}

void Screen::toggleStretchFilter() {
    Options::video.stretch_filter_linear = !Options::video.stretch_filter_linear;
    if (shader_backend_) {
        shader_backend_->setStretchFilter(Options::video.stretch_filter_linear);
    }
}

void Screen::nextShaderType() {
    if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;

    if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
        shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
        Options::video.current_shader = "crtpi";
        applyCurrentCrtPiPreset();
    } else {
        shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
        Options::video.current_shader = "postfx";
        applyCurrentPostFXPreset();
    }
}

void Screen::nextPreset() {
    if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;

    if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
        if (Options::postfx_presets.empty()) return;
        Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
        Options::video.current_postfx_preset = Options::postfx_presets[Options::current_postfx_preset].name;
        applyCurrentPostFXPreset();
    } else {
        if (Options::crtpi_presets.empty()) return;
        Options::current_crtpi_preset = (Options::current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
        Options::video.current_crtpi_preset = Options::crtpi_presets[Options::current_crtpi_preset].name;
        applyCurrentCrtPiPreset();
    }
}

void Screen::prevShaderType() {
    // Només dues opcions — prev == next
    nextShaderType();
}

void Screen::prevPreset() {
    if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;

    if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
        if (Options::postfx_presets.empty()) return;
        int n = static_cast<int>(Options::postfx_presets.size());
        Options::current_postfx_preset = (Options::current_postfx_preset - 1 + n) % n;
        Options::video.current_postfx_preset = Options::postfx_presets[Options::current_postfx_preset].name;
        applyCurrentPostFXPreset();
    } else {
        if (Options::crtpi_presets.empty()) return;
        int n = static_cast<int>(Options::crtpi_presets.size());
        Options::current_crtpi_preset = (Options::current_crtpi_preset - 1 + n) % n;
        Options::video.current_crtpi_preset = Options::crtpi_presets[Options::current_crtpi_preset].name;
        applyCurrentCrtPiPreset();
    }
}

auto Screen::getCurrentPresetName() const -> const char* {
    if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return "---";
    if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
        if (Options::current_postfx_preset < static_cast<int>(Options::postfx_presets.size()))
            return Options::postfx_presets[Options::current_postfx_preset].name.c_str();
    } else {
        if (Options::current_crtpi_preset < static_cast<int>(Options::crtpi_presets.size()))
            return Options::crtpi_presets[Options::current_crtpi_preset].name.c_str();
    }
    return "---";
}

void Screen::setActiveShader(Rendering::ShaderType type) {
    if (shader_backend_) {
        shader_backend_->setActiveShader(type);
    }
}

void Screen::applyCurrentPostFXPreset() {
    if (!shader_backend_ || Options::postfx_presets.empty()) return;
    const auto& preset = Options::postfx_presets[Options::current_postfx_preset];
    Rendering::PostFXParams p;
    p.vignette = preset.vignette;
    p.scanlines = preset.scanlines;
    p.chroma = preset.chroma;
    p.mask = preset.mask;
    p.gamma = preset.gamma;
    p.curvature = preset.curvature;
    p.bleeding = preset.bleeding;
    p.flicker = preset.flicker;
    shader_backend_->setPostFXParams(p);
}

void Screen::applyCurrentCrtPiPreset() {
    if (!shader_backend_ || Options::crtpi_presets.empty()) return;
    const auto& preset = Options::crtpi_presets[Options::current_crtpi_preset];
    Rendering::CrtPiParams p;
    p.scanline_weight = preset.scanline_weight;
    p.scanline_gap_brightness = preset.scanline_gap_brightness;
    p.bloom_factor = preset.bloom_factor;
    p.input_gamma = preset.input_gamma;
    p.output_gamma = preset.output_gamma;
    p.mask_brightness = preset.mask_brightness;
    p.curvature_x = preset.curvature_x;
    p.curvature_y = preset.curvature_y;
    p.mask_type = preset.mask_type;
    p.enable_scanlines = preset.enable_scanlines;
    p.enable_multisample = preset.enable_multisample;
    p.enable_gamma = preset.enable_gamma;
    p.enable_curvature = preset.enable_curvature;
    p.enable_sharper = preset.enable_sharper;
    shader_backend_->setCrtPiParams(p);
}

auto Screen::isHardwareAccelerated() const -> bool {
    return shader_backend_ && shader_backend_->isHardwareAccelerated();
}

auto Screen::getActiveShaderName() const -> const char* {
    if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return "SENSE GPU";
    return shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX ? "POSTFX" : "CRT-PI";
}

void Screen::updateRenderInfo() {
    static const Uint32 start_ticks = SDL_GetTicks();
    std::string driver = gpu_driver_.empty() ? "sdl" : toLower(gpu_driver_);
    std::string info = std::to_string(fps_.last_value) + " fps - " + driver;

    if (Options::video.shader_enabled) {
        std::string shader_name = toLower(getActiveShaderName());
        std::string preset_name = toLower(getCurrentPresetName());
        info += " - " + shader_name + " " + preset_name;
        if (Options::video.supersampling) info += " (ss)";
    }

    // Temps de joc: m:ss.cc
    Uint32 elapsed = SDL_GetTicks() - start_ticks;
    int minutes = elapsed / 60000;
    int seconds = (elapsed / 1000) % 60;
    int centis = (elapsed / 10) % 100;
    char time_buf[32];
    snprintf(time_buf, sizeof(time_buf), " - %d:%02d.%02d", minutes, seconds, centis);
    info += time_buf;

    Overlay::setRenderInfoText(info.c_str());
}

void Screen::adjustWindowSize() {
    int w = GAME_WIDTH * zoom_;
    // Si 4:3 actiu, l'alçada visual és 240 per zoom (200 * 1.2)
    int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;
    SDL_SetWindowSize(window_, w, h);
    SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}

void Screen::calculateMaxZoom() {
    SDL_DisplayID display = SDL_GetPrimaryDisplay();
    const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
    if (mode) {
        int max_w = mode->w / GAME_WIDTH;
        int max_h = mode->h / GAME_HEIGHT;
        max_zoom_ = (max_w < max_h) ? max_w : max_h;
        if (max_zoom_ < 1) max_zoom_ = 1;
    }
}
