#include "world.h"
#include <uf/utils/time/time.h>
#include <uf/utils/io/iostream.h>
#include <uf/utils/math/vector.h>
#include <uf/utils/math/transform.h>
#include <uf/utils/window/window.h>

#include <uf/utils/audio/audio.h>
#include <uf/utils/thread/thread.h>
#include <uf/utils/camera/camera.h>

#include <uf/engine/asset/asset.h>
#include <uf/engine/asset/masterdata.h>

#include "../../ext.h"

#include "./player/player.h"

#include "./gui/battle.h"
#include "./gui/dialogue.h"
#include "./gui/pause.h"

#include ".//battle.h"
#include ".//dialogue.h"

#include <uf/ext/vulkan/vulkan.h>
#include <uf/ext/vulkan/rendermodes/deferred.h>
#include <uf/ext/vulkan/rendermodes/rendertarget.h>
#include <uf/ext/vulkan/rendermodes/stereoscopic_deferred.h>
#include <uf/ext/openvr/openvr.h>

EXT_OBJECT_REGISTER_CPP(World)
void ext::World::initialize() {
	uf::Scene::initialize();
	this->m_name = "World";
//	this->load("./scenes/world/scene.json");

	uf::Serializer& metadata = this->getComponent<uf::Serializer>();
	uf::Asset& assetLoader = this->getComponent<uf::Asset>();

	/* Grab master data */ {
		std::vector<std::string> tables = {
			"Card",
			"Chara",
			"Skill",
			"Element",
			"Status",
		};
		for ( auto& table : tables ) {
			uf::MasterData data;
			metadata["system"]["mastertable"][table] = data.load(table);
		}
	}
	
	this->addHook( "world:Music.LoadPrevious.%UID%", [&](const std::string& event)->std::string{
		uf::Serializer json = event;

		if ( metadata["previous bgm"]["filename"] == "" ) return "false";

		std::string filename = metadata["previous bgm"]["filename"].asString();
		float timestamp = metadata["previous bgm"]["timestamp"].asFloat();

//		std::cout << metadata["previous bgm"] << std::endl;

		uf::Audio& audio = this->getComponent<uf::Audio>();
		if ( audio.playing() ) {
			metadata["previous bgm"]["filename"] = audio.getFilename();
			metadata["previous bgm"]["timestamp"] = audio.getTime();
			audio.stop();
		}
		audio.load(filename);
		audio.setVolume(metadata["volumes"]["bgm"].asFloat());
		audio.setTime(timestamp);
		audio.play();

		return "true";
	});
	this->addHook( "asset:Load." + std::to_string(this->getUid()), [&](const std::string& event)->std::string{	
		uf::Serializer json = event;
		std::string filename = json["filename"].asString();

		if ( uf::string::extension(filename) != "ogg" ) return "false";
		const uf::Audio* audioPointer = NULL;
		try { audioPointer = &assetLoader.get<uf::Audio>(filename); } catch ( ... ) {}
		if ( !audioPointer ) return "false";

		uf::Audio& audio = this->getComponent<uf::Audio>();
		if ( audio.playing() ) {
		/*
			if ( filename.find("_intro") == std::string::npos ) {
				metadata["previous bgm"]["filename"] = audio.getFilename();
				metadata["previous bgm"]["timestamp"] = audio.getTime();
			}
		*/
			audio.stop();
		}
		
//		std::cout << metadata["previous bgm"] << std::endl;

		audio.load(filename);
		audio.setVolume(metadata["volumes"]["bgm"].asFloat());
		audio.play();

		return "true";
	});

	static uf::Timer<long long> timer(false);
	if ( !timer.running() ) timer.start();
	this->addHook( "menu:Pause", [&](const std::string& event)->std::string{
		if ( timer.elapsed().asDouble() < 1 ) return "false";
		timer.reset();

		uf::Serializer json = event;
		ext::Gui* manager = (ext::Gui*) this->findByName("Gui Manager");
		if ( !manager ) return "false";
		uf::Serializer payload;
		ext::Gui* gui = (ext::Gui*) manager->findByUid( (payload["uid"] = manager->loadChild("/scenes/world/gui/pause/menu.json", false)).asUInt64() );
		uf::Serializer& metadata = gui->getComponent<uf::Serializer>();
		metadata["menu"] = json["menu"];
		gui->initialize();
		return payload;
	});
	this->addHook( "world:Entity.LoadAsset", [&](const std::string& event)->std::string{
		uf::Serializer json = event;

		std::string asset = json["asset"].asString();
		std::string uid = json["uid"].asString();

		assetLoader.load(asset, "asset:Load." + uid);

		return "true";
	});
	this->addHook( "world:Battle.Prepare", [&](const std::string& event)->std::string{
		uf::Serializer json = event;

		uf::Entity& player = *this->getController();
		uf::Serializer& pMetadata = player.getComponent<uf::Serializer>();

		uf::Serializer payload;
		
		payload["battle"]["music"] = json["music"];
		payload["battle"]["enemy"] = json[""];
		payload["battle"]["enemy"]["uid"] = json["uid"];

		payload["battle"]["player"] = pMetadata[""];
		payload["battle"]["player"]["uid"] = player.getUid();

		pMetadata["system"]["control"] = false;
		pMetadata["system"]["menu"] = "battle";
			
		this->callHook("world:Battle.Start", payload);

		return "true";
	});
	this->addHook( "world:Battle.Start", [&](const std::string& event)->std::string{
		if ( timer.elapsed().asDouble() < 1 ) return "false";
		timer.reset();
		ext::Gui* guiManager = (ext::Gui*) this->findByName("Gui Manager");
		if ( !guiManager ) return "false";
		uf::Serializer json = event;

		ext::HousamoBattle* battleManager = new ext::HousamoBattle;
		this->addChild(*battleManager);
		battleManager->initialize();
		battleManager->callHook("world:Battle.Start.%UID%", json);

		uf::Serializer payload;
		return payload;
	});
	this->addHook( "world:Battle.End", [&](const std::string& event)->std::string{
		uf::Serializer json = event;
		ext::HousamoBattle* battleManager = (ext::HousamoBattle*) this->findByName("Battle Manager");
		if ( !battleManager ) return "false";

		this->removeChild(*battleManager);
		battleManager->destroy();
		delete battleManager;

		return "true";
	});
	this->addHook( "menu:Dialogue.Start", [&](const std::string& event)->std::string{
		if ( timer.elapsed().asDouble() < 1 ) return "false";
		timer.reset();
		ext::Gui* guiManager = (ext::Gui*) this->findByName("Gui Manager");
		if ( !guiManager ) return "false";
		uf::Serializer json = event;

		ext::DialogueManager* dialogueManager = new ext::DialogueManager;
		this->addChild(*dialogueManager);
		dialogueManager->initialize();
		dialogueManager->callHook("menu:Dialogue.Start.%UID%", json);
		return json;
	});
	this->addHook( "menu:Dialogue.End", [&](const std::string& event)->std::string{
		uf::Serializer json = event;
		ext::DialogueManager* dialogueManager = (ext::DialogueManager*) this->findByName("Dialogue Manager");
		if ( !dialogueManager ) return "false";

		this->removeChild(*dialogueManager);
		dialogueManager->destroy();
		delete dialogueManager;

		return "true";
	});

	/* store viewport size */ {
		metadata["window"]["size"]["x"] = ext::vulkan::width;
		metadata["window"]["size"]["y"] = ext::vulkan::height;
		
		this->addHook( "window:Resized", [&](const std::string& event)->std::string{
			uf::Serializer json = event;

			pod::Vector2ui size; {
				size.x = json["window"]["size"]["x"].asUInt64();
				size.y = json["window"]["size"]["y"].asUInt64();
			}

			metadata["window"] = json["window"];

			return "true";
		});
	}

	// lock control
	{
		uf::Serializer payload;
		payload["state"] = false;
		uf::hooks.call("window:Mouse.CursorVisibility", payload);
		uf::hooks.call("window:Mouse.Lock");
	}
}

