#include "..\..\FalloutEngine\Fallout2.h"
#include "..\..\SafeWrite.h"
#include "..\HookScripts.h"
#include "..\CritterPoison.h"
#include "Common.h"

#include "ObjectHs.h"

// Object hook scripts
namespace sfall
{

static long UseObjOnHook_Script(fo::GameObject* source, fo::GameObject* item, fo::GameObject* target) {
	BeginHook();
	argCount = 3;

	args[0] = (DWORD)target; // target
	args[1] = (DWORD)source; // user
	args[2] = (DWORD)item;   // item

	RunHookScript(HOOK_USEOBJON);

	long result = (cRet > 0) ? rets[0] : -1;
	EndHook();

	return result; // -1 - default handler
}

long UseObjOnHook_Invoke(fo::GameObject* source, fo::GameObject* item, fo::GameObject* target) {
	if (!HookScripts::HookHasScript(HOOK_USEOBJON)) return -1;
	return UseObjOnHook_Script(source, item, target);
}

static void __declspec(naked) UseObjOnHook() {
	__asm {
		HookBegin;
		mov args[0], edx; // target
		mov args[4], eax; // user
		mov args[8], ebx; // object
		pushadc;
	}

	argCount = 3;
	RunHookScript(HOOK_USEOBJON);

	__asm {
		popadc;
		cmp cRet, 1;
		jl  defaultHandler;
		cmp rets[0], -1;
		je  defaultHandler;
		mov eax, rets[0];
		HookEnd;
		retn;
defaultHandler:
		HookEnd;
		jmp fo::funcoffs::protinst_use_item_on_;
	}
}

static void __declspec(naked) Drug_UseObjOnHook() {
	__asm {
		HookBegin;
		mov args[0], eax; // target
		mov args[4], eax; // user
		mov args[8], edx; // object
		pushadc;
	}

	argCount = 3;
	RunHookScript(HOOK_USEOBJON);

	__asm {
		popadc;
		cmp cRet, 1;
		jl  defaultHandler;
		cmp rets[0], -1;
		je  defaultHandler;
		mov eax, rets[0];
		HookEnd;
		retn;
defaultHandler:
		HookEnd;
		jmp fo::funcoffs::item_d_take_drug_;
	}
}

static void __declspec(naked) UseObjHook() {
	__asm {
		HookBegin;
		mov args[0], eax; // user
		mov args[4], edx; // object
		pushadc;
	}

	argCount = 2;
	RunHookScript(HOOK_USEOBJ);

	__asm {
		popadc;
		cmp cRet, 1;
		jl  defaultHandler;
		cmp rets[0], -1;
		je  defaultHandler;
		mov eax, rets[0];
		HookEnd;
		retn;
defaultHandler:
		HookEnd;
		jmp fo::funcoffs::protinst_use_item_;
	}
}

static DWORD __fastcall UseAnimateObjHook_Script(DWORD critter, DWORD animCode, DWORD object) {
	BeginHook();
	argCount = 3;

	args[0] = critter;
	args[1] = object;
	args[2] = animCode;

	RunHookScript(HOOK_USEANIMOBJ);

	if (cRet > 0 && static_cast<long>(rets[0]) <= 64) {
		animCode = rets[0]; // new anim code
	}
	EndHook();

	return animCode;
}

// Before animation of using map object
static void __declspec(naked) UseAnimateObjHook() {
	__asm {
		cmp  dword ptr [esp], 0x412292 + 5;
		push eax;
		push ecx;
		jne  contr;
		push ebp;                      // map object
		jmp  next;
contr:
		push edi;                      // map object
next:
		mov  ecx, eax;                 // source critter
		call UseAnimateObjHook_Script; // edx - anim code
		pop  ecx;
		cmp  eax, -1;                  // return anim code
		jle  end;                      // goto no animate
		mov  edx, eax;                 // restore vanilla or hook anim code
		pop  eax;
		jmp  fo::funcoffs::register_object_animate_;
end:
		pop  eax;
		retn;
	}
}

static DWORD __fastcall DescriptionObjHook_Script(DWORD object) {
	BeginHook();
	argCount = 1;

	args[0] = object;

	RunHookScript(HOOK_DESCRIPTIONOBJ);

	DWORD textPrt = (cRet > 0) ? rets[0] : 0;
	EndHook();

	return textPrt;
}

static void __declspec(naked) DescriptionObjHook() {
	__asm {
		push ecx;
		push edx;
		mov  ecx, eax; // object
		call DescriptionObjHook_Script;
		pop  edx;
		pop  ecx;
		test eax, eax; // pointer to text
		jnz  skip;
		mov  eax, ebp;
		jmp  fo::funcoffs::object_description_;
skip:
		retn;
	}
}

static bool __fastcall SetLightingHook_Script(DWORD &intensity, DWORD &radius, DWORD object) {
	BeginHook();
	argCount = 3;

	args[0] = object;
	args[1] = intensity;
	args[2] = radius;
	RunHookScript(HOOK_SETLIGHTING);

	bool result = false;
	if (cRet > 0) {
		int light = rets[0];
		if (light < 0) light = 0;
		intensity = light;
		if (cRet > 1 && object != -1) {
			int dist = rets[1];
			if (dist < 0) dist = 0;
			radius = dist;
		}
		result = true;
	}
	EndHook();
	return result;
}

static void __declspec(naked) SetObjectLightHook() {
	__asm {
		pushadc;
		push ebp;
		mov  edx, esp;  // &radius
		push ebx;
		mov  ecx, esp;  // &intensity
		push esi;       // object
		call SetLightingHook_Script;
		test al, al;
		jz   skip;
		pop  ebx;       // return intensity value
		pop  ebp;       // return radius value
		jmp  end;
skip:
		add  esp, 8;
end:
		popadc;
		jmp  fo::funcoffs::obj_turn_off_light_;
	}
}

static void __declspec(naked) SetMapLightHook() {
	__asm {
		push ecx;
		push ebx;
		mov  ecx, esp;  // &intensity
		push -1;        // no object (it's a map)
		mov  edx, esp;  // no radius
		call SetLightingHook_Script;
		add  esp, 4;
		test al, al;
		jz   skip;
		mov  ebx, [esp - 4]; // return intensity value
skip:
		pop  ecx;
		cmp  ebx, dword ptr ds:[0x47A93D]; // get miminal ambient light intensity (16384)
		retn;
	}
}

static DWORD __fastcall StdProcedureHook_Script(long numHandler, fo::ScriptInstance* script, DWORD procTable) {
	BeginHook();
	argCount = 4;

	args[0] = numHandler;
	args[1] = (DWORD)script->selfObject;
	args[2] = (DWORD)script->sourceObject;

	if (procTable) {
		args[3] = 0;
		RunHookScript(HOOK_STDPROCEDURE);

		if (cRet > 0) {
			long retval = rets[0];
			if (retval == -1) procTable = retval;
		}
	} else {
		args[3] = 1;
		RunHookScript(HOOK_STDPROCEDURE_END);
	}
	EndHook();
	return procTable;
}

static void __declspec(naked) ScriptStdProcedureHook() {
	using namespace fo::Scripts;
	__asm {
		mov  eax, [eax + 0x54]; // Script.procedure_table
		test eax, eax;
		jle  end;
		cmp  ecx, critter_p_proc;
		je   skip;
		cmp  ecx, timed_event_p_proc;
		je   skip;
		cmp  ecx, map_update_p_proc;
		je   skip;
		cmp  ecx, start;
		jle  skip;
		push ecx;
		push eax;      // procTable
		mov  edx, esi; // script
		call StdProcedureHook_Script; // ecx - numHandler
		pop  ecx;
skip:
		test eax, eax;
end:
		retn;
	}
}

static void __declspec(naked) After_ScriptStdProcedureHook() {
	using namespace fo::Scripts;
	__asm {
		call fo::funcoffs::executeProcedure_;
		cmp  ecx, critter_p_proc;
		je   skip;
		cmp  ecx, timed_event_p_proc;
		je   skip;
		cmp  ecx, map_update_p_proc;
		je   skip;
		cmp  ecx, start;
		jle  skip;
		mov  edx, [esp + 0x28 - 0x18 + 4]; // script
		push 0;                            // procTable
		call StdProcedureHook_Script;      // ecx - numHandler
skip:
		retn;
	}
}

static DWORD __fastcall AdjustPoison_Script(DWORD critter, long amount, DWORD addr) {
	BeginHook();
	argCount = 3;

	bool checkPoison = (addr == (0x42D32C + 5)); // from critter_check_poison_

	args[0] = critter;
	args[1] = amount;
	args[2] = (checkPoison) ? CritterPoison::adjustPoisonHP_Default : 0;

	RunHookScript(HOOK_ADJUSTPOISON);

	if (cRet > 0) amount = rets[0];
	if (cRet > 1 && checkPoison && (long)rets[1] < 0) CritterPoison::adjustPoisonHP = rets[1];

	EndHook();
	return amount;
}

static void __declspec(naked) critter_adjust_poison_hack() {
	__asm {
		push [esp + 0x24 + 4];    // called from
		mov  ecx, eax;            // critter
		call AdjustPoison_Script; // edx - amount
		mov  esi, eax;            // old/new amount
		mov  eax, edi;            // restore eax value
		jmp  critter_adjust_poison_hack_fix; // in CritterPoison
	}
}

static DWORD __fastcall AdjustRads_Script(DWORD critter, long amount) {
	if (!HookScripts::HookHasScript(HOOK_ADJUSTRADS)) return amount;

	BeginHook();
	argCount = 2;

	args[0] = critter; // always dude
	args[1] = amount;

	RunHookScript(HOOK_ADJUSTRADS);
	if (cRet) amount = rets[0];

	EndHook();
	return amount;
}

void __declspec(naked) critter_adjust_rads_hack() {
	using namespace fo;
	using namespace Fields;
	__asm {
		cmp  dword ptr [eax + protoId], PID_Player; // critter.pid
		jne  isNotDude;
		push ecx;
		call AdjustRads_Script; // ecx - critter, edx - amount
		pop  ecx;
		mov  ebx, eax;          // old/new amount
		mov  edx, ds:[FO_VAR_obj_dude];
		xor  eax, eax;          // for continue func
isNotDude:
		retn;
	}
}

void Inject_UseObjOnHook() {
	HookCalls(UseObjOnHook, { 0x49C606, 0x473619 });

	// the following hooks allows to catch drug use of AI and from action cursor
	HookCalls(Drug_UseObjOnHook, {
		//0x4285DF, // ai_check_drugs
		//0x4286F8, // ai_check_drugs
		//0x4287F8, // ai_check_drugs
		0x473573  // inven_action_cursor
	});
}

void Inject_UseObjHook() {
	HookCalls(UseObjHook, { 0x42AEBF, 0x473607, 0x49C12E });
}

void Inject_UseAnimateObjHook() {
	HookCalls(UseAnimateObjHook, { 0x4120C1, 0x412292 });
}

void Inject_DescriptionObjHook() {
	HookCall(0x49AE28, DescriptionObjHook);
}

void Inject_SetLightingHook() {
	HookCall(0x48ACA0, SetObjectLightHook);
	MakeCall(0x47A934, SetMapLightHook, 1);
}

void Inject_ScriptProcedureHook() {
	MakeCall(0x4A491F, ScriptStdProcedureHook);
}

void Inject_ScriptProcedureHook2() {
	HookCall(0x4A49A7, After_ScriptStdProcedureHook);
}

void Inject_AdjustPoisonHook() {
	MakeCall(0x42D21C, critter_adjust_poison_hack, 1);
}

void Inject_AdjustRadsHook() {
	MakeCall(0x42D3B0, critter_adjust_rads_hack, 1);
	SafeWrite16(0x42D3B6, 0xC085); // test eax, eax
}

void InitObjectHookScripts() {
	HookScripts::LoadHookScript("hs_useobjon", HOOK_USEOBJON);
	HookScripts::LoadHookScript("hs_useobj", HOOK_USEOBJ);
	HookScripts::LoadHookScript("hs_useanimobj", HOOK_USEANIMOBJ);
	HookScripts::LoadHookScript("hs_descriptionobj", HOOK_DESCRIPTIONOBJ);
	HookScripts::LoadHookScript("hs_setlighting", HOOK_SETLIGHTING);
	HookScripts::LoadHookScript("hs_stdprocedure", HOOK_STDPROCEDURE); // combo hook
	HookScripts::LoadHookScript("hs_stdprocedure", HOOK_STDPROCEDURE_END);
	HookScripts::LoadHookScript("hs_adjustpoison", HOOK_ADJUSTPOISON);
	HookScripts::LoadHookScript("hs_adjustrads", HOOK_ADJUSTRADS);
}

}
