#include "gameplay/scene.h"
#include "gameplay/ai.h"
#include "gameplay/gameactor.h"
#include "gameplay/map.h"
#include "gameplay/physics.h"
#include "gameplay/weapons/bulletmanager.h"
#include "gameplay/weapons/weapons.h"

#include "graphics/animation.h"
#include "graphics/background.h"
#include "graphics/sprite.h"

#include "utility/component.h"
#include "utility/debugdraw.h"
#include "utility/events.h"
#include "utility/ftfont.h"
#include "utility/raycaster.h"
#include "utility/resourcemanager.h"
#include "utility/script.h"
#include "utility/xmlloader.h"

#include <memory>

Scene::Scene(SceneType sceneType, std::shared_ptr<ResourceManager> resources,
             std::weak_ptr<EventManager> globalEvents)
    : type(sceneType), resourceManager(resources),
      globalEventManager(globalEvents) {
  physicsEngine = std::make_shared<PhysicsEngine>();
  eventManager = std::make_shared<EventManager>();
}

void Scene::init() {
  physicsEngine->hookEventManager(eventManager);
  loadDebugShooterScene();
}

// This function is full of hardcoded values and test sprites, NOT for use with
// final product
void Scene::loadDebugShooterScene() {
  hookSceneEvents();
  sceneData = resourceManager->loadScene("000");
  if (!sceneData)
    return;
  EntityData playerData = sceneData->entities[0];
  auto mapData = sceneData->map;
  auto playerShader = resourceManager->loadShader(
      "GL_player", "shaders/GL_player.vert", "shaders/GL_player.frag");
  auto bubbleShader = resourceManager->loadShader(
      "GL_bubble", "shaders/GL_bubble.vert", "shaders/GL_bubble.frag");
  auto weaponShader = resourceManager->loadShader(
      "GL_pistol", "shaders/GL_pistol.vert", "shaders/GL_pistol.frag");

  if (!sceneData->bgFile.empty()) {
    LOG(INFO, "Found background loading '{}'", sceneData->bgFile);
    background = resourceManager->loadBackground(sceneData->bgFile);
  }

  // creating map from scene
  auto tileShader = resourceManager->loadShader(
      "GL_tile", "shaders/GL_tile.vert", "shaders/GL_tile.frag");
  map = std::make_shared<Map>(mapData, tileShader, resourceManager);

  for (EntityData entityData : sceneData->entities) {
    auto entity = std::make_shared<GameActor>(this, playerShader);
    // Directional is the kind of sprite sheet we are using, in this case for
    // directional, I have the sprite sheet handle the rotations instead of just
    // rotating the object, this makes it look quite a bit better from the end
    // user perspective.
    if (entityData.animated) {
      auto entityAnimation = resourceManager->loadAnimationSet(
          entityData.graphic, entity->getEntityID());
      // because we don't want to have the engine rotate the object based on the
      // entities rotation, we set the this value to false so we no longer
      // rotate the object.
      if (entityAnimation->getDirectional())
        entity->setRotatable(false);
      entity->addComponent(
          std::make_unique<AnimationComponent>(entityAnimation, eventManager));
    } else {
      auto entitySprite = resourceManager->loadSpriteStatic(entityData.graphic);
      entity->addComponent(std::make_unique<SpriteComponent>(entitySprite));
    }
    auto defaultWeapon =
        resourceManager->loadWeapon("gun/pistol", weaponShader, bubbleShader);
    auto entityWeapon = resourceManager->loadWeapon(entityData.weapon,
                                                    weaponShader, bubbleShader);

    entity->pickupWeapon(std::move(defaultWeapon));
    entity->pickupWeapon(std::move(entityWeapon));
    entity->setPosition(glm::vec3(entityData.x * mapData->tileSize,
                                  entityData.y * mapData->tileSize, 0.f));
    entity->setScale(glm::vec3(mapData->tileSize, mapData->tileSize, 1.f));

    entity->addPhysicsComponent(physicsEngine->createObject(
        entity->getEntityID(), entity->getPosition(), 49.0,
        PhysicsComponent::Collider::Shape::Circle,
        glm::vec3(mapData->tileSize / 2)));

    if (entityData.isPlayer) {
      player = entity;
    } else {
      // attach ai
      if (!entityData.script.empty()) {
        auto behaviour =
            resourceManager->loadScript<AIScript>(entityData.script);
        auto rayCaster = std::make_unique<Raycaster>(
            40.f, 300.f, map->getCollisionMap(), mapData->tileSize);
        auto ai = std::make_shared<AI>(entity.get(), std::move(rayCaster));
        ai->setTarget(player.get());
        if (behaviour != nullptr)
          ai->attachBehaviourScript(std::move(behaviour));
        entity->addComponent(std::make_unique<AIComponent>(ai));
      }
    }
    entities.emplace(entity->getEntityID(), entity);
    SDL_Delay(1); // This is to make sure each entity gets a unique ID
  }

  physicsEngine->loadCollisionMap(map->getCollisionMap(), mapData->tileSize);
}

std::shared_ptr<GameActor> Scene::getPlayer() const {
  return (!player) ? nullptr : player;
}

void Scene::update(double deltaTime) {

  for (const auto &[id, e] : entities) {
    e->update(deltaTime);
  }
  physicsEngine->update(deltaTime);
}

void Scene::render(std::shared_ptr<Renderer> renderer) {
  renderer->clear();

  renderer->addDrawable(RenderLayer::Map, map.get());
  if (background) {
    renderer->addDrawable(RenderLayer::Background,
                          static_cast<Drawable *>(background));
  }
  for (auto &[id, e] : entities) {
    renderer->addDrawable(RenderLayer::GameObjects, e.get());
    if (e->getHeldWeapon()) {
      renderer->addDrawable(RenderLayer::GameObjects, e->getHeldWeapon());
      const auto &weapons = e->getAllWeapons();
      for (auto &w : weapons) {
        for (auto b : w->getBulletManager()->getBullets()) {
          renderer->addDrawable(RenderLayer::GameObjects, b.get());
        }
      }
    }
  }

  renderer->render();
  DEBUG_TEXT(glm::vec3(10.f, 10.f, 0.f), glm::vec4(0.f, 0.f, 0.f, 1.f), 0.5f,
             "{} / {}", getPlayer()->getHeldWeapon()->getMagazine(),
             getPlayer()->getHeldWeapon()->getAmmo());
}

void Scene::unloadScene() {
  // xmlLoader.reset();
}

void Scene::hookSceneEvents() {
  std::weak_ptr<Scene> weakSelf = shared_from_this();
  eventManager->subscribe<BulletCollideEvent>(
      [weakSelf](const BulletCollideEvent &e) {
        if (auto self = weakSelf.lock()) {
          GameActor *shooter = self->getGameActorByID(e.ownerID);
          GameActor *target = self->getGameActorByID(e.victimID);
          if (shooter && target)
            if (auto weapon = shooter->getHeldWeapon())
              weapon->onHitCallback(target, e.bullet.get(), e.normal);
        }
      });
}

GameActor *Scene::getGameActorByID(const unsigned int ID) {
  auto iterator = entities.find(ID);
  if (iterator == entities.end())
    return nullptr;
  return iterator->second.get();
}

std::span<std::weak_ptr<GameActor>> Scene::getAllEntities() const {
  entityCache.clear();
  for (const auto &[_, entity] : entities) {
    entityCache.push_back(entity);
  }
  return entityCache;
}