void ext::World::tick() {
	uf::Scene::tick();

	uf::Serializer& metadata = this->getComponent<uf::Serializer>();
	uf::Asset& assetLoader = this->getComponent<uf::Asset>();

	/* check if audio needs to loop */ try {
		uf::Audio& bgm = this->getComponent<uf::Audio>();
		float current = bgm.getTime();
		float end = bgm.getDuration();
		float epsilon = 0.005f;
		if ( current + epsilon >= end || !bgm.playing() ) {
			// intro to main transition
			std::string filename = bgm.getFilename();
			filename = assetLoader.getOriginal(filename);
			if ( filename.find("_intro") != std::string::npos ) {
				assetLoader.load(uf::string::replace( filename, "_intro", "" ), "asset:Load." + std::to_string(this->getUid()));
			// loop
			} else {
				bgm.setTime(0);
				if ( !bgm.playing() ) bgm.play();
			}
		}
	} catch ( ... ) {

	}

	/* Regain control if nothing requests it */ {
		ext::Gui* menu = (ext::Gui*) this->findByName("Gui: Menu");
		if ( !menu ) {
			uf::Serializer payload;
			payload["state"] = false;
			uf::hooks.call("window:Mouse.CursorVisibility", payload);
			uf::hooks.call("window:Mouse.Lock");
		}
	}

	/* Print Entity Information */  {
		static uf::Timer<long long> timer(false);
		if ( !timer.running() ) timer.start();
		if ( uf::Window::isKeyPressed("U") && timer.elapsed().asDouble() >= 1 ) { timer.reset();
			uf::iostream << "Current size: " << uf::Entity::entities.size() << " | UIDs: " << uf::Entity::uids << "\n";
			uint orphans = 0;
			uint empty = 0;
			for ( uf::Entity* e : uf::Entity::entities ) {
				if ( e && !e->hasParent() ) {
					++orphans;
					uf::iostream << "Orphan: " << e->getName() << ": " << e << "\n";
				}
				if ( !e ) ++empty;
			}
			uf::iostream << "Orphans: " << orphans << "\n";
			uf::iostream << "Empty: " << empty << "\n";
		}
	}
	
	/* Updates Sound Listener */ {
		pod::Transform<>& transform = this->getController()->getComponent<pod::Transform<>>();
		
		ext::oal.listener( "POSITION", { transform.position.x, transform.position.y, transform.position.z } );
		ext::oal.listener( "VELOCITY", { 0, 0, 0 } );
		ext::oal.listener( "ORIENTATION", { 0, 0, 1, 1, 0, 0 } );
	}
}

void ext::World::render() {
	uf::Scene::render();
}