#include "OHAF_Events.h"
#include "OHAF_DataHandler.h"
#include "OHAF_Functions.h"

namespace MaxsuOnHitAnimFW
{
	using EventResult = RE::BSEventNotifyControl;

	bool OnHitEventHandler::RegisterOnHitEvent()
	{
		static OnHitEventHandler g_hiteventhandler;

		auto ScriptEventSource = RE::ScriptEventSourceHolder::GetSingleton();

		if (!ScriptEventSource) {
			logger::error("ScriptEventSource not found!");
			return false;
		}

		ScriptEventSource->AddEventSink(&g_hiteventhandler);

		logger::info("Register OnHit Event Handler!");

		return true;
	}

	EventResult OnHitEventHandler::ProcessEvent(const RE::TESHitEvent* a_event, RE::BSTEventSource<RE::TESHitEvent>* a_eventSource)
	{
		if (!a_event || !a_eventSource) {
			logger::error("Event Source Not Found!");
			return EventResult::kContinue;
		}

		logger::debug("OnHit Event Trigger!");

		auto OHAF_datahandler = DataHandler::GetSingleton();

		if (!OHAF_datahandler) {
			logger::error("OHAF DatahHandler Not Found!");
			return EventResult::kContinue;
		}

		auto hit_target = a_event->target.get();

		if (!ShouldProcessOnHit(hit_target->As<RE::Actor>()))
			return EventResult::kContinue;

		auto thisgraph = GetAnimGraph(hit_target->As<RE::Actor>());

		if (!thisgraph) {
			logger::error("Not Animation Graph Found in the actor!");
			return EventResult::kContinue;
		}

		std::string graph_name = thisgraph->projectName.data();

		std::transform(graph_name.begin(), graph_name.end(), graph_name.begin(), [](unsigned char c) { return std::tolower(c); });

		logger::debug(FMT_STRING("Target Actor Graph Name is \"{}\"."), graph_name);

		auto this_AGObj = OHAF_datahandler->LookUpAGObjByName(graph_name);

		if (!this_AGObj) {
			logger::debug("Not Matching AnimGraph Type Found For Hit Target!");
			return EventResult::kContinue;
		}

		auto hitsource = RE::TESForm::LookupByID<RE::TESObjectWEAP>(a_event->source);

		if (!hitsource) {
			logger::debug("Weapon Hit Source Not Found!");
			return EventResult::kContinue;
		}

		if (hitsource->formType != RE::FormType::Weapon) {
			logger::debug("Hit Source Is Not Weapon!");
			return EventResult::kContinue;
		} else
			logger::debug(FMT_STRING("Weapon Name is \"{}\", ID is \"{:x}\""), hitsource->GetName(), hitsource->GetFormID());

		using HitFlag = RE::TESHitEvent::Flag;
		using Type = OHAF_AnimGraphObj::Type;

		auto CheckHitFlag = [a_event](HitFlag flag, Type type) -> bool {
			if (flag == HitFlag::kNone)
				return true;

			switch (type) {
			case Type::kAny:
				return a_event->flags.any(flag);

			case Type::kAll:
				return a_event->flags.all(flag);

			case Type::kNone:
				return a_event->flags.none(flag);

			default:
				return false;
			}
		};

		for (int i = 0; i < Type::kTotal; i++) {
			if (!CheckHitFlag(this_AGObj->flags[i], Type(i))) {
				logger::debug(FMT_STRING("Hit Flag {} Not Matched!"), i);
				return EventResult::kContinue;
			}
		}

		auto lastHitData = [hit_target]() -> RE::HitData* {
			if (hit_target && hit_target->As<RE::Actor>()) {
				auto cur_process = hit_target->As<RE::Actor>()->currentProcess;
				if (cur_process && cur_process->middleHigh) {
					return cur_process->middleHigh->lastHitData;
				}
			}
			return nullptr;
		}();

		if (!lastHitData || lastHitData->totalDamage < this_AGObj->MinDamage) {
			logger::debug("Hit Damage too low!");
			return EventResult::kContinue;
		}

		float AnimVarFloat = 0.f;

		hit_target->GetGraphVariableFloat(this_AGObj->VarFloatName, AnimVarFloat);

		logger::debug(FMT_STRING("Current Variable Float {} is {}"), this_AGObj->VarFloatName.c_str(), AnimVarFloat);

		if (abs(AnimVarFloat) <= 1e-6)	
		{
			hit_target->NotifyAnimationGraph(this_AGObj->EventName.c_str());
			logger::debug("Trigger an OnHit Event Successfully!");
		}

		return EventResult::kContinue;
	}

