#pragma once

#include "VarMap.h"
#include "Serialization.h"
#include "GameAPI.h"
#include <map>

// OBSE array datatype, represented by std::map<ArrayKey, ArrayElement>
// Data elements can be of mixed types (string, UInt32/formID, float)
// Keys can be doubles or strings
// Can optionally be treated as vector (i.e. removal of an element shifts upper elements down)
// Commands such as GetEquippedItems can return arrays

// Array variable interface is based on ArrayIDs, which are simply integers identifying the array
// All array variable operations go through ArrayVarMap interface

// TODO: Support for iteration via script (ForEach?)

/* Serialization layout
String	::= { UInt16 dataLen; char data[dataLen]; }
Element ::= { double num || string str || UInt32 arrayID || UInt32 formID }
Key		::= { string || double }

	ARVS - empty chunk indicating start of array variables
		ARVR
			UInt8	modIndex
			UInt32	ID
			UInt8	keyType
			bool	packed
** v1 **	UInt32  numRefs       <- references to this array (by variables or array element)
** v1 **	UInt8   refs[numRefs] <- mod indexes of each reference to array; on load, fix up, discard those from unloaded mods
			UInt32	numElements
			
			//for each element
			Key			key
			UInt8		elementType
			Element		data
		[ARVR]
			...
	ARVE - empty chunk indicating end of variables

As with string variables, array vars discarded on load if owning mod no longer present in modlist

*/

class ArrayVar;
class ArrayVarMap;
struct Slice;
struct ArrayKey;
struct ArrayElement;

typedef UInt32 ArrayID;

#if OBLIVION

enum
{
	kDataType_Invalid,

	kDataType_Numeric,
	kDataType_Form,
	kDataType_String,
	kDataType_Array,
};

struct ArrayType {
	union {
		double		num;
		UInt32		formID;
	};

	std::string		str;
};

struct ArrayElement
{
	friend class ArrayVar;
	friend class ArrayVarMap;

	ArrayType	m_data;
	UInt8		m_dataType;
	ArrayID		m_owningArray;

	void  Unset();
	std::string ToString() const;
public:
	UInt8 DataType() const { return m_dataType; }

	bool GetAsNumber(double* out);
	bool GetAsString(std::string& out);
	bool GetAsFormID(UInt32* out);
	bool GetAsArray(ArrayID* out);

	bool SetForm(const TESForm* form);
	bool SetFormID(UInt32 refID);
	bool SetString(const std::string& str);
	bool SetArray(ArrayID arr, UInt8 modIndex);		// creates a copy of arr, sets m_data to array ID
	bool SetNumber(double num);
	bool Set(const ArrayElement& elem);

	ArrayElement();

	static bool CompareAsString(const ArrayElement& lhs, const ArrayElement& rhs);

	bool operator<(const ArrayElement& rhs) const;
	bool Equals(const ArrayElement& compareTo) const;

	bool IsGood() { return m_dataType != kDataType_Invalid;	}
};

struct ArrayKey
{
private:
	ArrayType	key;
	UInt8		keyType;
public:
	ArrayKey();
	ArrayKey(std::string& _key);
	ArrayKey(double _key);
	ArrayKey(const char* _key);

	ArrayType	Key() const	{	return key;	}
	UInt8		KeyType() const { return keyType; }
	void		SetNumericKey(double newVal)	{	keyType = kDataType_Numeric; key.num = newVal;	}
	bool		IsValid() const { return keyType != kDataType_Invalid;	}

	bool operator<(const ArrayKey& rhs) const;
	bool operator==(const ArrayKey& rhs) const;
	bool operator>=(const ArrayKey& rhs) const { return !(*this < rhs);	}
	bool operator>(const ArrayKey& rhs) const { return !(*this < rhs || *this == rhs); }
	bool operator<=(const ArrayKey& rhs) const { return !(*this > rhs); }
};

class ArrayVar
{
	friend class ArrayVarMap;

	std::map<ArrayKey, ArrayElement> m_elements;
	ArrayID				m_ID;
	UInt8				m_owningModIndex;
	UInt8				m_keyType;
	bool				m_bPacked;
	std::vector<UInt8>	m_refs;		// data is modIndex of referring object; size() is number of references

