#include "ParamInfos.h"
#include "obse/Commands_Array.h"
#include "CommandTable.h"
#include "obse_editor\EditorAPI.h"
#include "GameForms.h"
#include "ScriptUtils.h"

#if OBLIVION

#include "GameAPI.h"

static const double s_arrayErrorCodeNum = -99999;		// sigil return values for cmds returning array keys
static std::string s_arrayErrorCodeStr = "";		// indicating invalid/non-existent key

// DW Nifscript stuff
#include "obse_common/SafeWrite.h"
#include "GameObjects.h"

static UInt32 kModelMap_LookupHookAddr = 0x0043A278;
static UInt32 kModelMap_LookupRetnAddr = 0x0043A27E;

struct ModelMapEntry
{
	struct Data
	{
		char		* filePath;
		UInt32		refCount;
		NiNode		* niNode;
	};

	char			* key;		// all lowercase
	Data			* data;
	ModelMapEntry	* next;
};

static void __stdcall DoModelMapHook(ModelMapEntry** bucket, char* path, UInt32 modelLoader, UInt32 modelMap)
{
	_MESSAGE("DoModelMapHook entry path: %s bucket: %04X", path, bucket);

	// are we interested in this file?
	if (!path || !(path[0] == '#'))
	{
		_MESSAGE("DoModelMapHook: sigil not found");
		return;
	}

	// is it in the bucket already?
	ModelMapEntry* cur = *bucket;
	ModelMapEntry* prev = NULL;

	while (cur)
	{
		int cmp = _stricmp(cur->key, path);
		if (cmp == 0)
			return;			// already in bucket
		else if (cmp > 0)	// cur comes first alphabetically
		{
			prev = cur;
			cur = cur->next;
			continue;
		}

		// found place to insert new entry
		break;
	}

	// presumably here you'd look up/create a NiNode based on the filename
	// if allocated outside of game, will need to COPY its contents to form heap
	// for testing purposes, using a NiNode* obtained from elsewhere
	// I'm testing in the starting dungeon so using a persistent door ref, assume NiNode will be loaded
	// however using a NiNode which is in use by other objects is asking for trouble

	TESObjectREFR* form = (TESObjectREFR*)LookupFormByID(0x1FC44);
	if (!form)
	{
		_MESSAGE("ModelMapHook: form not found");
		return;
	}

	NiNode* niNode = form->GetNiNode();
	if (!niNode)
	{
		_MESSAGE("ModelMapHook: niNode not found");
		return;
	}
/*....
	NiNode* niNode = (NiNode*)FormHeap_Allocate(sizeof(NiNode));
	memcpy(niNode, stockNode, sizeof(niNode));
	niNode->m_uiRefCount = 1;
....*/
	// create ModelMapEntry and data	
	ModelMapEntry::Data* modelData = (ModelMapEntry::Data*)FormHeap_Allocate(sizeof(ModelMapEntry::Data));
	ModelMapEntry* entry = (ModelMapEntry*)FormHeap_Allocate(sizeof(ModelMapEntry));

	UInt32 pathLen = strlen(path);
	char* pathCopy = (char*)FormHeap_Allocate(pathLen + 1);
	memcpy(pathCopy, path, pathLen);
	pathCopy[pathLen] = 0;

	modelData->filePath = pathCopy;
	modelData->refCount = 1;		// probably not correct
	modelData->niNode = niNode;

	char* pathCopy2 = (char*)FormHeap_Allocate(pathLen + 1);
	memcpy(pathCopy2, pathCopy, pathLen);
	pathCopy2[pathLen] = 0;

	entry->data = modelData;
	entry->key = pathCopy2;
	entry->next = NULL;	

	// add to bucket
	// assumption: entries stored alphabetically by key
	if (!prev)
		*bucket = entry;
	else
		prev->next = entry;

	entry->next = cur;

	_MESSAGE("ModelMapHook: exit");
}