	bool OnEffectStartHandler::RegisterEffectStartEvent()
	{
		static OnEffectStartHandler g_effecteventhandler;

		auto ScriptEventSource = RE::ScriptEventSourceHolder::GetSingleton();

		if (!ScriptEventSource) {
			logger::error("ScriptEventSource not found!");
			return false;
		}

		ScriptEventSource->AddEventSink(&g_effecteventhandler);

		logger::info("Register OnEffectStart Event Handler!");

		return true;
	}

	EventResult OnEffectStartHandler::ProcessEvent(const RE::TESMagicEffectApplyEvent* a_event, RE::BSTEventSource<RE::TESMagicEffectApplyEvent>* a_eventSource)
	{
		if (!a_event || !a_eventSource) {
			logger::error("Event Source Not Found!");
			return EventResult::kContinue;
		}

		logger::debug("OnEffectStart Event Trigger!");

		auto OHAF_datahandler = DataHandler::GetSingleton();

		if (!OHAF_datahandler) {
			logger::error("OHAF DatahHandler Not Found!");
			return EventResult::kContinue;
		}

		auto effect_target = a_event->target.get();

		if (!effect_target) {
			logger::error("Effect Target Not Found!");
			return EventResult::kContinue;
		}

		if (!ShouldProcessOnHit(effect_target->As<RE::Actor>()))
			return EventResult::kContinue;

		auto thisgraph = GetAnimGraph(effect_target->As<RE::Actor>());

		if (!thisgraph) {
			logger::debug("Not Animation Graph Found in the actor!");
			return EventResult::kContinue;
		}

		std::string graph_name = thisgraph->projectName.data();

		std::transform(graph_name.begin(), graph_name.end(), graph_name.begin(), [](unsigned char c) { return std::tolower(c); });

		logger::debug(FMT_STRING("Target Actor Graph Name is {}."), graph_name);

		auto this_AGObj = OHAF_datahandler->LookUpAGObjByName(graph_name);

		if (!this_AGObj) {
			logger::debug("Not Matching AnimGraph Type Found For Effect Target!");
			return EventResult::kContinue;
		}

		using Flags = RE::EffectSetting::EffectSettingData::Flag;
		using CType = RE::MagicSystem::CastingType;
		using DType = RE::MagicSystem::Delivery;

		auto this_effect = RE::TESForm::LookupByID<RE::EffectSetting>(a_event->magicEffect);

		if (!this_effect) {
			logger::debug("Magic Effect Form Not Found!");
			return EventResult::kContinue;
		} else {
			logger::debug(FMT_STRING("Current Magic Effect name is \"{}\", ID is \"{:x}\""), this_effect->GetName(), this_effect->GetFormID());
		}

		if (!(this_effect->data.flags & Flags::kHostile)) {
			logger::debug("Magic Effect Is Not Hostile!");
			return EventResult::kContinue;
		}

		if (this_effect->data.flags & Flags::kPainless) {
			logger::debug("Magic Effect Is Painless!");
			return EventResult::kContinue;
		}

		if (this_effect->data.delivery == DType::kSelf) {
			logger::debug("Magic Effect Is Self Delivered");
			return EventResult::kContinue;
		}

		if (this_effect->GetMagickSkill() == RE::ActorValue::kAlteration || this_effect->GetMagickSkill() == RE::ActorValue::kConjuration || this_effect->GetMagickSkill() == RE::ActorValue::kIllusion) {
			logger::debug("Magic Skill Not Matched!");
			return EventResult::kContinue;
		}

		if (this_effect->data.castingType != CType::kFireAndForget && this_effect->data.castingType != CType::kScroll) {
			logger::debug("Magic Effect Is Not Fire And Forget!");
			return EventResult::kContinue;
		}

		if (!this_effect->conditions.IsTrue(a_event->caster.get(), a_event->target.get())) {
			logger::debug("Magic Effect Conditions Not Matched!");
			return EventResult::kContinue;
		}

		float AnimVarFloat = 0.f;

		effect_target->GetGraphVariableFloat(this_AGObj->VarFloatName, AnimVarFloat);

		logger::debug(FMT_STRING("Current Variable Float {} is {}"), this_AGObj->VarFloatName.c_str(), AnimVarFloat);

		if (abs(AnimVarFloat) <= 1e-6)	
		{
			effect_target->NotifyAnimationGraph(this_AGObj->EventName.c_str());
			logger::debug("Trigger an OnEffectStart Event Successfully!");
		}

		return EventResult::kContinue;
	}

}
#include "OHAF_Events.h"
#include "OHAF_DataHandler.h"
#include "OHAF_Functions.h"

