#pragma once

#include "skse64_common/Utilities.h"
#include "skse64/PapyrusVM.h"

#include <vector>

class VMState;
class VMValue;
class VMClassRegistry;
struct StaticFunctionTag;
class EffectSetting;

class VMArgList
{
public:
	VMArgList();
	~VMArgList();

	MEMBER_FN_PREFIX(VMArgList);
	DEFINE_MEMBER_FN(GetOffset, UInt32, 0x0124F790, VMState * state);
	DEFINE_MEMBER_FN(Get, VMValue *, 0x0124F7F0, VMState * state, UInt32 idx, UInt32 offset);
};

template <typename T>
class VMArray
{
public:
	VMValue::ArrayData	* arr;
	UInt32 Length() const				{ return arr ? arr->len : 0; }
	void Get(T * dst, const UInt32 idx)	{ UnpackValue(dst, arr->GetData()+idx); }
	void Set(T * src, const UInt32 idx)
	{
		VMClassRegistry * registry = (*g_skyrimVM)->GetClassRegistry();
		PackValue(arr->GetData()+idx, src, registry);
	}
};

template<typename T>
class VMResultArray : public std::vector<T>
{
public:
	void PackArray(VMValue::ArrayData * data, VMClassRegistry * registry)
	{
		// Copy the contents from the reference array to the VM array
		UInt32 i = 0;
		for(std::vector<T>::iterator it = begin(); it != end(); ++it, i++) {
			VMValue * value = data->GetData() + i;
			PackValue(value, (T*)&(*it), registry);
			value->type = GetTypeID<T>(registry); // Always pack the type, even if empty data
		}
	}
};

template <typename T>
void PackValue(VMValue * dst, VMResultArray<T> * src, VMClassRegistry * registry)
{
	// Clear out old contents if any
	dst->SetNone();
	if(src->size() > 0) {
		VMValue::ArrayData * data = NULL;
		// Request the VM allocate a new array
		registry->CreateArray(dst, src->size(), &data);
		if(data) {
			src->PackArray(data, registry);

			// Set the appropriate TypeID and assign the new data array
			dst->type = GetTypeID<VMResultArray<T>>(registry);
			dst->data.arr = data;
		}
	}

	// Clear the temp contents of the reference array
	src->clear();
}

template <typename T>
void UnpackValue(VMArray<T*> * dst, VMValue * src)
{
	UnpackArray(dst, src, GetTypeIDFromFormTypeID(T::kTypeID, (*g_skyrimVM)->GetClassRegistry()) | VMValue::kType_Identifier);
}

template <typename T>
void PackValue(VMValue * dst, T * src, VMClassRegistry * registry);

template <typename T>
void UnpackValue(T * dst, VMValue * src);

template <typename T>
UInt64 GetTypeID(VMClassRegistry * registry);

template <> void PackValue <void>(VMValue * dst, void * src, VMClassRegistry * registry);
template <> void PackValue <UInt32>(VMValue * dst, UInt32 * src, VMClassRegistry * registry);
template <> void PackValue <SInt32>(VMValue * dst, SInt32 * src, VMClassRegistry * registry);
template <> void PackValue <float>(VMValue * dst, float * src, VMClassRegistry * registry);
template <> void PackValue <bool>(VMValue * dst, bool * src, VMClassRegistry * registry);
template <> void PackValue <BSFixedString>(VMValue * dst, BSFixedString * src, VMClassRegistry * registry);

void PackHandle(VMValue * dst, void * src, UInt32 typeID, VMClassRegistry * registry);

template <typename T>
void PackValue(VMValue * dst, T ** src, VMClassRegistry * registry)
{
	typedef std::remove_pointer <T>::type	BaseType;
	PackHandle(dst, *src, BaseType::kTypeID, registry);
}

template <> void UnpackValue <float>(float * dst, VMValue * src);
template <> void UnpackValue <UInt32>(UInt32 * dst, VMValue * src);
template <> void UnpackValue <SInt32>(SInt32 * dst, VMValue * src);
template <> void UnpackValue <bool>(bool * dst, VMValue * src);
template <> void UnpackValue <BSFixedString>(BSFixedString * dst, VMValue * src);

template <> void UnpackValue <VMArray<float>>(VMArray<float> * dst, VMValue * src);
template <> void UnpackValue <VMArray<UInt32>>(VMArray<UInt32> * dst, VMValue * src);
template <> void UnpackValue <VMArray<SInt32>>(VMArray<SInt32> * dst, VMValue * src);
template <> void UnpackValue <VMArray<bool>>(VMArray<bool> * dst, VMValue * src);
template <> void UnpackValue <VMArray<BSFixedString>>(VMArray<BSFixedString> * dst, VMValue * src);

void * UnpackHandle(VMValue * src, UInt32 typeID);

template <typename T>
void UnpackValue(T ** dst, VMValue * src)
{
	*dst = (T *)UnpackHandle(src, T::kTypeID);
}

template <typename T>
void UnpackArray(VMArray<T> * dst, VMValue * src, const UInt64 type)
{
	VMValue::ArrayData * arrData;

	if (src->type != type || (arrData = src->data.arr, !arrData))
	{
		dst->arr = NULL;
		return;
	}

	dst->arr = arrData;
}

UInt64 GetTypeIDFromFormTypeID(UInt32 formTypeID, VMClassRegistry * registry);

template <> UInt64 GetTypeID <void>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <UInt32>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <SInt32>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <int>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <float>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <bool>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <BSFixedString>(VMClassRegistry * registry);

template <> UInt64 GetTypeID <VMArray<UInt32>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMArray<SInt32>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMArray<int>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMArray<float>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMArray<bool>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMArray<BSFixedString>>(VMClassRegistry * registry);

template <> UInt64 GetTypeID <VMResultArray<UInt32>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMResultArray<SInt32>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMResultArray<int>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMResultArray<float>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMResultArray<bool>>(VMClassRegistry * registry);
template <> UInt64 GetTypeID <VMResultArray<BSFixedString>>(VMClassRegistry * registry);

template<typename T>
struct IsArrayType
{
	enum { value = 0 };
	typedef T TypedArg;
};

template<typename T>
struct IsArrayType<VMArray<T*>>
{
	enum { value = 1 };
	typedef T TypedArg;
};

template<typename T>
struct IsArrayType<VMResultArray<T*>>
{
	enum { value = 1 };
	typedef T TypedArg;
};

template <typename T>
UInt64 GetTypeID <T *>(VMClassRegistry * registry)
{
	UInt64		result;

	typedef std::remove_pointer <IsArrayType<T>::TypedArg>::type	BaseType;
	if(!IsArrayType<T>::value) {
		result = GetTypeIDFromFormTypeID(BaseType::kTypeID, registry);
	} else { // Arrays are ClassInfo ptr + 1
		result = GetTypeIDFromFormTypeID(BaseType::kTypeID, registry) | VMValue::kType_Identifier;
	}

	return result;
}

template <class T>
struct IsStaticType
{
	enum { value = 0 };
};

template <>
struct IsStaticType <StaticFunctionTag>
{
	enum { value = 1 };
};