static __declspec(naked) void ModelMap_LookupHook(void)
{
	// on entry: 
	//	ebx = key (filepath, all lowercase)
	//	edx = hash (bucket)
	//	esi = ModelLoader*
	//	eax = LockFreeCaseInsensitiveStringMap <Model> *

	// if filepath is modified by NifScript, first character will be '#'

	__asm {
		pushad

		push eax		// ModelMap*
		push esi		// ModelLoader*
		push ebx		// const char* key)

		// calc bucket
		mov		ecx,		[eax+0x0C]
		lea		eax,		[ecx+edx*4]
	
		// call our hook
		push	eax		// ModelMapEntry*
		call	DoModelMapHook

		popad

		// calc bucket again
		mov		ecx,		[eax+0x0C]
		lea		eax,		[ecx+edx*4]

		// get out of here
		jmp		[kModelMap_LookupRetnAddr]
	}
}

static bool Cmd_InstallModelMapHook_Execute(COMMAND_ARGS)
{
	WriteRelJump(kModelMap_LookupHookAddr, (UInt32)&ModelMap_LookupHook);
	return true;
}

static bool Cmd_ar_Construct_Execute(COMMAND_ARGS)
{
	char arType[0x200];
	*result = 0;

	if (!ExpressionEvaluator::Active())
	{
		Console_Print("Error in script %08X: ar_Construct called outside of a valid context.", scriptObj->refID);
		return true;
	}

	if (!ExtractArgs(PASS_EXTRACT_ARGS, &arType))
		return true;

	UInt8 keyType = kDataType_Numeric;
	bool bPacked = false;
	if (!_stricmp(arType, "StringMap"))
		keyType = kDataType_String;
	else if (_stricmp(arType, "Map"))
		bPacked = true;

	ArrayID newArr = g_ArrayMap.Create(keyType, bPacked, scriptObj->GetModIndex());
	*result = newArr;
	return true;
}

static bool Cmd_ar_Size_Execute(COMMAND_ARGS)
{
	*result = -1;
	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.Arg(0))
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
		{
			ArrayID arrID = arrToken.value.arrID;
			UInt32 size = g_ArrayMap.SizeOf(arrID);
			*result = (size == -1) ? -1 : size;
		}
	}

	return true;
}

static bool Cmd_ar_Dump_Execute(COMMAND_ARGS)
{
	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.Arg(0))
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
			g_ArrayMap.DumpArray(arrToken.value.arrID);
	}

	return true;
}

static bool Cmd_ar_DumpID_Execute(COMMAND_ARGS)
{
	UInt32 arrayID = 0;
	if (ExtractArgs(PASS_EXTRACT_ARGS, &arrayID))
	{
		if (!g_ArrayMap.Exists(arrayID))
			Console_Print("Array does not exist.");
		else if (!g_ArrayMap.SizeOf(arrayID))
			Console_Print("Array is empty.");
		else
			g_ArrayMap.DumpArray(arrayID);
	}

	return true;
}

static bool Cmd_ar_Erase_Execute(COMMAND_ARGS)
{
	// returns num elems erased or -1 on error
	*result = -1;
	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.Arg(0) && eval.Arg(1))
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
		{
			ArrayID arrID = arrToken.value.arrID;

			// are we erasing a range or a single element?
			Slice* slice = eval.Arg(1)->GetSlice();
			if (slice)
			{
				ArrayKey lo, hi;
				slice->GetArrayBounds(lo, hi);
				*result = g_ArrayMap.EraseElements(arrID, lo, hi);
			}
			else
			{
				ScriptToken keyToken = eval.Arg(1)->ConvertTo(kTokenType_String, &eval);
				if (keyToken.IsGood())
				{
					ArrayKey toErase(keyToken.value.str);
					*result = g_ArrayMap.EraseElements(arrID, toErase, toErase);
				}
				else
				{
					keyToken = eval.Arg(1)->ConvertTo(kTokenType_Number, &eval);
					if (keyToken.IsGood())
					{
						ArrayKey toErase(keyToken.value.num);
						*result = g_ArrayMap.EraseElements(arrID, toErase, toErase);
					}
				}
			}
		}
	}

	return true;
}

static bool Cmd_ar_Sort_Execute(COMMAND_ARGS)
{
	// we return this empty array if something goes wrong
	ArrayID sortedID = g_ArrayMap.Create(kDataType_Numeric, true, scriptObj->GetModIndex());

	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.Arg(0))
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
		{
			ArrayVarMap::SortOrder order = ArrayVarMap::kSort_Ascending;
			if (eval.Arg(1) && eval.Arg(1)->ConvertTo(kTokenType_Number, &eval).value.num)
				order = ArrayVarMap::kSort_Descending;
			sortedID = g_ArrayMap.Sort(arrToken.value.arrID, order, ArrayVarMap::kSortType_Default, scriptObj->GetModIndex());
		}
	}

	*result = sortedID;
	return true;
}

