#include <renderer/renderer.hpp>
#include <algorithm>
#include <utils.hpp>
#include <state/gamestate.hpp>
#include <object/entity.hpp>
#include <window/window.hpp>
#include <object/camera.hpp>

namespace Game::Renderer {
    Renderer::Renderer() : mRenderer(nullptr) {}

    Renderer::~Renderer() {
        destroy();
    }

    void Renderer::destroy() {
        if (mRenderer) {
            SDL_DestroyRenderer(mRenderer);
            mRenderer = nullptr;
            LOG("Renderer uničen");
        }
    }

    bool Renderer::init(SDL_Window* window) {
        // Request VSync before/at renderer setup; some backends honor this hint.
        SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");

        mRenderer = SDL_CreateRenderer(window, nullptr);
        if (!mRenderer) {
            std::string errorMsg = std::string("Neuspešno ustvarjanje rendererja: ") + std::string(SDL_GetError());
            ERROR(errorMsg.c_str());
            return false;
        }

        mVSyncEnabled = SDL_SetRenderVSync(mRenderer, 1);
        if (!mVSyncEnabled) {
            WARN("VSync ni mogoče omogočiti, uporabljam programsko omejitev okvirjev: " << SDL_GetError());
        }
        
        if (!SDL_SetRenderDrawColor(mRenderer, 0, 0, 255, 255)) {
            ERROR("Neuspelo nastavitev barve rendererja: " << SDL_GetError());
            return false;
        }

        LOG("Renderer uspešno ustvarjen");
        
        return true;
    }

    void Renderer::renderFrame() {
        mClear();
        
        float camX, camY;
        Object::Camera::getInstance().getPosition(camX, camY);
        int screenW, screenH;
        SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &screenW, &screenH);
        // Pass the config to avoid wasting time recalculating it for every entity, since it's not gonna change during the frame
        RendererConfig config{ camX, camY, screenW, screenH };

        try {
            Game::State::GameState::getInstance().withEntitiesLocked([&](auto& entities) {
                std::vector<Game::Object::Entity*> renderOrder;
                renderOrder.reserve(entities.size());
                for (auto& [name, entity] : entities) {
                    (void)name;
                    if (entity) {
                        renderOrder.push_back(entity.get());
                    }
                }

                std::sort(renderOrder.begin(), renderOrder.end(), [](Game::Object::Entity* a, Game::Object::Entity* b) {
                    return a->getZIndex() < b->getZIndex();
                });

                for (auto* entity : renderOrder) {
                    if (entity && entity->isActive()) {
                        entity->render(this, config);
                    }
                }
            });
        } catch (const std::exception& e) {
            ERROR("Exception while rendering frame: " << e.what());
        }

        mPresent();
    }

    void Renderer::mClear() {
        if (!SDL_RenderClear(mRenderer)) {
            ERROR("Failed to clear renderer: " << SDL_GetError());
        }
    }

    void Renderer::mPresent() {
        if (!SDL_RenderPresent(mRenderer)) {
            ERROR("Failed to present renderer: " << SDL_GetError());
        }
    }
}