#include "skse64/InternalTasks.h"
#include "common/IMemPool.h"

#include "common/ICriticalSection.h"
#include <queue>

#include "skse64/GameAPI.h"
#include "skse64/GameReferences.h"
#include "skse64/GameData.h"
#include "skse64/GameForms.h"
#include "skse64/GameRTTI.h"

#include "skse64/NiNodes.h"
#include "skse64/NiExtraData.h"

#include "Hooks_Threads.h"

IThreadSafeBasicMemPool<SKSETaskUpdateTintMasks, 10>		s_updateTintMasksDelegatePool;
IThreadSafeBasicMemPool<SKSETaskUpdateHairColor, 10>		s_updateHairColorDelegatePool;
IThreadSafeBasicMemPool<SKSETaskUpdateWeight, 10>		s_updateWeightDelegatePool;
IThreadSafeBasicMemPool<SKSETaskRegenHead, 10>			s_regenHeadDelegatePool;
IThreadSafeBasicMemPool<SKSETaskChangeHeadPart, 10>		s_changeHeadPartDelegatePool;
IThreadSafeBasicMemPool<SKSETaskUpdateWorldData, 10>		s_updateWorldDataDelegatePool;
IThreadSafeBasicMemPool<SKSETaskUpdateExpression, 10>	s_updateExpressionDelegatePool;
IThreadSafeBasicMemPool<SKSETaskUpdateHarvestModel, 10>	s_updateHarvestModelDelegatePool;

void TaskInterface::UpdateTintMasks()
{
	SKSETaskUpdateTintMasks * cmd = s_updateTintMasksDelegatePool.Allocate();
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}

void TaskInterface::UpdateHairColor()
{
	SKSETaskUpdateHairColor * cmd = s_updateHairColorDelegatePool.Allocate();
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}

void TaskInterface::RegenerateHead(Actor * actor)
{
	SKSETaskRegenHead * cmd = SKSETaskRegenHead::Create(actor);
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}

void TaskInterface::ChangeHeadPart(Actor * actor, BGSHeadPart * oldPart, BGSHeadPart * newPart)
{
	SKSETaskChangeHeadPart * cmd = SKSETaskChangeHeadPart::Create(actor, oldPart, newPart);
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}

void TaskInterface::UpdateWeight(Actor * actor, float delta, UInt32 updateFlags, bool redrawWeapon)
{
	SKSETaskUpdateWeight * cmd = SKSETaskUpdateWeight::Create(actor, delta, updateFlags, redrawWeapon);
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}

void TaskInterface::UpdateWorldData(NiAVObject * object)
{
	SKSETaskUpdateWorldData * cmd = SKSETaskUpdateWorldData::Create(object);
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}

void TaskInterface::UpdateExpression(Actor * actor, UInt8 type, UInt16 index, float value)
{
	SKSETaskUpdateExpression * cmd = SKSETaskUpdateExpression::Create(actor, type, index, value);
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}

void TaskInterface::UpdateHarvestModel(TESObjectREFR * refr)
{
	SKSETaskUpdateHarvestModel * cmd = SKSETaskUpdateHarvestModel::Create(refr);
	if (cmd) {
		TaskInterface::AddTask(cmd);
	}
}


void SKSETaskUpdateTintMasks::Dispose(void)
{
	s_updateTintMasksDelegatePool.Free(this);
}

void SKSETaskUpdateTintMasks::Run()
{
	(*g_thePlayer)->UpdateSkinColor();
	UpdatePlayerTints();
}

void SKSETaskUpdateHairColor::Run()
{
	(*g_thePlayer)->UpdateHairColor();
}

void SKSETaskUpdateHairColor::Dispose(void)
{
	s_updateHairColorDelegatePool.Free(this);
}

SKSETaskRegenHead * SKSETaskRegenHead::Create(Actor * actor)
{
	SKSETaskRegenHead * cmd = s_regenHeadDelegatePool.Allocate();
	if (cmd)
	{
		cmd->m_actor = actor;
	}
	return cmd;
}

void SKSETaskRegenHead::Dispose(void)
{
	s_regenHeadDelegatePool.Free(this);
}

void SKSETaskRegenHead::Run()
{
	TESNPC * npc = DYNAMIC_CAST(m_actor->baseForm, TESForm, TESNPC);
	BSFaceGenNiNode * faceNode = m_actor->GetFaceGenNiNode();
	BGSHeadPart * facePart = npc->GetCurrentHeadPartByType(BGSHeadPart::kTypeFace);
	if (npc && faceNode && facePart) {
		CALL_MEMBER_FN(FaceGen::GetSingleton(), RegenerateHead)(faceNode, facePart, npc);
	}
}


SKSETaskChangeHeadPart * SKSETaskChangeHeadPart::Create(Actor * actor, BGSHeadPart* oldPart, BGSHeadPart* newPart)
{
	SKSETaskChangeHeadPart * cmd = s_changeHeadPartDelegatePool.Allocate();
	if (cmd)
	{
		cmd->m_actor = actor;
		cmd->m_newPart = newPart;
		cmd->m_oldPart = oldPart;
	}
	return cmd;
}

void SKSETaskChangeHeadPart::Dispose(void)
{
	s_changeHeadPartDelegatePool.Free(this);
}

void SKSETaskChangeHeadPart::Run()
{
	if (m_actor) {
		ChangeActorHeadPart(m_actor, m_oldPart, m_newPart);
	}
}