static bool Cmd_ar_SortAlpha_Execute(COMMAND_ARGS)
{
	// we return this empty array if something goes wrong
	ArrayID sortedID = g_ArrayMap.Create(kDataType_Numeric, true, scriptObj->GetModIndex());

	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.Arg(0))
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
		{
			ArrayVarMap::SortOrder order = ArrayVarMap::kSort_Ascending;
			if (eval.Arg(1) && eval.Arg(1)->ConvertTo(kTokenType_Number, &eval).value.num)
				order = ArrayVarMap::kSort_Descending;
			sortedID = g_ArrayMap.Sort(arrToken.value.arrID, order, ArrayVarMap::kSortType_Alpha, scriptObj->GetModIndex());
		}
	}

	*result = sortedID;
	return true;
}

static ArrayElement BasicTokenToElem(ScriptToken* token, ExpressionEvaluator* context)
{
	// ### doesn't support converting to an ArrayID
	ScriptToken resolved = token->ConvertTo(kTokenType_Number, context);
	ArrayElement elem;
	if (resolved.IsGood())
		elem.SetNumber(resolved.value.num);
	else
	{		
		resolved = token->ConvertTo(kTokenType_String, context);
		if (resolved.IsGood())
			elem.SetString(resolved.value.str);
		else
		{
			resolved = token->ConvertTo(kTokenType_Form, context);
			if (resolved.IsGood())
				elem.SetFormID(resolved.value.formID);
			else
			{
				resolved = token->ConvertTo(kTokenType_Array, context);
				if (resolved.IsGood())
					elem.SetArray(resolved.value.arrID, g_ArrayMap.GetOwningModIndex(resolved.value.arrID));
			}
		}
	}

	return elem;
}

static bool Cmd_ar_Find_Execute(COMMAND_ARGS)
{
	*result = 0;
	if (!ExpressionEvaluator::Active())
	{
		ShowRuntimeError(scriptObj, "ar_Find must be called within an OBSE expression.");
		return true;
	}

	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.NumArgs() >= 2)
	{
		// what type of element are we looking for?
		ArrayElement toFind = BasicTokenToElem(eval.Arg(0), &eval);
		
		// get the array
		ScriptToken arrToken = eval.Arg(1)->ConvertTo(kTokenType_Array, &eval);
		if (!arrToken.IsGood())
			return true;

		// set result to the default (not found)
		UInt8 keyType = g_ArrayMap.GetKeyType(arrToken.value.arrID);
		if (keyType == kDataType_String)
		{
			ExpressionEvaluator::ExpectReturnType(kRetnType_String);
			*result = g_StringMap.Add(scriptObj->GetModIndex(), s_arrayErrorCodeStr.c_str(), true);
		}
		else
		{
			ExpressionEvaluator::ExpectReturnType(kRetnType_Default);
			*result = s_arrayErrorCodeNum;
		}
		
		if (!toFind.IsGood())		// return error code if toFind couldn't be resolved
			return true;

		// got a range?
		Slice* range = NULL;
		if (eval.Arg(2))
			range = eval.Arg(2)->GetSlice();

		// do the search
		ArrayKey idx = g_ArrayMap.Find(arrToken.value.arrID, toFind, range);
		if (!idx.IsValid())
			return true;

		if (keyType == kDataType_Numeric)
			*result = idx.Key().num;
		else
			*result = g_StringMap.Add(scriptObj->GetModIndex(), idx.Key().str.c_str(), true);
	}

	return true;
}

static enum eIterMode {
	kIterMode_First,
	kIterMode_Last,
	kIterMode_Next,
	kIterMode_Prev
};

