#include "dialogue.h"

#include <uf/utils/hook/hook.h>
#include <uf/utils/time/time.h>
#include <uf/utils/serialize/serializer.h>
#include <uf/utils/userdata/userdata.h>
#include <uf/utils/window/window.h>
#include <uf/utils/camera/camera.h>
#include <uf/utils/string/ext.h>
// #include <uf/gl/glyph/glyph.h>
#include <uf/engine/asset/asset.h>

#include <unordered_map>

#include <uf/ext/vulkan/vulkan.h>
#include <uf/ext/vulkan/graphic.h>

#include <uf/utils/http/http.h>
#include <uf/utils/audio/audio.h>
#include <sys/stat.h>
#include <fstream>

#include "../world.h"
#include "..//dialogue.h"

namespace {
	ext::Gui* circleIn;
	ext::Gui* circleOut;
	ext::Gui* dialogueMessage;
	ext::Gui* dialogueOptions;

	ext::DialogueManager* dialogueManager;

	ext::World* world;

	uf::Serializer masterTableGet( const std::string& table ) {
		if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
		if ( !world ) return std::string();
		uf::Serializer& mastertable = world->getComponent<uf::Serializer>();
		return mastertable["system"]["mastertable"][table];
	}
	uf::Serializer masterDataGet( const std::string& table, const std::string& key ) {
		if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
		if ( !world ) return std::string();
		uf::Serializer& mastertable = world->getComponent<uf::Serializer>();
		return mastertable["system"]["mastertable"][table][key];
	}
	inline int64_t parseInt( const std::string& str ) {
		return atoi(str.c_str());
	}

	void playSound( ext::GuiDialogue& entity, const std::string& id, const std::string& key ) {
		if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
		if ( !world ) return;
		uf::Serializer& masterdata = world->getComponent<uf::Serializer>();

		uf::Serializer cardData = masterDataGet("Card", id);
		uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
		std::string name = charaData["filename"].asString();
		std::string url = "https://cdn..xyz//unity/Android/voice/voice_" + name + "_"+key+".ogg";
		if ( charaData["internal"].asBool() ) {
			url = "/smtsamo/voice/voice_" + name + "_" + key + ".ogg";
		}

		uf::Asset& assetLoader = world->getComponent<uf::Asset>();
		assetLoader.cache(url, "asset:Cache.Voice." + std::to_string(entity.getUid()));
	}
	void playSound( ext::GuiDialogue& entity, std::size_t uid, const std::string& key ) {
		if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
		if ( !world ) return;
		uf::Serializer& pMetadata = entity.getComponent<uf::Serializer>();
		uf::Serializer& masterdata = world->getComponent<uf::Serializer>();

		uf::Entity*  = world->findByUid(uid);
		if ( ! ) return;
		uf::Serializer& metadata = ->getComponent<uf::Serializer>();
		std::string id = metadata[""]["id"].asString();
		playSound( entity, id, key );
	}
	void playSound( ext::GuiDialogue& entity, const std::string& key ) {
		if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
		if ( !world ) return;
		uf::Serializer& metadata = entity.getComponent<uf::Serializer>();
		uf::Serializer& masterdata = world->getComponent<uf::Serializer>();

		std::string url = "./data/audio/ui/" + key + ".ogg";

		uf::Asset& assetLoader = world->getComponent<uf::Asset>();
		assetLoader.cache(url, "asset:Cache.SFX." + std::to_string(entity.getUid()));
	}
	void playMusic( ext::GuiDialogue& entity, const std::string& filename ) {
		if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
		if ( !world ) return;
		uf::Asset& assetLoader = world->getComponent<uf::Asset>();
		uf::Serializer& masterdata = world->getComponent<uf::Serializer>();

		assetLoader.load(filename, "asset:Load." + std::to_string(world->getUid()));
	}
	std::string getKeyFromIndex( uf::Serializer& object, uint64_t index ) {
		uint64_t i = 0;
		std::string key = "";
		for ( auto it = object.begin(); it != object.end(); ++it ) {
			if ( i++ == index ) key = it.key().asString();
		}
		return key;
	}

	uf::Audio sfx;
	uf::Audio voice;

	struct DialogueStats {
		struct {
			int selection = 0;
			int selectionsMax = 3;
			std::vector<std::string> invalids;
			uf::Serializer current;
			std::string index = "0";
		} actions;
		std::string state = "open";
		bool initialized = false;
	};
	static DialogueStats stats;
}


