#include "obse/GameObjects.h"
#include "obse/GameAPI.h"
#include "obse/GameData.h"

typedef Sky * (* _Sky_GetSingleton)(void);

#if OBLIVION_VERSION == OBLIVION_VERSION_1_1

PlayerCharacter ** g_thePlayer = (PlayerCharacter **)0x00AEAAE4;

static const UInt32	s_Actor_EquipItem =					0x005E6380;
static const UInt32	s_Actor_GetBaseActorValue =			0x005E4D30;

static const UInt32	s_PlayerCharacter_SetActiveSpell =	0x00650C30;

static const _Sky_GetSingleton	Sky_GetSingleton = (_Sky_GetSingleton)0x00537420;
static const UInt32				s_Sky_RefreshClimate = 0x00537C00;

#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2

PlayerCharacter ** g_thePlayer = (PlayerCharacter **)0x00B333C4;

static const UInt32	s_Actor_EquipItem =					0x005FACE0;
static const UInt32	s_Actor_GetBaseActorValue =			0x005F1750;

static const UInt32	s_PlayerCharacter_SetActiveSpell =	0x006641B0;

static const _Sky_GetSingleton	Sky_GetSingleton = (_Sky_GetSingleton)0x00542F10;
static const UInt32				s_Sky_RefreshClimate = 0x00543270;

#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2_416

PlayerCharacter ** g_thePlayer = (PlayerCharacter **)0x00B333C4;

static const UInt32	s_Actor_EquipItem =					0x005FAEA0;
static const UInt32	s_Actor_GetBaseActorValue =			0x005F1910;

static const UInt32	s_PlayerCharacter_SetActiveSpell =	0x00664700;

static const _Sky_GetSingleton	Sky_GetSingleton = (_Sky_GetSingleton)0x00542EA0;
static const UInt32				s_Sky_RefreshClimate = 0x00543200;

#else

#error unsupported version of oblivion

#endif

void Actor::EquipItem(TESObjectREFR * objType, UInt32 unk1, UInt32 unk2, UInt32 unk3, bool lockEquip)
{
	ThisStdCall(s_Actor_EquipItem, this, objType, unk1, unk2, unk3, lockEquip);
}

UInt32 Actor::GetBaseActorValue(UInt32 value)
{
	return ThisStdCall(s_Actor_GetBaseActorValue, this, value);
}

EquippedItemsList Actor::GetEquippedItems()
{
	EquippedItemsList itemList;

	ExtraContainerChanges	* xChanges = static_cast <ExtraContainerChanges *>(baseExtraList.GetByType(kExtraData_ContainerChanges));
	if(xChanges && xChanges->data && xChanges->data->objList)
		for(ExtraContainerChanges::Entry * entry = xChanges->data->objList; entry; entry = entry->next)
			if(entry->data && entry->data->extendData && entry->data->type)
				for(ExtraContainerChanges::EntryExtendData * extend = entry->data->extendData; extend; extend = extend->next)
					if(extend->data && extend->data->HasType(kExtraData_Worn) || extend->data->HasType(kExtraData_WornLeft))
						itemList.push_back(entry->data->type);

	return itemList;
}

bool PlayerCharacter::SetActiveSpell(MagicItem * item)
{
	return ThisStdCall(s_PlayerCharacter_SetActiveSpell, this, item) != 0;
}

void PlayerCharacter::TogglePOV(bool bFirstPerson)
{
#if OBLIVION_VERSION == OBLIVION_VERSION_1_1
	ThisStdCall(0x00655560, this, bFirstPerson);
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2
	ThisStdCall(0x0066C040, this, bFirstPerson);
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2_416
	ThisStdCall(0x0066C580, this, bFirstPerson);
#else
#error unsupported Oblivion version 
#endif
}

class UsedPowerFinder
{
	SpellItem	* m_toFind;
public:
	UsedPowerFinder(SpellItem* power) : m_toFind(power) { }

	bool Accept(const Actor::PowerListData* data)
	{
		return (data->power == m_toFind);
	}
};

bool Actor::CanCastGreaterPower(SpellItem* power)
{
	Visitor<PowerListEntry, PowerListData> visitor(&greaterPowerList);
	if (visitor.Find(UsedPowerFinder(power)))
		return false;
	else
		return true;
}