static bool ArrayIterCommand(COMMAND_ARGS, eIterMode iterMode)
{
	*result = 0;
	if (!ExpressionEvaluator::Active())
	{
		ShowRuntimeError(scriptObj, "ar_First/Last must be called within an OBSE expression.");
		return true;
	}

	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.NumArgs())
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
		{
			UInt8 keyType = g_ArrayMap.GetKeyType(arrToken.value.arrID);
			ArrayKey foundKey;
			ArrayElement foundElem;		// unused
			switch (keyType)
			{
			case kDataType_Numeric:
				{
					*result = s_arrayErrorCodeNum;
					ExpressionEvaluator::ExpectReturnType(kRetnType_Default);
					if (iterMode == kIterMode_First)
						g_ArrayMap.GetFirstElement(arrToken.value.arrID, &foundElem, &foundKey);
					else if (iterMode == kIterMode_Last)
						g_ArrayMap.GetLastElement(arrToken.value.arrID, &foundElem, &foundKey);
					else if (eval.NumArgs() == 2)
					{
						ScriptToken numIdxToken = eval.Arg(1)->ConvertTo(kTokenType_Number, &eval);
						if (numIdxToken.IsGood())
						{
							ArrayKey curKey = numIdxToken.value.num;
							switch (iterMode)
							{
							case kIterMode_Next:
								g_ArrayMap.GetNextElement(arrToken.value.arrID, &curKey, &foundElem, &foundKey);
								break;
							case kIterMode_Prev:
								g_ArrayMap.GetPrevElement(arrToken.value.arrID, &curKey, &foundElem, &foundKey);
								break;
							}
						}
					}

					if (foundKey.IsValid())
						*result = foundKey.Key().num;

					return true;
				}
			case kDataType_String:
				{
					ExpressionEvaluator::ExpectReturnType(kRetnType_String);
					std::string keyStr = s_arrayErrorCodeStr;
					if (iterMode == kIterMode_First)
						g_ArrayMap.GetFirstElement(arrToken.value.arrID, &foundElem, &foundKey);
					else if (iterMode == kIterMode_Last)
						g_ArrayMap.GetLastElement(arrToken.value.arrID, &foundElem, &foundKey);
					else if (eval.NumArgs() == 2)
					{
						ScriptToken strIdxToken = eval.Arg(1)->ConvertTo(kTokenType_String, &eval);
						if (strIdxToken.IsGood())
						{
							ArrayKey curKey = strIdxToken.value.str;
							switch (iterMode)
							{
							case kIterMode_Next:
								g_ArrayMap.GetNextElement(arrToken.value.arrID, &curKey, &foundElem, &foundKey);
								break;
							case kIterMode_Prev:
								g_ArrayMap.GetPrevElement(arrToken.value.arrID, &curKey, &foundElem, &foundKey);
								break;
							}
						}
					}

					if (foundKey.IsValid())
						keyStr = foundKey.Key().str;

					AssignToStringVar(PASS_COMMAND_ARGS, keyStr.c_str());
					return true;
				}
			default:
				{
					eval.Error("Invalid array key type in ar_First/Last. Check that the array has been initialized.");
					return true;
				}
			}
		}
		else
		{
			eval.Error("Invalid array access in ar_First/Last. Is the array initialized?");
			return true;
		}
	}

	return true;
}
						
static bool Cmd_ar_First_Execute(COMMAND_ARGS)
{
	return ArrayIterCommand(PASS_COMMAND_ARGS, kIterMode_First);
}

static bool Cmd_ar_Last_Execute(COMMAND_ARGS)
{
	return ArrayIterCommand(PASS_COMMAND_ARGS, kIterMode_Last);
}

static bool Cmd_ar_Next_Execute(COMMAND_ARGS)
{
	return ArrayIterCommand(PASS_COMMAND_ARGS, kIterMode_Next);
}

static bool Cmd_ar_Prev_Execute(COMMAND_ARGS)
{
	return ArrayIterCommand(PASS_COMMAND_ARGS, kIterMode_Prev);
}

static bool Cmd_ar_Keys_Execute(COMMAND_ARGS)
{
	*result = 0;
	if (!ExpressionEvaluator::Active())
	{
		ShowRuntimeError(scriptObj, "ar_Keys must be called within an OBSE expression.");
		return true;
	}

	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.Arg(0))
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
			*result = g_ArrayMap.GetKeys(arrToken.value.arrID, scriptObj->GetModIndex());
	}

	return true;
}

