#include "behavior.h"

#include <uf/utils/serialize/serializer.h>

#include <uf/utils/audio/audio.h>
#include <uf/utils/math/transform.h>
#include <uf/engine/asset/asset.h>

#include <mutex>

UF_BEHAVIOR_REGISTER_CPP(ext::BgmEmitterBehavior)
UF_BEHAVIOR_TRAITS_CPP(ext::BgmEmitterBehavior, ticks = true, renders = false, multithread = true)
#define this ((uf::Object*) &self)
void ext::BgmEmitterBehavior::initialize( uf::Object& self ) {
	auto& metadata = this->getComponent<ext::BgmEmitterBehavior::Metadata>();
	auto& metadataJson = this->getComponent<uf::Serializer>();

	UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);

	this->addHook( "asset:Load.%UID%", [&](pod::payloads::assetLoad& payload){
		if ( !uf::asset::isExpected( payload, uf::asset::Type::AUDIO ) ) return;
		auto& track = metadata.tracks[payload.uri];

		
		track.userdata = uf::asset::release( payload.filename );
		auto& audio = uf::pointeredUserdata::get<uf::Audio>( track.userdata );

	#if UF_AUDIO_MAPPED_VOLUMES
		auto volume = uf::audio::volumes.count("bgm") > 0 ? uf::audio::volumes.at("bgm") : 1.0;
	#else
		auto volume = uf::audio::volumes::bgm;
	#endif

		audio.setVolume(track.fade.x > 0 ? 0 : volume);
		audio.loop( payload.uri != track.intro );
		audio.play();

		metadata.current = payload.uri;
		track.active = true;
	});

	this->addHook( "bgm:Play.%UID%", [&](ext::json::Value& payload){
		auto filename = payload["filename"].as<uf::stl::string>();
		auto delay = payload["delay"].as<float>(0.0f);

		if ( filename == "" && !metadata.tracks.empty() ) {
			filename = uf::stl::random_it( metadata.tracks.begin(), metadata.tracks.end() )->first;
		}
		if ( metadata.tracks.count( filename ) == 0 ) {
			if ( filename == "" ) return;
		}

		auto& track = metadata.tracks[filename];
		if ( track.intro != "" ) filename = track.intro;
		this->callHook( "asset:QueueLoad.%UID%", this->resolveToPayload( filename ) );
	});

	if ( !metadata.tracks.empty() || metadata.current != "" ) {
		ext::json::Value payload;
		payload["filename"] = metadata.current;
		this->queueHook( "bgm:Play.%UID%", payload );
	}
}
void ext::BgmEmitterBehavior::tick( uf::Object& self ) {
	auto& metadata = this->getComponent<ext::BgmEmitterBehavior::Metadata>();
	auto& metadataJson = this->getComponent<uf::Serializer>();
#if UF_ENTITY_METADATA_USE_JSON
	metadata.deserialize(self, metadataJson);
#endif

#if UF_AUDIO_MAPPED_VOLUMES
	auto volume = uf::audio::volumes.count("bgm") > 0 ? uf::audio::volumes.at("bgm") : 1.0;
#else
	auto volume = uf::audio::volumes::bgm;
#endif

	if ( metadata.tracks.count( metadata.current ) > 0 ) {
		auto& track = metadata.tracks[metadata.current];
		if ( track.active ) {
			auto& audio = uf::pointeredUserdata::get<uf::Audio>( track.userdata );

			float current = audio.getTime();
			float end = audio.getDuration();
			bool isIntro = metadata.current == track.intro;

			float a = volume;
			if ( current < track.fade.x ) {
				a *= current / track.fade.x;
			} else if ( !audio.loops() && end - current < track.fade.y ) {
				a *= 1.0f - (end - current) / track.fade.y;
			}

			audio.setVolume(a);

			if ( audio.loops() ) {
				if ( !audio.playing() ) audio.play();
			} else if ( isIntro && end > 0 && (current + track.epsilon >= end || !audio.playing()) ) {
				auto payload = this->resolveToPayload( track.filename );
				this->callHook( "asset:QueueLoad.%UID%", payload );
			}
		}
	}

	for ( auto it : metadata.tracks ) {
		auto track = metadata.tracks[it.first];
		if ( !track.active ) continue;

		auto& audio = uf::pointeredUserdata::get<uf::Audio>( track.userdata );
		
		if ( audio.playing() ) audio.update();
		if ( it.first == metadata.current ) continue;

		float current = audio.getTime();
		float end = audio.getDuration();

		if ( current >= end && audio.playing() ) {
			audio.stop();
			uf::pointeredUserdata::destroy( track.userdata );
			track.active = false;
			continue;
		}

		float a = volume;
		if ( current < track.fade.x ) {
			a *= current / track.fade.x;
		} else if ( end - current < track.fade.y ) {
			a *= 1.0f - (end - current) / track.fade.y;
		}
		audio.setVolume(a);
	}
}

void ext::BgmEmitterBehavior::render( uf::Object& self ){}
void ext::BgmEmitterBehavior::destroy( uf::Object& self ){
	auto& metadata = this->getComponent<ext::BgmEmitterBehavior::Metadata>();
	for ( auto it : metadata.tracks ) {
		auto track = metadata.tracks[it.first];
		if ( !track.active ) continue;
		auto& audio = uf::pointeredUserdata::get<uf::Audio>( track.userdata );
		audio.stop();
		uf::pointeredUserdata::destroy( track.userdata );
		track.active = false;
	}
}
void ext::BgmEmitterBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){
	for ( auto it : /*this->*/tracks ) {
		serializer["bgm"]["tracks"][it.first]["filename"] = it.first;
		serializer["bgm"]["tracks"][it.first]["intro"] = it.second.intro;
		serializer["bgm"]["tracks"][it.first]["filename"] = it.second.filename;
		serializer["bgm"]["tracks"][it.first]["epsilon"] = it.second.epsilon;
		serializer["bgm"]["tracks"][it.first]["fade"] = ext::json::encode( it.second.fade );
	}
}
void ext::BgmEmitterBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){
	// iterate by value instead of reference because some keys might not exist and jsoncpp is agony
	ext::json::forEach( serializer["bgm"]["tracks"], [&]( const uf::stl::string& key, ext::json::Value value ){
		auto& track = /*this->*/tracks[key];
		track.filename = key;
		track.intro = value["intro"].as(track.intro);
		track.epsilon = value["epsilon"].as(track.epsilon);
		if ( value["fade"].is<bool>() ) {
			track.fade = { track.epsilon, track.epsilon };
		} else if ( value["fade"].is<float>() ) {
			track.fade = { value["fade"].as<float>(), value["fade"].as<float>() };
		} else {
			// YUC
			track.fade = ext::json::decode<float,2>( value["fade"], track.fade );
		}

		if ( track.intro != "" ) tracks[track.intro] = track;
	});

	if ( serializer["bgm"]["load"].is<uf::stl::string>() ) {
		/*this->*/current = serializer["bgm"]["load"].as(/*this->*/current);
	}
}
#undef this