#include <window/window.hpp>

#include <algorithm>

namespace Game::Window {
    std::mutex Window::sMutex;

    Window::Window() : mWindow(nullptr), mRenderer(), mGameManager(), mRunning(false) { }

    Window::~Window() {
        mRenderer.destroy();

        // Try to kill the game slave thread
        if (mGameThread.joinable()) {
            mGameThread.request_stop();
            mGameThread.join();
            LOG("Game thread stopped successfully");
        }

        if (mWindow) {
            SDL_DestroyWindow(mWindow);
            mWindow = nullptr;
            sWindowBackend = nullptr;
            LOG("Window destroyed successfully");
        }
        SDL_Quit();
    }

    bool Window::init(int width, int height, const std::string& title) {
        if (!SDL_Init(SDL_INIT_VIDEO)) {
            ERROR("Failed to initialize SDL: " << SDL_GetError());
            return false;
        }

        if (!TTF_Init()) {
            ERROR("Failed to initialize SDL_ttf: " << SDL_GetError());
            SDL_Quit();
            return false;
        }

        Audio::Audio::getInstance().init();

        mWindow = SDL_CreateWindow(title.c_str(), width, height, SDL_WINDOW_RESIZABLE);
        if (!mWindow) {
            ERROR("Failed to create window: " << SDL_GetError());
            SDL_Quit();
            return false;
        }
        mLastWindowWidth = width;
        mLastWindowHeight = height;
        sWindowBackend = mWindow;

        LOG("Window created successfully");

        if (!mRenderer.init(mWindow)) {
            SDL_DestroyWindow(mWindow);
            mWindow = nullptr;
            sWindowBackend = nullptr;
            SDL_Quit();
            return false;
        }

#if ENABLE_LOW_LATENCY_VSYNC
        if (mRenderer.isVSyncEnabled()) {
            const int vsyncCap = std::max(1, mTargetFPS - VSYNC_FPS_OFFSET);
            mEffectiveFrameCap = vsyncCap;
            LOG("Low-latency VSync mode enabled. Target FPS: " << mTargetFPS << ", cap: " << mEffectiveFrameCap);
        } else {
            mEffectiveFrameCap = std::max(1, mTargetFPS);
            LOG("VSync unavailable, using software cap: " << mEffectiveFrameCap);
        }
#else
        mEffectiveFrameCap = std::max(1, mTargetFPS);
#endif

        mGameManager.setTargetUpdatesPerSecond(TARGET_UPDATE_RATE);
        LOG("Target updates per second: " << mGameManager.getTargetUpdatesPerSecond());

        mGameThread = std::jthread(std::bind_front(&Game::GameManager::run, &mGameManager));

        mRunning = true;

        return true;
    }

    void Window::run() {
        SDL_Event event;
        while (mRunning) {
            const auto frameStart = std::chrono::steady_clock::now();

            SDL_PumpEvents();
            while (SDL_PollEvent(&event)) {
                if (event.type == SDL_EVENT_QUIT) {
                    mRunning = false;
                }

                // Window resize event - update the renderer's viewport
                if (event.type == SDL_EVENT_WINDOW_RESIZED) {
                    std::scoped_lock lock(mMutex);
                    SDL_SetRenderViewport(mRenderer.getSDLRenderer(), nullptr);

                    int newWidth, newHeight;
                    SDL_GetWindowSizeInPixels(mWindow, &newWidth, &newHeight);

                    const int oldWidth = mLastWindowWidth;
                    const int oldHeight = mLastWindowHeight;
                    const bool canScale = oldWidth > 0 && oldHeight > 0;
                    const float scaleX = canScale ? static_cast<float>(newWidth) / static_cast<float>(oldWidth) : 1.f;
                    const float scaleY = canScale ? static_cast<float>(newHeight) / static_cast<float>(oldHeight) : 1.f;

                    auto entities = State::GameState::getInstance().getEntitiesSnapshot();
                    for (auto* entity : entities) {
                        if (entity) {
                            if (canScale) {
                                Object::Transform* transform = entity->getTransform();
                                transform->x *= scaleX;
                                transform->y *= scaleY;
                            }
                            entity->onWindowResized(newWidth, newHeight);
                        }
                    }

                    mLastWindowWidth = newWidth;
                    mLastWindowHeight = newHeight;
                }
            }

            mRenderer.renderFrame();

            // Keep rendering slightly below refresh in low-latency mode to reduce queueing/input delay.
            const int capFps = std::max(1, mEffectiveFrameCap);
            const auto targetFrameDuration = std::chrono::duration<double>(1.0 / static_cast<double>(capFps));
            const auto elapsed = std::chrono::steady_clock::now() - frameStart;
            const auto remaining = targetFrameDuration - elapsed;
            if (remaining > std::chrono::duration<double>::zero()) {
                std::this_thread::sleep_for(remaining);
            }

            #ifdef DEBUG
            const auto frameEnd = std::chrono::steady_clock::now();
            const double frameMs = std::chrono::duration<double, std::milli>(frameEnd - frameStart).count();
            mTelemetryFrameTimeMsTotal += frameMs;
            mTelemetryFrameCount++;

            const auto telemetryElapsed = std::chrono::duration_cast<std::chrono::seconds>(frameEnd - mTelemetryStart).count();
            if (telemetryElapsed >= 2 && mTelemetryFrameCount > 0) {
                const double avgFrameMs = mTelemetryFrameTimeMsTotal / static_cast<double>(mTelemetryFrameCount);
                const double avgFps = 1000.0 / std::max(0.001, avgFrameMs);
                LOG("Frame telemetry: avg ms=" << avgFrameMs << ", avg fps=" << avgFps << ", cap=" << mEffectiveFrameCap << ", vsync=" << (mRenderer.isVSyncEnabled() ? "on" : "off"));

                mTelemetryFrameTimeMsTotal = 0.0;
                mTelemetryFrameCount = 0;
                mTelemetryStart = frameEnd;
            }
            #endif
        }
    }
}