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

#include "graphics/tile.h"
#include "graphics/sprite.h"

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

// Scene xml files, should contain a node called <player> that holds the sprite location
/*
like this:
<player sprite="sprites/player2Atlas.png" frameSize=64.0>
	<x=5/>
	<y=6/>
</player>
*/

Scene::Scene(SceneType sceneType, std::shared_ptr<ResourceManager> resources) : type(sceneType), resourceManager(resources)
{
	camera = std::make_shared<Camera>(800.f, 600.f);
	physicsEngine = std::make_shared<PhysicsEngine>();
	eventManager = std::make_shared<EventManager>();
	physicsEngine->hookEventManager(eventManager);
	if (sceneType == SCENE_SHOOTER)
		loadDebugShooterScene();
}

// This function is full of hardcoded values and test sprites, NOT for use with final product
void Scene::loadDebugShooterScene()
{
	sceneData = resourceManager->loadScene("000");
	if (!sceneData)
		return;
	EntityData playerData = sceneData->entities[0];
	MapData 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");

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

	for (EntityData entityData : sceneData->entities)
	{
		auto entity = std::make_shared<GameActor>(playerShader);
		std::shared_ptr<Sprite> entitySprite;
		// 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.isDirectional)
		{
			entitySprite = resourceManager->loadSpriteDirAnimated(entityData.sprite, entityData.frameSize);
			// 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.
			entity->setRotatable(false);
		}
		else
			entitySprite = resourceManager->loadSpriteAnimated(entityData.sprite, entityData.frameSize);
		auto entityWeapon = resourceManager->loadWeapon(entityData.weapon, weaponShader, bubbleShader);

		entityWeapon->hookEventManager(eventManager);
		entity->addComponent(std::make_shared<SpriteComponent>(entitySprite, eventManager));
		entity->equipWeapon(entityWeapon);
		entity->attachEventManager(eventManager);
		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->getActorID(), 
				entity->getPosition(), 
				49.0, 
				PhysicsComponent::Collider::Shape::Circle, 
				glm::vec3(mapData.tileSize / 2),
				glm::abs(entity->getCenter() - entity->getPosition()))
		);

		if (entityData.isPlayer)
		{
			player = entity;
			camera->setTarget(entity.get());
			entity->setLocalPosition(camera->worldToLocal(entity->getPosition()));
		}
		else
		{
			// attach ai
			if (!entityData.script.empty())
			{
				auto behaviourScript = resourceManager->loadAIScript(entityData.script);
				auto rayCaster = std::make_shared<Raycaster>(40.f, 300.f, map->getCollisionMap(), mapData.tileSize);
				auto ai = std::make_shared<AI>(entity, rayCaster);
				ai->setTarget(player);
				ai->attachBehaviourScript(behaviourScript);
				entity->addComponent(std::make_shared<AIComponent>(ai));
			}
		}
		entities.push_back(entity);
	}

	physicsEngine->loadCollisionMap(map->getCollisionMap(), sceneData->map.tileSize);

	// Setup map and other entities...
}

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

void Scene::update(float deltaTime)
{
	for (auto& e : entities)
	{
		e->update(deltaTime);
		if (camera->getTarget() == e.get())
			e->setLocalPosition(camera->worldToLocal(e->getPosition()));
	}
	physicsEngine->update(deltaTime);
	camera->update(deltaTime);
}

void Scene::render()
{
	map->render(camera);
	for (auto& e : entities)
	{
		e->render(camera);
	}
	DebugDrawer::getInstance().draw(camera->getProjectionMatrix() * camera->getViewMatrix());
}

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