static bool Cmd_ar_HasKey_Execute(COMMAND_ARGS)
{
	*result = 0;
	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.NumArgs() == 2)
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
		{
			UInt8 keyType = g_ArrayMap.GetKeyType(arrToken.value.arrID);
			if (keyType == kDataType_String)
			{
				ScriptToken strToken = eval.Arg(1)->ConvertTo(kTokenType_String, &eval);
				if (strToken.IsGood())
					*result = g_ArrayMap.HasKey(arrToken.value.arrID, ArrayKey(strToken.value.str)) ? 1 : 0;
			}
			else if (keyType == kDataType_Numeric)
			{
				ScriptToken numToken = eval.Arg(1)->ConvertTo(kTokenType_Number, &eval);
				if (numToken.IsGood())
					*result = g_ArrayMap.HasKey(arrToken.value.arrID, ArrayKey(numToken.value.num)) ? 1 : 0;
			}
		}
	}

	return true;
}

static bool Cmd_ar_BadStringIndex_Execute(COMMAND_ARGS)
{
	AssignToStringVar(PASS_COMMAND_ARGS, s_arrayErrorCodeStr.c_str());
	return true;
}

static bool Cmd_ar_BadNumericIndex_Execute(COMMAND_ARGS)
{
	*result = s_arrayErrorCodeNum;
	return true;
}

static bool ArrayCopyCommand(COMMAND_ARGS, bool bDeepCopy)
{
	*result = 0;
	if (!ExpressionEvaluator::Active())
	{
		ShowRuntimeError(scriptObj, "Array copy command must be called within an OBSE expression.");
		return true;
	}

	ExpressionEvaluator eval(PASS_COMMAND_ARGS);
	if (eval.ExtractArgs() && eval.NumArgs() == 1)
	{
		ScriptToken arrToken = eval.Arg(0)->ConvertTo(kTokenType_Array, &eval);
		if (arrToken.IsGood())
			*result = g_ArrayMap.Copy(arrToken.value.arrID, scriptObj->GetModIndex(), bDeepCopy);
	}

	return true;
}

static bool Cmd_ar_Copy_Execute(COMMAND_ARGS)
{
	return ArrayCopyCommand(PASS_COMMAND_ARGS, false);
}

static bool Cmd_ar_DeepCopy_Execute(COMMAND_ARGS)
{
	return ArrayCopyCommand(PASS_COMMAND_ARGS, true);
}

static bool Cmd_ar_Null_Execute(COMMAND_ARGS)
{
	*result = 0;
	if (!ExpressionEvaluator::Active())
		ShowRuntimeError(scriptObj, "ar_Null must be called within an OBSE expression.");
	
	return true;
}

#endif

/*
static bool Cmd_Array_Parse(UInt32 numParams, ParamInfo* paramInfo, ScriptLineBuffer* lineBuf, ScriptBuffer* scriptBuf)
{
#if OBLIVION
	Console_Print("This command cannot be called from the console.");
	return false;
#endif

	ExpressionParser parser(scriptBuf, lineBuf);
	Token_Type result = parser.Parse();
	if (CanConvertOperand(result, kTokenType_Array))
		return true;

	ShowCompilerError(lineBuf, "Expression does not evaluate to an array.");
	return false;
}
*/

CommandInfo kCommandInfo_ar_Construct =
{
	"ar_Construct",
	"",
	0,
	"creates a new array",
	0,
	1,
	kParams_OneString,
	HANDLER(Cmd_ar_Construct_Execute),
	Cmd_Default_Parse,
	NULL,
	0
};

static ParamInfo kParams_OneArray[] =
{
	{	"array",	kOBSEParamType_Array,	0	},
};