	UInt32 GetUnusedIndex();

	explicit ArrayVar(UInt8 modIndex);
	ArrayVar(UInt32 keyType, bool packed, UInt8 modIndex);

	ArrayElement* Get(ArrayKey& key, bool bCanCreateNew);

	UInt32 ID()		{ return m_ID;	}
	UInt32 Size()  { return m_elements.size(); }
	UInt8 KeyType() { return m_keyType; }
	bool IsPacked() { return m_bPacked; }
	void Pack();

	void Dump();

public:
	~ArrayVar();
};

class ArrayVarMap : public VarMap<ArrayVar>
{
	// this gets incremented whenever serialization format changes
	static const UInt32 kVersion = 1;

	void Add(ArrayVar* var, UInt32 varID, UInt32 numRefs, UInt8* refs);
public:
	ArrayVarMap()	{	}
	~ArrayVarMap()	{	}

	enum SortOrder
	{
		kSort_Ascending,
		kSort_Descending,
	};

	enum SortType
	{
		kSortType_Default,
		kSortType_Alpha
	};

	void Save(OBSESerializationInterface* intfc);
	void Load(OBSESerializationInterface* intfc);
	void Clean();

	// operations on ArrayVars
	ArrayID	Create(UInt32 keyType, bool bPacked, UInt8 modIndex);
	void    AddReference(ArrayID* ref, ArrayID toRef, UInt8 referringModIndex);
	void    RemoveReference(ArrayID* ref, UInt8 referringModIndex);
	void    AddReference(double* ref, ArrayID toRef, UInt8 referringModIndex);
	void	RemoveReference(double* ref, UInt8 referringModIndex);
	ArrayID Copy(ArrayID from, UInt8 modIndex, bool bDeepCopy);
	ArrayID MakeSlice(ArrayID src, Slice* slice, UInt8 modIndex);
	UInt32	GetKeyType(ArrayID id);
	bool	Exists(ArrayID id);
	void	Erase(ArrayID toErase);
	void	DumpArray(ArrayID toDump);
	UInt32	SizeOf(ArrayID id);
	UInt32  EraseElements(ArrayID id, ArrayKey& lo, ArrayKey& hi);	// returns num erased
	ArrayID Sort(ArrayID src, SortOrder order, SortType type, UInt8 modIndex);
	ArrayKey Find(ArrayID toSearch, const ArrayElement& toFind, Slice* range = NULL);
	std::string GetTypeString(ArrayID arr);
	UInt8	GetOwningModIndex(ArrayID id);
	ArrayID GetKeys(ArrayID id, UInt8 modIndex);
	bool	HasKey(ArrayID id, ArrayKey& key);

	// operations on ArrayElements
	bool SetElementNumber(ArrayID id, ArrayKey key, double num);
	bool SetElementString(ArrayID id, ArrayKey key, const std::string& str);
	bool SetElementFormID(ArrayID id, ArrayKey key, UInt32 refID);
	bool SetElementArray(ArrayID id, ArrayKey key, ArrayID srcID);
	bool SetElement(ArrayID id, ArrayKey key, const ArrayElement& val);

	bool GetElementNumber(ArrayID id, ArrayKey key, double* out);
	bool GetElementString(ArrayID id, ArrayKey key, std::string& out);
	bool GetElementFormID(ArrayID id, ArrayKey key, UInt32* out);
	bool GetElementForm(ArrayID id, ArrayKey key, TESForm** out);
	bool GetElementArray(ArrayID id, ArrayKey key, ArrayID* out);
	bool GetElementAsBool(ArrayID id, ArrayKey key, bool* out);

	bool GetFirstElement(ArrayID id, ArrayElement* outElem, ArrayKey* outKey);
	bool GetLastElement(ArrayID id, ArrayElement* outElem, ArrayKey* outKey);
	bool GetNextElement(ArrayID id, ArrayKey* prevKey, ArrayElement* outElem, ArrayKey* outKey);
	bool GetPrevElement(ArrayID id, ArrayKey* prevKey, ArrayElement* outElem, ArrayKey* outKey);
};

extern ArrayVarMap g_ArrayMap;

#endif

typedef std::map<ArrayKey, ArrayElement>::iterator ArrayIterator;