#include "LevelBuilder.h"
#include "Application.h"
#include "SceneManager.h"
#include "Box2D/Collision/Shapes/b2CircleShape.h"
#include "Box2D/Collision/Shapes/b2PolygonShape.h"

#include "CharacterAnimator.hpp"
#include "LevelOverOnHitByCharacter.hpp"

using namespace std;
using namespace sre;
using namespace rapidjson;

LevelBuilder::LevelBuilder(
	Event<void(float)>* Update,
	Event<void(sre::SpriteBatch::SpriteBatchBuilder&)>* DrawScene,
	Event<void()>* PhysicsStep,
	Event<void(SDL_Event&)>* keyEvent,
	std::shared_ptr<b2World> physicsWorld)
{
	this->updateEvent = Update;
	this->drawEvent = DrawScene;
	this->physicsEvent = PhysicsStep;
	this->physicsWorld = physicsWorld;
	this->keyEvent = keyEvent;
}


LevelBuilder::~LevelBuilder(){}

Level LevelBuilder::BuildFromFile(std::string fileName)
{
	Level level = LoadLevel(fileName);

	return level;
}

Level LevelBuilder::LoadLevel(string fileName)
{
	Level level;
	
	level.levelObj = std::make_shared<GameObject>();
	level.cameraObject = std::make_shared<GameObject>();
	level.cameraObject->setName("Main Camera");
	auto com = level.cameraObject->AddComponent<CameraComponent>();
	CameraComponent::Mutator::SetHalfHeight(com, 150);
	level.cameraObject->GetTransform()->SetPosition(glm::vec2(231.5f, 300));
	level.camera = &CameraComponent::Mutator::GetCamera(com);
	auto cameraBehaviour = level.cameraObject->GetComponents<Behaviour>()[0];
	Behaviour::Mutator::SetUpdateEvent(*cameraBehaviour, updateEvent);
	level.cameraObject->ToggleActive();
	

	ifstream ifs(fileName);
	IStreamWrapper isw(ifs);
	Document document;
	document.ParseStream(isw);

	level.levelObj->setId(document["id"].GetInt());
	level.levelObj->setName(document["name"].GetString());

	level.deathLevelInY = document["deathLevelInY"].GetFloat();
	//TODO Background 
	//background = document["background"].GetString();

	//TODO Music
	//level.music = document["music"].GetString();

	//Should be optimized, eg, if a tile with x ID has been loaded once it should just add it not load it again
	// Not really, they need to be unique GameObjects - TileID is one thing,
	// but runtime GameObjects *should* be unique, i.e. have different IDs. They're different objects.
	for (int i = 0; i < document["arrayOfTiles"].GetArray().Size(); i++)
	{

		for (int j = 0; j < document["arrayOfTiles"].GetArray()[i].GetArray().Size(); j++)
		{
			//Gets the ID of the Tile
			int tileID = document["arrayOfTiles"].GetArray()[i][j].GetArray()[0].GetInt();
			auto tile = MakeGameObject(tileID, &level);

			//Uses the ID to load the tile
			auto transform = tile->GetTransform();
			glm::vec2 position = glm::vec2(document["arrayOfTiles"].GetArray()[i][j].GetArray()[1].GetInt() * 21, document["arrayOfTiles"].GetArray()[i][j].GetArray()[2].GetInt() * 21);
			transform->SetPosition(position);

			level.levelObjects.push_back(tile);
			tile->ToggleActive();
		}
	}

	return level;
}