SKSETaskUpdateWeight * SKSETaskUpdateWeight::Create(Actor * actor, float delta, UInt32 updateFlags, bool redrawWeapon)
{
	SKSETaskUpdateWeight * cmd = s_updateWeightDelegatePool.Allocate();
	if (cmd)
	{
		cmd->m_actor = actor;
		cmd->m_delta = delta;
		cmd->m_updateFlags = updateFlags;
		cmd->m_redraw = redrawWeapon;
	}
	return cmd;
}

void SKSETaskUpdateWeight::Dispose(void)
{
	s_updateWeightDelegatePool.Free(this);
}

void SKSETaskUpdateWeight::Run()
{
	if (m_actor) {
		TESNPC * npc = DYNAMIC_CAST(m_actor->baseForm, TESForm, TESNPC);
		if (npc) {
			BSFaceGenNiNode * faceNode = m_actor->GetFaceGenNiNode();
			if (faceNode) {
				CALL_MEMBER_FN(faceNode, AdjustHeadMorph)(BSFaceGenNiNode::kAdjustType_Neck, 0, m_delta);
				UpdateModelFace(faceNode);
			}

			/*ActorWeightModel * lowModel = m_actor->GetWeightModel(ActorWeightModel::kWeightModel_Small);
			if(lowModel && lowModel->weightData)
			CALL_MEMBER_FN(lowModel->weightData, UpdateWeightData)();

			ActorWeightModel * highModel = m_actor->GetWeightModel(ActorWeightModel::kWeightModel_Large);
			if(highModel && highModel->weightData)
			CALL_MEMBER_FN(highModel->weightData, UpdateWeightData)();

			//UInt32 updateFlags = ActorEquipData::kFlags_Unk01 | ActorEquipData::kFlags_Unk02 | ActorEquipData::kFlags_Mobile;
			// Resets ActorState
			//updateFlags |= ActorEquipData::kFlags_DrawHead | ActorEquipData::kFlags_Reset;
			UInt32 updateFlags = m_updateFlags;

			CALL_MEMBER_FN(m_actor->processManager, SetEquipFlag)(updateFlags);
			CALL_MEMBER_FN(m_actor->processManager, UpdateEquipment)(m_actor);*/

			// Force redraw weapon, weight model update causes weapon position to be reset
			// Looking at DrawSheatheWeapon there is a lot of stuff going on, hard to find
			// out how to just manually move the weapon to its intended position
			if (m_redraw && m_actor->actorState.IsWeaponDrawn()) {
				m_actor->DrawSheatheWeapon(false);
				m_actor->DrawSheatheWeapon(true);
			}
		}
	}
}

SKSETaskUpdateWorldData * SKSETaskUpdateWorldData::Create(NiAVObject * object)
{
	SKSETaskUpdateWorldData * cmd = s_updateWorldDataDelegatePool.Allocate();
	if (cmd)
	{
		if (object)
			object->IncRef();
		cmd->m_object = object;
	}
	return cmd;
	return NULL;
}

void SKSETaskUpdateWorldData::Dispose(void)
{
	if (m_object)
		m_object->DecRef();
	s_updateWorldDataDelegatePool.Free(this);
}

void SKSETaskUpdateWorldData::Run()
{
	NiAVObject::ControllerUpdateContext ctx;
	m_object->UpdateWorldData(&ctx);
}

SKSETaskUpdateHarvestModel * SKSETaskUpdateHarvestModel::Create(TESObjectREFR * reference)
{
	SKSETaskUpdateHarvestModel * cmd = s_updateHarvestModelDelegatePool.Allocate();
	if (cmd)
	{
		cmd->m_reference = reference;
	}
	return cmd;
}

void SKSETaskUpdateHarvestModel::Dispose(void)
{
	s_updateHarvestModelDelegatePool.Free(this);
}

void SKSETaskUpdateHarvestModel::Run()
{
	if (m_reference) {
		NiNode * rootModel = m_reference->GetNiNode();
		if (rootModel)
			UpdateHarvestModel(m_reference, rootModel);
	}
}

SKSETaskUpdateExpression * SKSETaskUpdateExpression::Create(Actor * actor, UInt8 type, UInt16 index, float value)
{
	SKSETaskUpdateExpression * cmd = s_updateExpressionDelegatePool.Allocate();
	if (cmd)
	{
		cmd->m_actor = actor;
		cmd->m_type = type;
		cmd->m_index = index;
		if (value < 0.0f)
			value = 0.0f;
		if (value > 1.0f)
			value = 1.0f;
		cmd->m_value = value;
	}
	return cmd;
}

void SKSETaskUpdateExpression::Dispose(void)
{
	s_updateExpressionDelegatePool.Free(this);
}

void SKSETaskUpdateExpression::Run()
{
	BSFaceGenAnimationData * animationData = m_actor->GetFaceGenAnimationData();
	if (animationData) {
		if (m_type != BSFaceGenAnimationData::kKeyframeType_Reset) {
			FaceGen::GetSingleton()->isReset = 0;
			BSFaceGenKeyframeMultiple * keyframe = &animationData->keyFrames[m_type];
			if (m_index >= keyframe->count)
				return;
			keyframe->values[m_index] = m_value;
			keyframe->isUpdated = 0;
		}
		else {
			animationData->overrideFlag = 0;
			CALL_MEMBER_FN(animationData, Reset)(1.0, 1, 1, 0, 0);
			FaceGen::GetSingleton()->isReset = 1;
		}
		NiNode * face = m_actor->GetFaceGenNiNode();
		if (face)
			UpdateModelFace(face);
	}
}