CommandInfo kCommandInfo_ar_Size =
{
	"ar_Size",
	"",
	0,
	"returns the size of an array or -1 if the array does not exist",
	0,
	1,
	kParams_OneArray,
	HANDLER(Cmd_ar_Size_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_Dump =
{
	"ar_Dump",
	"",
	0,
	"dumps the contents of an array for debugging purposes.",
	0,
	1,
	kParams_OneArray,
	HANDLER(Cmd_ar_Dump_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

DEFINE_COMMAND(ar_DumpID, dumps an array given an integer ID, 0, 1, kParams_OneInt);


static ParamInfo kParams_ar_Erase[] =
{
	{	"array",			kOBSEParamType_Array,	0	},
	{	"index or range",	kOBSEParamType_ArrayIndex | kOBSEParamType_Slice,	0	},
};

CommandInfo kCommandInfo_ar_Erase =
{
	"ar_Erase",
	"",
	0,
	"erases an element or range of elements from an array",
	0,
	2,
	kParams_ar_Erase,
	HANDLER(Cmd_ar_Erase_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

static ParamInfo kParams_OneAssignableArray[] =
{
	{	"array",	kOBSEParamType_ArrayVarOrElement,	0	},
};

static ParamInfo kParams_ar_Sort[] =
{
	{	"array",		kOBSEParamType_Array,	0	},
	{	"bDescending",	kOBSEParamType_Number,	1	},
};

CommandInfo kCommandInfo_ar_Sort =
{
	"ar_Sort",
	"",
	0,
	"returns an array containing the source array's elements in sorted order",
	0,
	2,
	kParams_ar_Sort,
	HANDLER(Cmd_ar_Sort_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_SortAlpha =
{
	"ar_SortAlpha",
	"",
	0,
	"returns an array containing the source array's elements in alphabetical order",
	0,
	2,
	kParams_ar_Sort,
	HANDLER(Cmd_ar_SortAlpha_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

static ParamInfo kParams_ar_Find[] =
{
	{	"valueToFind",	kOBSEParamType_BasicType,	0	},
	{	"array",		kOBSEParamType_Array,		0	},
	{	"range",		kOBSEParamType_Slice,		1	},
};

CommandInfo kCommandInfo_ar_Find =
{
	"ar_Find",
	"",
	0,
	"finds the first occurence of a value within an array",
	0,
	3,
	kParams_ar_Find,
	HANDLER(Cmd_ar_Find_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_First =
{
	"ar_First",
	"",
	0,
	"returns the key of the first element in an array",
	0,
	1,
	kParams_OneArray,
	HANDLER(Cmd_ar_First_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_Last =
{
	"ar_Last",
	"",
	0,
	"returns the key of the last element in an array",
	0,
	1,
	kParams_OneArray,
	HANDLER(Cmd_ar_Last_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

static ParamInfo kParams_ArrayAndKey[] =
{
	{	"array",			kOBSEParamType_Array,		0	},
	{	"array index",	kOBSEParamType_ArrayIndex,	0	},
};

CommandInfo kCommandInfo_ar_Next =
{
	"ar_Next",
	"",
	0,
	"returns the key of the element immediately following the specified key",
	0,
	2,
	kParams_ArrayAndKey,
	HANDLER(Cmd_ar_Next_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_Prev =
{
	"ar_Prev",
	"",
	0,
	"returns the key of the element immediately preceding the specified key",
	0,
	2,
	kParams_ArrayAndKey,
	HANDLER(Cmd_ar_Prev_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_Keys =
{
	"ar_Keys",
	"",
	0,
	"returns an Array containing the keys of the specified array",
	0,
	1,
	kParams_OneArray,
	HANDLER(Cmd_ar_Keys_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_HasKey =
{
	"ar_HasKey",
	"",
	0,
	"returns 1 if the array contains an element with the specified key",
	0,
	2,
	kParams_ArrayAndKey,
	HANDLER(Cmd_ar_HasKey_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

DEFINE_COMMAND(ar_BadStringIndex, returns the return value of a bad string array index, 0, 0, NULL);
DEFINE_COMMAND(ar_BadNumericIndex, returns the return value of a bad numeric array index, 0, 0, NULL);

CommandInfo kCommandInfo_ar_Copy =
{
	"ar_Copy",
	"",
	0,
	"returns a shallow copy of an array",
	0,
	1,
	kParams_OneArray,
	HANDLER(Cmd_ar_Copy_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

CommandInfo kCommandInfo_ar_DeepCopy =
{
	"ar_DeepCopy",
	"",
	0,
	"returns a deep copy of an array, copying any arrays contained in that array",
	0,
	1,
	kParams_OneArray,
	HANDLER(Cmd_ar_DeepCopy_Execute),
	Cmd_Expression_Parse,
	NULL,
	0
};

DEFINE_COMMAND(ar_Null, makes an array variable refer to no array, 0, 0, NULL);

DEFINE_COMMAND(InstallModelMapHook, nothing, 0, 0, NULL);