namespace MaxsuOnHitAnimFW
{
	using EventResult = RE::BSEventNotifyControl;

	//-------------------------------------OnHit Event-----------------------------------------------------------------------------------------------------

	bool OnHitEventHandler::RegisterOnHitEvent()
	{
		static OnHitEventHandler g_hiteventhandler;

		auto ScriptEventSource = RE::ScriptEventSourceHolder::GetSingleton();

		if (!ScriptEventSource) {
			logger::error("ScriptEventSource not found!");
			return false;
		}

		ScriptEventSource->AddEventSink(&g_hiteventhandler);

		logger::info("Register OnHit Event Handler!");

		return true;
	}


	EventResult OnHitEventHandler::ProcessEvent(const RE::TESHitEvent* a_event, RE::BSTEventSource<RE::TESHitEvent>* a_eventSource)
	{
		if (!a_event || !a_eventSource) {
			logger::error("Event Source Not Found!");
			return EventResult::kContinue;
		}

		logger::debug("OnHit Event Trigger!");

		auto OHAF_datahandler = DataHandler::GetSingleton();

		if (!OHAF_datahandler) {
			logger::error("OHAF DatahHandler Not Found!");
			return EventResult::kContinue;
		}

		//----------------------Check Hit Target-----------------------------------------------

		auto hit_target = a_event->target.get();

		if (!ShouldProcessOnHit(hit_target->As<RE::Actor>()))
			return EventResult::kContinue;
		//---------------------------------------------------------------------------------------


		//----------------------Check Target AnimGraph-----------------------------------------

		auto thisgraph = GetAnimGraph(hit_target->As<RE::Actor>());

		if (!thisgraph) {
			logger::error("Not Animation Graph Found in the actor!");
			return EventResult::kContinue;
		}

		std::string graph_name = thisgraph->projectName.data();

		std::transform(graph_name.begin(), graph_name.end(), graph_name.begin(), [](unsigned char c) { return std::tolower(c); });

		logger::debug(FMT_STRING("Target Actor Graph Name is \"{}\"."), graph_name);

		auto this_AGObj = OHAF_datahandler->LookUpAGObjByName(graph_name);

		if (!this_AGObj) {
			logger::debug("Not Matching AnimGraph Type Found For Hit Target!");
			return EventResult::kContinue;
		}
		//-------------------------------------------------------------------------------


		//----------------------Check Hit Source---------------------------------------------

		auto hitsource = RE::TESForm::LookupByID<RE::TESObjectWEAP>(a_event->source);

		if (!hitsource) {
			logger::debug("Weapon Hit Source Not Found!");
			return EventResult::kContinue;
		}

		if (hitsource->formType != RE::FormType::Weapon) {
			logger::debug("Hit Source Is Not Weapon!");
			return EventResult::kContinue;
		} else
			logger::debug(FMT_STRING("Weapon Name is \"{}\", ID is \"{:x}\""), hitsource->GetName(), hitsource->GetFormID());

		//-------------------------------------------------------------------------------------


		//----------------------Check Hit Flag & Data--------------------------------------------------

		using HitFlag = RE::TESHitEvent::Flag;
		using Type = OHAF_AnimGraphObj::Type;
		/*
		if (a_event->flags & HitFlag::kHitBlocked) {
			logger::debug("Hit Is Blocked!");
			return EventResult::kContinue;
		}
		*/
		auto CheckHitFlag = [a_event](HitFlag flag, Type type) -> bool {
			if (flag == HitFlag::kNone)
				return true;

			switch (type) {
			case Type::kAny:
				return a_event->flags.any(flag);

			case Type::kAll:
				return a_event->flags.all(flag);

			case Type::kNone:
				return a_event->flags.none(flag);

			default:
				return false;
			}
		};

		for (int i = 0; i < Type::kTotal; i++) {
			if (!CheckHitFlag(this_AGObj->flags[i], Type(i))) {
				logger::debug(FMT_STRING("Hit Flag {} Not Matched!"), i);
				return EventResult::kContinue;
			}
		}

		auto lastHitData = [hit_target]() -> RE::HitData* {
			if (hit_target && hit_target->As<RE::Actor>()) {
				auto cur_process = hit_target->As<RE::Actor>()->currentProcess;
				if (cur_process && cur_process->middleHigh) {
					return cur_process->middleHigh->lastHitData;
				}
			}
			return nullptr;
		}();

		if (!lastHitData || lastHitData->totalDamage < this_AGObj->MinDamage) {
			logger::debug("Hit Damage too low!");
			return EventResult::kContinue;
		}
	
		//--------------------------------------------------------------------------------------------

		float AnimVarFloat = 0.f;

		hit_target->GetGraphVariableFloat(this_AGObj->VarFloatName, AnimVarFloat);

		logger::debug(FMT_STRING("Current Variable Float {} is {}"), this_AGObj->VarFloatName.c_str(), AnimVarFloat);

		if (abs(AnimVarFloat) <= 1e-6)	//Check if the Graph Variable Float value equal to zero.
		{
			hit_target->NotifyAnimationGraph(this_AGObj->EventName.c_str());
			logger::debug("Trigger an OnHit Event Successfully!");
		}

		return EventResult::kContinue;
	}
	//---------------------------------------------------------------------------------------------------------------------------------------------------------