void ext::GuiDialogue::initialize() {
	ext::Gui::initialize();

	uf::Serializer& metadata = this->getComponent<uf::Serializer>();
	dialogueManager = (ext::DialogueManager*) this->getRootParent().findByName( "Dialogue Manager" );

	this->addHook( "asset:Cache.SFX." + 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";

		if ( filename == "" ) return "false";
		uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
		uf::Audio& sfx = this->getComponent<uf::Audio>();
		if ( sfx.playing() ) sfx.stop();
		sfx = uf::Audio();
		sfx.load(filename);
		sfx.setVolume(masterdata["volumes"]["sfx"].asFloat());
		sfx.play();

		return "true";
	});

	this->addHook( "asset:Cache.Voice." + 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";

		if ( filename == "" ) return "false";
		uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
		uf::Audio& voice = this->getComponent<uf::Audio>();
		if ( voice.playing() ) voice.stop();
		voice = uf::Audio();
		voice.load(filename);
		voice.setVolume(masterdata["volumes"]["voice"].asFloat());
		voice.play();

		return "true";
	});

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

	this->addHook("menu:Close.%UID%", [&]( const std::string& event ) -> std::string {		
		// kill
		this->getParent().removeChild(*this);
		this->destroy();
		delete this;
		return "true";
	});

	stats = DialogueStats();
}
void ext::GuiDialogue::tick() {
	if ( !dialogueManager ) return;

	ext::World& world = this->getRootParent<ext::World>();
	uf::Serializer& masterdata = world.getComponent<uf::Serializer>();
	uf::Serializer& bMetadata = this->getComponent<uf::Serializer>();

	static float alpha = 0.0f;
	static uf::Timer<long long> timer(false);
	static float timeout = 1.0f;
	if ( !timer.running() ) timer.start();

	std::function<void(uf::Serializer&)> postParseResult = [&]( uf::Serializer& result ) {
		if ( result["end"].asBool() ) bMetadata["system"]["closing"] = true;
		if ( !result["timeout"].isNull() ) timeout = result["timeout"].asFloat();
		else timeout = 1;
		if ( result["message"].asString() != "" ) {
			if ( dialogueMessage ) {
				dialogueMessage->destroy();
				this->removeChild(*dialogueMessage);
				delete dialogueMessage;	
			}
			dialogueMessage = new ext::Gui;
			this->addChild(*dialogueMessage);
			uf::Serializer& pMetadata = dialogueMessage->getComponent<uf::Serializer>();
			dialogueMessage->load("./dialogue-text.json", true);
			pMetadata["text settings"]["string"] = result["message"].asString();
			dialogueMessage->initialize();
		}
		timer.reset();
	};

	std::function<void(uf::Serializer)> renderDialogueOptions = [&]( uf::Serializer actions ) {
		if ( dialogueOptions ) {
			this->removeChild(*dialogueOptions);
			dialogueOptions->destroy();
			delete dialogueOptions;
			dialogueOptions = NULL;
		}
		if ( actions.isNull() ) {
			stats.actions.selection = 0;
			return;
		}
		stats.actions.current = actions;
		std::string string = "";
		std::size_t counter = 0;
		std::size_t start = stats.actions.selection;
		auto keys = actions.getMemberNames();
		for ( int i = start; i < keys.size(); ++i ) {
			std::string key = getKeyFromIndex( actions, i );
			std::string text = actions[key].asString();
			if ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), key ) != stats.actions.invalids.end() ) continue;
			if ( ++counter > stats.actions.selectionsMax ) break;
			if ( i != stats.actions.selection ) text = "%#FFFFFF%" + text;
			else text = "%#FF0000%" + text;
			string += "\n" + text;
		}
		if ( keys.size() > stats.actions.selectionsMax ) {
			for ( int i = 0; i < keys.size(); ++i ) {
				std::string key = getKeyFromIndex( actions, i );
				std::string text = actions[key].asString();
				if ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), key ) != stats.actions.invalids.end() ) continue;
				if ( ++counter > stats.actions.selectionsMax ) break;
				if ( i != stats.actions.selection ) text = "%#FFFFFF%" + text;
				else text = "%#FF0000%" + text;
				string += "\n" + text;
			}
		}
		if ( string != "" ) {
			string = string.substr(1);
			dialogueOptions = new ext::Gui;
			this->addChild(*dialogueOptions);
			uf::Serializer& pMetadata = dialogueOptions->getComponent<uf::Serializer>();
			dialogueOptions->load("./dialogue-option.json", true);
			pMetadata["text settings"]["string"] = string;
			pod::Transform<>& pTransform = dialogueOptions->getComponent<pod::Transform<>>();
			dialogueOptions->initialize();
		}
	};

	if ( !stats.initialized ) {
		stats.initialized = true;
		this->addHook("menu:Dialogue.Turn.%UID%", [&](const std::string& event) -> std::string {
			uf::Serializer json = event;
			uf::Serializer payload;
			payload["action"] = "dialogue-select";
			payload["index"] = stats.actions.index;
			uf::Serializer result = uf::hooks.call("menu:Dialogue.Action." + std::to_string(dialogueManager->getUid()), payload)[0];
			postParseResult(result);

			// renderDialogueOptions(std::string(""));
			renderDialogueOptions(result["actions"]);

			return "true";
		});
		this->queueHook("menu:Dialogue.Turn.%UID%");
	}

	{
		uf::Serializer& metadata = this->getComponent<uf::Serializer>();
		if ( metadata["system"]["closing"].asBool() ) {
			if ( alpha >= 1.0f ) {
				uf::Asset assetLoader;
				std::string canonical = assetLoader.load("./data/audio/ui/menu close.ogg");
				uf::Audio& sfx = assetLoader.get<uf::Audio>(canonical);
				sfx.setVolume(masterdata["volumes"]["sfx"].asFloat());
				sfx.play();
			}
			if ( alpha <= 0.0f ) {
				alpha = 0.0f;
				metadata["system"]["closing"] = false;
				metadata["system"]["closed"] = true;
			} else alpha -= uf::physics::time::delta;
			metadata["color"][3] = alpha;
		} else if ( metadata["system"]["closed"].asBool() ) {
			uf::Object& parent = this->getParent<uf::Object>();
			parent.getComponent<uf::Serializer>()["system"]["closed"] = true;
			this->destroy();
			parent.removeChild(*this);
		} else {
			if ( !metadata["initialized"].asBool() ) alpha = 0.0f;
			metadata["initialized"] = true;
			if ( alpha >= 1.0f ) alpha = 1.0f;
			else alpha += uf::physics::time::delta * 1.5f;
			metadata["color"][3] = alpha;
		}
	}

	ext::Gui::tick();

	if ( stats.state != "open" ) {
		if ( timer.elapsed().asDouble() >= timeout ) stats.state = "open";
		return;
	}

	struct {
		bool up = uf::Window::isKeyPressed("W");
		bool down = uf::Window::isKeyPressed("S");
		bool back = uf::Window::isKeyPressed("Escape");
		bool select = uf::Window::isKeyPressed("E");
	} keys;
	static bool lastPress = false;

	if ( dialogueOptions ) {
		bool delta = false;
		bool invalid = false;
		pod::Transform<>& transform = dialogueOptions->getComponent<pod::Transform<>>();
		uf::Serializer& metadata = dialogueOptions->getComponent<uf::Serializer>();
		metadata["color"][3] = alpha;
		for ( uf::Entity* pointer : dialogueOptions->getChildren()  ) {
			uf::Serializer& cMetadata = pointer->getComponent<uf::Serializer>();
			cMetadata["text settings"]["color"][3] = alpha;
		}
		static bool released = true;
		if ( keys.up ) { 
			if ( released ) {
				delta = true;
				released = false;
				do {
					--stats.actions.selection;
					if ( stats.actions.selection < 0 ) { stats.actions.selection = stats.actions.current.size() - 1; }
				} while ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), getKeyFromIndex(stats.actions.current, stats.actions.selection) ) != stats.actions.invalids.end() );
			}
		}
		else if ( keys.down ) {
			if ( released ) {
				delta = true;
				released = false;
				do {
					++stats.actions.selection;
					if ( stats.actions.selection >= stats.actions.current.size() ) { stats.actions.selection = 0; }
				} while ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), getKeyFromIndex(stats.actions.current, stats.actions.selection) ) != stats.actions.invalids.end() );
			}
		}
		else released = true;
		if ( stats.actions.selection < 0 ) { stats.actions.selection = stats.actions.current.size() - 1; }
		else if ( stats.actions.selection >= stats.actions.current.size() ) { stats.actions.selection = 0; }
		if ( stats.actions.current.size() == 0 ) released = true;
		if ( !released ) {
			if ( invalid ) {
				playSound(*this, "invalid select");
			} else if ( delta ) {
				playSound(*this, "menu flip");
				renderDialogueOptions(stats.actions.current);
			}
		} else if ( keys.back && timer.elapsed().asDouble() >= timeout ) {
			playSound(*this, "invalid select");
			timer.reset();
		} else if ( keys.select && timer.elapsed().asDouble() >= timeout ) {
			playSound(*this, "menu close");
			uf::Serializer payload;
			std::string action = getKeyFromIndex(stats.actions.current, stats.actions.selection);
			payload["action"] = "dialogue-select";
			payload["index"] = action;
			stats.actions.index = action;
			
			renderDialogueOptions(std::string(""));

			timer.reset();

			stats.state = "waiting";
			stats.actions.invalids.clear();
			this->queueHook("menu:Dialogue.Turn.%UID%", "", timeout);
		}
	}
}

void ext::GuiDialogue::render() {
	ext::Gui::render();
}

void ext::GuiDialogue::destroy() {
	ext::Gui::destroy();
}