void Actor::SetCanUseGreaterPower(SpellItem* power, bool bAllowUse, float timer)
{
	Visitor<PowerListEntry, PowerListData> visitor(&greaterPowerList);
	const PowerListEntry* foundEntry = visitor.Find(UsedPowerFinder(power));

	if (bAllowUse && foundEntry)
	{
		visitor.Remove(foundEntry->data);
	}
	else if (!bAllowUse && !foundEntry)
	{
		PowerListData* nuData = (PowerListData*)FormHeap_Allocate(sizeof(PowerListData));
		
		if (timer == -1)
		{
			TESGlobal* timeScaleGlob = (*g_dataHandler)->GetGlobalVarByName("TimeScale", strlen("TimeScale"));
			if (!timeScaleGlob)			// what?
				nuData->timer = 2800;
			else
				nuData->timer = 3600 / timeScaleGlob->data * 24;
		}
		else
			nuData->timer = timer;

		nuData->power = power;

		PowerListEntry* nuEntry = (PowerListEntry*)FormHeap_Allocate(sizeof(PowerListEntry));
		nuEntry->data = nuData;
		nuEntry->next = NULL;

		visitor.Append(nuEntry);
	}
	else if (!bAllowUse && foundEntry && timer != -1)		// change timer on power already in list
	{
		foundEntry->data->timer = timer;
	}
}

void Actor::PowerListEntry::Delete()
{
	FormHeap_Free(data);
	FormHeap_Free(this);
}

void Actor::PowerListEntry::DeleteHead(PowerListEntry* replaceWith)
{
	FormHeap_Free(data);
	if (replaceWith)
	{
		data = replaceWith->data;
		next = replaceWith->next;
		FormHeap_Free(replaceWith);
	}
	else
		memset(this, 0, sizeof(PowerListEntry));
}

Sky * Sky::GetSingleton(void)
{
	return Sky_GetSingleton();
}

void Sky::RefreshClimate(TESClimate * climate, UInt32 unk1)
{
	ThisStdCall(s_Sky_RefreshClimate, this, climate, unk1);
}

ScriptEventList* TESObjectREFR::GetEventList() const
{
	BSExtraData* xData = baseExtraList.GetByType(kExtraData_Script);
	if (xData)
	{
		ExtraScript* xScript = (ExtraScript*)Oblivion_DynamicCast(xData, 0, RTTI_BSExtraData, RTTI_ExtraScript, 0);
		if (xScript)
			return xScript->eventList;
	}

	return 0;
}

TESForm* TESObjectREFR::GetInventoryItem(UInt32 itemIndex, bool bGetWares)
{
	//if getWares == true, looks up info in g_DataHandler->unkCDC

	ExtraContainerChanges::EntryData* data;

#if OBLIVION_VERSION == OBLIVION_VERSION_1_1
	data = (ExtraContainerChanges::EntryData*)ThisStdCall(0x4CEB10, this, itemIndex, bGetWares);
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2
	data = (ExtraContainerChanges::EntryData*)ThisStdCall(0x4D88E0, this, itemIndex, bGetWares);
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2_416
	data = (ExtraContainerChanges::EntryData*)ThisStdCall(0x4D88F0, this, itemIndex, bGetWares);
#else
	#error unsupported Oblivion version
#endif

	if (data)
		return data->type;
	else
		return NULL;
}

typedef void (*_DisableRef)(TESObjectREFR *refr);
typedef void (*_EnableRef)(TESObjectREFR *refr);

#if OBLIVION_VERSION == OBLIVION_VERSION_1_1
const _DisableRef DisableRef = (_DisableRef)0x004F16A0;
const _EnableRef EnableRef = (_EnableRef)0x004F0650;
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2
const _DisableRef DisableRef = (_DisableRef)0x004FBC80;
const _EnableRef EnableRef = (_EnableRef)0x004FA5F0;
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2_416
const _DisableRef DisableRef = (_DisableRef)0x004FBB30;
const _EnableRef EnableRef = (_EnableRef)0x004FA540;
#else
#error unsupported version of oblivion
#endif

void TESObjectREFR::Disable()
{
	DisableRef(this);
}

void TESObjectREFR::Enable()
{
	EnableRef(this);
}

bool TESObjectREFR::RunScripts()
{
#if OBLIVION_VERSION == OBLIVION_VERSION_1_1
#error unsupported Oblivion version
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2
#error unsupported Oblivion version
#elif OBLIVION_VERSION == OBLIVION_VERSION_1_2_416
	return ThisStdCall(0x004D7190, this) ? true : false;
#else
#error unsupported Oblivion version
#endif
}

bool TESObjectREFR::IsDeleted() const
{
	if (flags & kFlags_Deleted)
	{
		DEBUG_PRINT("Ref %08x is flagged as deleted", refID);
		return true;
	}

	return false;
}
