#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/scene/scene.h>
#include <frostbite2D/scene/scene_manager.h>
#include <frostbite2D/scene/ui_scene.h>

namespace frostbite2D {

namespace {

RenderStyleProfileId resolveSceneRenderStyleProfile(const Scene* scene,
                                                    const Renderer& renderer) {
  if (!scene) {
    return renderer.getDefaultRenderStyleProfile();
  }
  return scene->ResolveRenderStyleProfile(renderer.getDefaultRenderStyleProfile());
}

} // namespace

SceneManager& SceneManager::get() {
  static SceneManager instance;
  return instance;
}

void SceneManager::PushScene(Ptr<Scene> scene) {
  if (!scene) {
    return;
  }

  if (!sceneStack_.empty()) {
    sceneStack_.back()->onExit();
  }

  sceneStack_.push_back(scene);
  Scene::current_ = scene.Get();
  scene->onEnter();
}

void SceneManager::PopScene() {
  if (sceneStack_.empty()) {
    return;
  }

  sceneStack_.back()->onExit();
  sceneStack_.pop_back();

  Scene::current_ = sceneStack_.empty() ? nullptr : sceneStack_.back().Get();
  if (!sceneStack_.empty()) {
    sceneStack_.back()->onEnter();
  }
}

void SceneManager::ReplaceScene(Ptr<Scene> scene) {
  if (!scene) {
    return;
  }

  if (!sceneStack_.empty()) {
    sceneStack_.back()->onExit();
    sceneStack_.pop_back();
  }

  sceneStack_.push_back(scene);
  Scene::current_ = scene.Get();
  scene->onEnter();
}

void SceneManager::PushUIScene(Ptr<UIScene> scene) {
  if (!scene) {
    return;
  }

  uiSceneStack_.push_back(scene);
  scene->onEnter();
}

void SceneManager::PopUIScene() {
  if (uiSceneStack_.empty()) {
    return;
  }

  uiSceneStack_.back()->onExit();
  uiSceneStack_.pop_back();
}

void SceneManager::ReplaceUIScene(Ptr<UIScene> scene) {
  if (!scene) {
    return;
  }

  if (!uiSceneStack_.empty()) {
    uiSceneStack_.back()->onExit();
    uiSceneStack_.pop_back();
  }

  uiSceneStack_.push_back(scene);
  scene->onEnter();
}

bool SceneManager::RemoveUIScene(UIScene* scene) {
  if (!scene) {
    return false;
  }

  for (auto it = uiSceneStack_.begin(); it != uiSceneStack_.end(); ++it) {
    if (it->Get() != scene) {
      continue;
    }

    (*it)->onExit();
    uiSceneStack_.erase(it);
    return true;
  }

  return false;
}

void SceneManager::ClearUIScenes() {
  while (!uiSceneStack_.empty()) {
    uiSceneStack_.back()->onExit();
    uiSceneStack_.pop_back();
  }
}

void SceneManager::ClearAll() {
  ClearUIScenes();

  while (!sceneStack_.empty()) {
    sceneStack_.back()->onExit();
    sceneStack_.pop_back();
  }

  Scene::current_ = nullptr;
}

void SceneManager::Update(float deltaTime) {
  if (!sceneStack_.empty()) {
    Renderer& renderer = Renderer::get();
    RenderStyleProfileId profile =
        resolveSceneRenderStyleProfile(sceneStack_.back().Get(), renderer);
    renderer.applyRenderStyleToCamera(renderer.getCamera(), profile,
                                      RenderStyleLayerRole::World);
    sceneStack_.back()->Update(deltaTime);
  }
}

void SceneManager::UpdateUI(float deltaTime) {
  Renderer& renderer = Renderer::get();
  for (auto& scene : uiSceneStack_) {
    if (scene) {
      scene->GetCamera()->setViewport(renderer.getUIVirtualWidth(),
                                      renderer.getUIVirtualHeight());
      RenderStyleProfileId profile =
          resolveSceneRenderStyleProfile(scene.Get(), renderer);
      renderer.applyRenderStyleToCamera(scene->GetCamera(), profile,
                                        RenderStyleLayerRole::UI);
      scene->Update(deltaTime);
    }
  }
}

void SceneManager::Render() {
  Renderer& renderer = Renderer::get();
  Camera* worldCamera = renderer.getCamera();
  RenderStyleProfileId worldProfile = renderer.getDefaultRenderStyleProfile();
  bool previousCullingEnabled = renderer.isCullingEnabled();

  if (!sceneStack_.empty()) {
    renderer.applyWorldViewport();
    renderer.setCullingEnabled(true);
    worldProfile =
        resolveSceneRenderStyleProfile(sceneStack_.back().Get(), renderer);
    renderer.applyRenderStyleToCamera(worldCamera, worldProfile,
                                      RenderStyleLayerRole::World);
    renderer.setActiveRenderStyleProfile(worldProfile,
                                         RenderStyleLayerRole::World);
    sceneStack_.back()->Render();
  }

  renderer.setCullingEnabled(false);
  for (auto& scene : uiSceneStack_) {
    if (!scene) {
      continue;
    }

    RenderStyleProfileId uiProfile =
        resolveSceneRenderStyleProfile(scene.Get(), renderer);
    scene->GetCamera()->setViewport(renderer.getUIVirtualWidth(),
                                    renderer.getUIVirtualHeight());
    renderer.applyRenderStyleToCamera(scene->GetCamera(), uiProfile,
                                      RenderStyleLayerRole::UI);
    renderer.setActiveRenderStyleProfile(uiProfile, RenderStyleLayerRole::UI);
    renderer.setCamera(scene->GetCamera());
    renderer.applyUIViewport();
    scene->Render();
  }

  if (renderer.getCamera() != worldCamera) {
    renderer.setActiveRenderStyleProfile(worldProfile, RenderStyleLayerRole::World);
    renderer.setCamera(worldCamera);
  }
  renderer.applyWorldViewport();
  renderer.setCullingEnabled(previousCullingEnabled);
  renderer.setActiveRenderStyleProfile(worldProfile, RenderStyleLayerRole::World);
}

bool SceneManager::DispatchEvent(const Event& event) {
  if (DispatchUIEvent(event)) {
    return true;
  }

  Scene* currentScene = GetCurrentScene();
  return currentScene ? currentScene->OnEvent(event) : false;
}

bool SceneManager::DispatchUIEvent(const Event& event) {
  for (auto it = uiSceneStack_.rbegin(); it != uiSceneStack_.rend(); ++it) {
    if (*it && (*it)->OnEvent(event)) {
      return true;
    }
  }

  return false;
}

Scene* SceneManager::GetCurrentScene() const {
  if (sceneStack_.empty()) {
    return nullptr;
  }
  return sceneStack_.back().Get();
}

UIScene* SceneManager::GetCurrentUIScene() const {
  if (uiSceneStack_.empty()) {
    return nullptr;
  }
  return uiSceneStack_.back().Get();
}

bool SceneManager::HasActiveScene() const {
  return !sceneStack_.empty();
}

bool SceneManager::HasActiveUIScene() const {
  return !uiSceneStack_.empty();
}

} // namespace frostbite2D