std::shared_ptr<GameObject> LevelBuilder::MakeGameObject(int id, Level* levelInConstruction)
{
	auto building = std::make_shared<GameObject>();
	
	std::ostringstream oss;
	//The name of the tile has to be tile + ID , otherwise this will not work
	oss << "tile" << id << ".json";
	string fileName = oss.str();

	fstream ifs(fileName);
	IStreamWrapper isw(ifs);
	Document document;
	document.ParseStream(isw);

	building->setId(document["id"].GetInt());
	building->setName(document["name"].GetString());

	//Sprite stuff
	//assert(document["sprite"].HasMember);
	if (document.HasMember("sprite"))
	{
		oss.str("");
		oss << document["sprite"].GetInt() << ".png";
		string spriteId = oss.str();

		auto sprite = SceneManager::GetInstance()->atlas->get(spriteId);
		auto spriteComp = building->AddComponent<SpriteRenderer>();
		spriteComp->SetSprite(sprite);
	}

	if (document.HasMember("physics")) {
		Logger::Debug("Building a physics");
		auto physicsComponent = building->AddComponent<PhysicsBody>();
		PhysicsBody::Mutator::SetPhysicsWorld(*physicsComponent, physicsWorld);
		b2BodyDef bodyDef;
		b2BodyType rbType;

		std::string bodyValue = document["physics"]["type"].GetString();
		if (bodyValue.compare("static") == 0) {
			bodyDef.type = b2_staticBody;
			rbType = b2_staticBody;
		}
		else if (bodyValue.compare("kinematic") == 0) {
			bodyDef.type = b2_kinematicBody;
			rbType = b2_kinematicBody;
		}
		else {
			bodyDef.type = b2_dynamicBody;
			rbType = b2_dynamicBody;
		}
		
		bodyDef.position = b2Vec2(building->GetTransform()->GetPosition().x, building->GetTransform()->GetPosition().y);		
		PhysicsBody::Mutator::DefineBody(*physicsComponent, bodyDef);
		b2FixtureDef fixtureDef;
		
		b2Shape::Type shapeType;
		
		b2PolygonShape * polygon = nullptr;
		shapeType = b2Shape::Type::e_polygon;
		polygon = new b2PolygonShape();
		polygon->SetAsBox(10, 10, { 0,0 }, 0);
		fixtureDef.shape = polygon;
		fixtureDef.isSensor = document["physics"]["sensor"].GetBool();
		
		fixtureDef.density = document["physics"]["density"].GetInt();
		PhysicsBody::Mutator::AddFixture(*physicsComponent, fixtureDef);
	}
	
	if (document.HasMember("characterController"))
	{
		auto physicsComponent = building->GetComponents<PhysicsBody>()[0];
		if (physicsComponent == nullptr)
		{
			Logger::Log("Player doesn't have a physicsbody component");
			return building;
		}
		
		levelInConstruction->playerObject = building;
		auto controller = building->AddComponent<CharacterController>();
		CameraComponent::Mutator::SetPlayerTransform(levelInConstruction->cameraObject->GetComponents<CameraComponent>()[0], building);
		if (document.HasMember("animation"))
		{
			auto animator = building->AddComponent<CharacterAnimator>();
			for (int i = 0; i < document["animation"].GetArray().Size(); i++)
			{
				oss.str("");
				oss << document["animation"].GetArray()[i].GetInt() << ".png";
				string spriteId = oss.str();

				auto sprite = SceneManager::GetInstance()->atlas->get(spriteId);
				CharacterAnimator::Mutator::AddSprite(*animator, sprite);
			}
		}
	}

	if (document.HasMember("leveloverbox")) {
		building->AddComponent<LevelOverOnHitByCharacter>();
	}

	for (auto & behaviour : building->GetComponents<Behaviour>()) {
		Behaviour::Mutator::SetUpdateEvent(*behaviour, updateEvent);
	}
	for (auto & renderer : building->GetComponents<SpriteRenderer>()) {
		SpriteRenderer::Mutator::SetDrawEvent(*renderer, drawEvent);
	}
	for (auto & body : building->GetComponents<PhysicsBody>()) {
		PhysicsBody::Mutator::SetPhysicsStepEvent(*body, physicsEvent);
	}
	for (auto & controller : building->GetComponents<CharacterController>()) {
		CharacterController::Mutator::SetKeyEvent(*controller, keyEvent);
	}
	return building;
}