	//-------------------------------------EffectStart Event-----------------------------------------------------------------------------------------------------

	bool OnEffectStartHandler::RegisterEffectStartEvent()
	{
		static OnEffectStartHandler g_effecteventhandler;

		auto ScriptEventSource = RE::ScriptEventSourceHolder::GetSingleton();

		if (!ScriptEventSource) {
			logger::error("ScriptEventSource not found!");
			return false;
		}

		ScriptEventSource->AddEventSink(&g_effecteventhandler);

		logger::info("Register OnEffectStart Event Handler!");

		return true;
	}


	EventResult OnEffectStartHandler::ProcessEvent(const RE::TESMagicEffectApplyEvent* a_event, RE::BSTEventSource<RE::TESMagicEffectApplyEvent>* a_eventSource)
	{
		if (!a_event || !a_eventSource) {
			logger::error("Event Source Not Found!");
			return EventResult::kContinue;
		}

		logger::debug("OnEffectStart Event Trigger!");

		auto OHAF_datahandler = DataHandler::GetSingleton();

		if (!OHAF_datahandler) {
			logger::error("OHAF DatahHandler Not Found!");
			return EventResult::kContinue;
		}


		//----------------------Check Effect Target-----------------------------------------------

		auto effect_target = a_event->target.get();

		if (!effect_target) {
			logger::error("Effect Target Not Found!");
			return EventResult::kContinue;
		}

		if (!ShouldProcessOnHit(effect_target->As<RE::Actor>()))
			return EventResult::kContinue;
		//----------------------------------------------------------------------------------------


		//----------------------Check Target AnimGraph--------------------------------------------

		auto thisgraph = GetAnimGraph(effect_target->As<RE::Actor>());

		if (!thisgraph) {
			logger::debug("Not Animation Graph Found in the actor!");
			return EventResult::kContinue;
		}

		std::string graph_name = thisgraph->projectName.data();

		std::transform(graph_name.begin(), graph_name.end(), graph_name.begin(), [](unsigned char c) { return std::tolower(c); });

		logger::debug(FMT_STRING("Target Actor Graph Name is {}."), graph_name);

		auto this_AGObj = OHAF_datahandler->LookUpAGObjByName(graph_name);

		if (!this_AGObj) {
			logger::debug("Not Matching AnimGraph Type Found For Effect Target!");
			return EventResult::kContinue;
		}

		//-----------------------------------------------------------------------------------------


		//----------------------Check Effect Type--------------------------------------------------

		using Flags = RE::EffectSetting::EffectSettingData::Flag;
		using CType = RE::MagicSystem::CastingType;
		using DType = RE::MagicSystem::Delivery;

		auto this_effect = RE::TESForm::LookupByID<RE::EffectSetting>(a_event->magicEffect);

		if (!this_effect) {
			logger::debug("Magic Effect Form Not Found!");
			return EventResult::kContinue;
		} else {
			logger::debug(FMT_STRING("Current Magic Effect name is \"{}\", ID is \"{:x}\""), this_effect->GetName(), this_effect->GetFormID());
		}


		if (!(this_effect->data.flags & Flags::kHostile)) {
			logger::debug("Magic Effect Is Not Hostile!");
			return EventResult::kContinue;
		}

		if (this_effect->data.flags & Flags::kPainless) {
			logger::debug("Magic Effect Is Painless!");
			return EventResult::kContinue;
		}


		if (this_effect->data.delivery == DType::kSelf) {
			logger::debug("Magic Effect Is Self Delivered");
			return EventResult::kContinue;
		}


		if (this_effect->GetMagickSkill() == RE::ActorValue::kAlteration || this_effect->GetMagickSkill() == RE::ActorValue::kConjuration || this_effect->GetMagickSkill() == RE::ActorValue::kIllusion) {
			logger::debug("Magic Skill Not Matched!");
			return EventResult::kContinue;
		}


		if (this_effect->data.castingType != CType::kFireAndForget && this_effect->data.castingType != CType::kScroll) {
			logger::debug("Magic Effect Is Not Fire And Forget!");
			return EventResult::kContinue;
		}


		if (!this_effect->conditions.IsTrue(a_event->caster.get(), a_event->target.get())) {
			logger::debug("Magic Effect Conditions Not Matched!");
			return EventResult::kContinue;
		}

		//----------------------------------------------------------------------------------------

		float AnimVarFloat = 0.f;

		effect_target->GetGraphVariableFloat(this_AGObj->VarFloatName, AnimVarFloat);

		logger::debug(FMT_STRING("Current Variable Float {} is {}"), this_AGObj->VarFloatName.c_str(), AnimVarFloat);

		if (abs(AnimVarFloat) <= 1e-6)	//Check if the Graph Variable Float value equal to zero.
		{
			effect_target->NotifyAnimationGraph(this_AGObj->EventName.c_str());
			logger::debug("Trigger an OnEffectStart Event Successfully!");
		}

		return EventResult::kContinue;
	}
	//---------------------------------------------------------------------------------------------------------------------------------------------------------

}
