scriptname SKI_FavoritesManager extends SKI_QuestBase

import Math

; SCRIPT VERSION ----------------------------------------------------------------------------------
;
; History
;
; 1:	- Initial version
;
; 2:	- Added check for vampire lord
;
; 3:	- Less eagerly clearing of invalid entries
;
; 4:    - EDC - Added repeat find to InvalidateItem()

int function GetVersion()
	return 4
endFunction


; CONSTANTS ---------------------------------------------------------------------------------------

string property		FAVORITES_MENU	= "FavoritesMenu" autoReadonly
string property		MENU_ROOT		= "_root.MenuHolder.Menu_mc" autoReadonly

int property		GROUP_FLAG_UNEQUIP_ARMOR	= 1	autoReadonly
int property		GROUP_FLAG_UNEQUIP_HANDS	= 2	autoReadonly


; PROPERTIES --------------------------------------------------------------------------------------

; -- Version 1 --

Actor Property		PlayerREF auto

bool property		ButtonHelpEnabled	= true auto

int property GroupAddKey
	int function get()
		return _groupAddKey
	endFunction

	function set(int a_val)
		SwapControlKey(a_val, _groupAddKey)
		_groupAddKey = a_val
	endFunction
endProperty

int property GroupUseKey
	int function get()
		return _groupUseKey
	endFunction

	function set(int a_val)
		SwapControlKey(a_val, _groupUseKey)
		_groupUseKey = a_val
	endFunction
endProperty

int property SetIconKey
	int function get()
		return _setIconKey
	endFunction

	function set(int a_val)
		SwapControlKey(a_val, _setIconKey)
		_setIconKey = a_val
	endFunction
endProperty

int property SaveEquipStateKey
	int function get()
		return _saveEquipStateKey
	endFunction

	function set(int a_val)
		SwapControlKey(a_val, _saveEquipStateKey)
		_saveEquipStateKey = a_val
	endFunction
endProperty

int property ToggleFocusKey
	int function get()
		return _toggleFocusKey
	endFunction

	function set(int a_val)
		SwapControlKey(a_val, _toggleFocusKey)
		_toggleFocusKey = a_val
	endFunction
endProperty


; PRIVATE VARIABLES -------------------------------------------------------------------------------

; -- Version 1 --

Form[]				_items1
Form[]				_items2
int[]				_itemIds1
int[]				_itemIds2

int[]				_groupFlags

Form[]				_groupMainHandItems
int[]				_groupMainHandItemIds

Form[]				_groupOffHandItems
int[]				_groupOffHandItemIds

Form[]				_groupIconItems
int[]				_groupIconItemIds

bool				_silenceEquipSounds = false

SoundCategory		_audioCategoryUI

; Forms to support EquipSlot comparisons
EquipSlot 			_rightHandSlot
EquipSlot 			_eitherHandSlot
EquipSlot 			_leftHandSlot
EquipSlot 			_bothHandsSlot
EquipSlot 			_voiceSlot

; State variables for Group Use
bool				_usedRightHand		= false
bool				_usedLeftHand		= false
bool				_usedVoice			= false
int					_usedOutfitMask		= 0

; Keys
int					_groupAddKey		= 33 ; F
int					_groupUseKey		= 19 ; R
int					_setIconKey			= 56 ; LAlt
int					_saveEquipStateKey	= 20 ; T
int					_toggleFocusKey		= 57 ; Space

int[]				_groupHotkeys

; -- Version 2 --

Race				_vampireLordRace

; -- Version 3 --

bool[]				_itemInvalidFlags1
bool[]				_itemInvalidFlags2


; INITIALIZATION ----------------------------------------------------------------------------------

event OnInit()
	_items1			= new Form[128]
	_items2			= new Form[128]
	_itemIds1		= new int[128]
	_itemIds2		= new int[128]

	_groupFlags		= new int[8]

	_groupMainHandItems		= new Form[8]
	_groupMainHandItemIds	= new int[8]

	_groupOffHandItems		= new Form[8]
	_groupOffHandItemIds	= new int[8]
	
	_groupIconItems		= new Form[8]
	_groupIconItemIds	= new int[8]

	_groupHotkeys = new int[8]
	_groupHotkeys[0] = 59	; F1
	_groupHotkeys[1] = 60	; F2
	_groupHotkeys[2] = 61	; F3
	_groupHotkeys[3] = 62	; F4
	_groupHotkeys[4] = -1
	_groupHotkeys[5] = -1
	_groupHotkeys[6] = -1
	_groupHotkeys[7] = -1

	_audioCategoryUI	= Game.GetFormFromFile(0x00064451, "Skyrim.esm") as SoundCategory

	_rightHandSlot 		= Game.GetFormFromFile(0x00013f42, "Skyrim.esm") as EquipSlot
	_leftHandSlot 		= Game.GetFormFromFile(0x00013f43, "Skyrim.esm") as EquipSlot
	_eitherHandSlot		= Game.GetFormFromFile(0x00013f44, "Skyrim.esm") as EquipSlot
	_bothHandsSlot 		= Game.GetFormFromFile(0x00013f45, "Skyrim.esm") as EquipSlot
	_voiceSlot	 		= Game.GetFormFromFile(0x00025bee, "Skyrim.esm") as EquipSlot
	
	OnGameReload()
endEvent

; @implements SKI_QuestBase
event OnGameReload()
	CheckVersion()

	RegisterForModEvent("SKIFM_groupAdd", "OnGroupAdd")
	RegisterForModEvent("SKIFM_groupRemove", "OnGroupRemove")
	RegisterForModEvent("SKIFM_groupUse", "OnGroupUse")
	RegisterForModEvent("SKIFM_saveEquipState", "OnSaveEquipState")
	RegisterForModEvent("SKIFM_setGroupIcon", "OnSetGroupIcon")
	RegisterForModEvent("SKIFM_foundInvalidItem", "OnFoundInvalidItem")
	
	RegisterForMenu(FAVORITES_MENU)
	
	RegisterHotkeys()

	CleanUp()
endEvent

; @implements SKI_QuestBase
event OnVersionUpdate(int a_version)

	; Version 2
	if (a_version >= 2 && CurrentVersion < 2)
		Debug.Trace(self + ": Updating to script version 2")

		;_vampireLordRace	= Game.GetFormFromFile(0x0000283A, "Dawnguard.esm") as Race
	endIf

	; Version 3
	if (a_version >= 3 && CurrentVersion < 3)
		Debug.Trace(self + ": Updating to script version 3")

		_itemInvalidFlags1 = new bool[128]
		_itemInvalidFlags2 = new bool[128]
	endIf

	; Version 4
	if (a_version >= 4 && CurrentVersion < 4)
		Debug.Trace(self + ": Updating to script version 4")
	endIf
	
endEvent


; EVENTS ------------------------------------------------------------------------------------------

event OnMenuOpen(string a_menuName)

	int i = 0
	while (i < 128)
		_itemInvalidFlags1[i] = false
		i += 1
	endWhile

	i = 0
	while (i < 128)
		_itemInvalidFlags2[i] = false
		i += 1
	endWhile

	InitControls()
	InitMenuGroupData()
endEvent

event OnFoundInvalidItem(string a_eventName, string a_strArg, float a_numArg, Form a_sender)
	InvalidateItem(a_strArg as int,true)
endEvent

event OnGroupAdd(string a_eventName, string a_strArg, float a_numArg, Form a_sender)
	int		groupIndex = a_numArg as int
	int		itemId = a_strArg as int
	Form	item = a_sender

	if (GroupAdd(groupIndex, itemId, item))
		UpdateMenuGroupData(groupIndex)
	else
		UI.InvokeBool(FAVORITES_MENU, MENU_ROOT + ".unlock", true)
		Debug.Notification("Group full!")
	endIf
endEvent

event OnGroupRemove(string a_eventName, string a_strArg, float a_numArg, Form a_sender)
	int		groupIndex = a_numArg as int
	int		itemId = a_strArg as int

	if (GroupRemove(groupIndex, itemId))
		UpdateMenuGroupData(groupIndex)
	else
		UI.InvokeBool(FAVORITES_MENU, MENU_ROOT + ".unlock", true)
	endIf
endEvent

; Read the player's current equipment and save it to the target group
event OnSaveEquipState(string a_eventName, string a_strArg, float a_numArg, Form a_sender)
	int groupIndex = a_numArg as int
	
	int mainHandItemId = UI.GetInt(FAVORITES_MENU, MENU_ROOT + ".rightHandItemId")
	int offHandItemId = UI.GetInt(FAVORITES_MENU, MENU_ROOT + ".leftHandItemId")

	form mainHandForm = GetFormFromItemId(groupIndex,mainHandItemId) ; will return none if not in group
	if (mainHandForm)
		_groupMainHandItemIds[groupIndex] = mainHandItemId
		_groupMainHandItems[groupIndex] = mainHandForm
	else
		_groupMainHandItemIds[groupIndex] = 0
		_groupMainHandItems[groupIndex] = none
	endIf

	form offHandForm = GetFormFromItemId(groupIndex,offHandItemId)
	if (offHandForm)
		_groupOffHandItemIds[groupIndex] = offHandItemId
		_groupOffHandItems[groupIndex] = offHandForm
	else
		_groupOffHandItemIds[groupIndex] = 0
		_groupOffHandItems[groupIndex] = none
	endIf
	
	UpdateMenuGroupData(groupIndex)
endEvent

; This will set a form as the icon form for a group
event OnSetGroupIcon(string a_eventName, string a_strArg, float a_numArg, Form a_sender)
	int		groupIndex = a_numArg as int
	int		itemId = a_strArg as int
	Form	item = a_sender

	_groupIconItems[groupIndex] = item
	_groupIconItemIds[groupIndex] = itemId

	UpdateMenuGroupData(groupIndex)
endEvent

event OnGroupUse(string a_eventName, string a_strArg, float a_numArg, Form a_sender)
	gotoState("PROCESSING")

	GroupUse(a_numArg as int)

	gotoState("")
endEvent

event OnKeyDown(int a_keyCode)
	gotoState("PROCESSING")

	int groupIndex = _groupHotkeys.Find(a_keyCode)
	if (groupIndex != -1 && !Utility.IsInMenuMode())
		GroupUse(groupIndex)
	endIf

	gotoState("")
endEvent

state PROCESSING
	
	event OnGroupUse(string a_eventName, string a_strArg, float a_numArg, Form a_sender)
	endEvent

	event OnKeyDown(int a_keyCode)
	endEvent

endState


; FUNCTIONS ---------------------------------------------------------------------------------------

;get whether a flag is set for the specified group
bool function GetGroupFlag(int a_groupIndex, int a_flag)
    return LogicalAnd(_groupFlags[a_groupIndex], a_flag) as bool
endFunction
 
;set a flag for the specified group
function SetGroupFlag(int a_groupIndex, int a_flag, bool a_value)
	if (a_value)
		_groupFlags[a_groupIndex] = LogicalOr(_groupFlags[a_groupIndex], a_flag)
	else
		_groupFlags[a_groupIndex] = LogicalAnd(_groupFlags[a_groupIndex], LogicalNot(a_flag))
	endIf
endFunction

int[] function GetGroupHotkeys()
	; Return a copy
	int[] result = new int[8]
	int i = 0
	while (i<8)
		result[i] = _groupHotkeys[i]
		i += 1
	endWhile
	return result
endFunction

bool function SetGroupHotkey(int a_groupIndex, int a_keycode)

	; Special case for unmap
	if (a_keycode == -1)
		_groupHotkeys[a_groupIndex] = -1
		UnregisterForKey(oldKeycode)
		return true
	endIf

	; Old group index this keycode was bound to
	int oldIndex = _groupHotkeys.Find(a_keycode)
	; Old keycode at the target position
	int oldKeycode = _groupHotkeys[a_groupIndex]

	; Already assigned, no need to do anything
	if (oldIndex == a_groupIndex)
		return false
	endIf

	; Swap
	if (oldIndex != -1 && oldKeycode != -1)
		_groupHotkeys[oldIndex] = oldKeycode
	else
		; Unset previous group this key was assigned to
		if (oldIndex != -1)
			_groupHotkeys[oldIndex] = -1
		endIf

		; If we replaced a key, unregister it
		if (oldKeycode != -1)
			UnregisterForKey(oldKeycode)
		endIf

		RegisterForKey(a_keycode)
	endIf

	_groupHotkeys[a_groupIndex] = a_keycode

	return true
endFunction

; Send the group data to the UI, so that when the user selects a group, it can filter its entries.
function InitControls()
	int[] args = new int[6]
	args[0] = ButtonHelpEnabled as int
	args[1] = _groupAddKey
	args[2] = _groupUseKey
	args[3] = _setIconKey
	args[4] = _saveEquipStateKey
	args[5] = _toggleFocusKey

	UI.InvokeIntA(FAVORITES_MENU, MENU_ROOT + ".initControls", args)
endFunction

; Send the group data to the UI, so that when the user selects a group, it can filter its entries.
function InitMenuGroupData()
	; Don't send group data if vampire lord
	if (_vampireLordRace == PlayerRef.GetRace())
		return
	endIf

	; groupCount, mainHandFormId[8], offHandFormId[8], iconFormId[8]
	int[] args = new int[25]
	args[0] = 8

	int c = 1

	int i = 0
	while (i<8)
		args[c] = _groupMainHandItemIds[i]
		i += 1
		c += 1
	endWhile

	i = 0
	while (i<8)
		args[c] = _groupOffHandItemIds[i]
		i += 1
		c += 1
	endWhile
	
	i = 0
	while (i<8)
		args[c] = _groupIconItemIds[i]
		i += 1
		c += 1
	endWhile

	UI.InvokeIntA(FAVORITES_MENU, MENU_ROOT + ".pushGroupItems", _itemIds1)
	UI.InvokeIntA(FAVORITES_MENU, MENU_ROOT + ".pushGroupItems", _itemIds2)
	UI.InvokeIntA(FAVORITES_MENU, MENU_ROOT + ".finishGroupData", args)
endFunction

function UpdateMenuGroupData(int a_groupIndex)
	int offset = 32 * a_groupIndex

	int[] itemIds

	if (offset >= 128)
		offset -= 128
		itemIds = _itemIds2
	else
		itemIds = _itemIds1
	endIf

	; groupIndex, mainHandItemId, offHandItemID, iconItemId, itemIds[32]
	int[] args = new int[36]

	args[0] = a_groupIndex
	args[1] = _groupMainHandItemIds[a_groupIndex]
	args[2] = _groupOffHandItemIds[a_groupIndex]
	args[3] = _groupIconItemIds[a_groupIndex]

	int i = 4
	int j = offset

	while (i<36)
		args[i] = itemIds[j]

		i += 1
		j += 1
	endWhile
	
	; This also unlocks the menu, so no need to call unlock
	UI.InvokeIntA(FAVORITES_MENU, MENU_ROOT + ".updateGroupData", args)
endFunction

; Ensure that our data is still valid. Might not be the case if a mod was uninstalled
function CleanUp()
	
	; Note on thread safety:
	; Since we don't manage an explicit group count, items can just be set or unset from multiple threads

	int i = 0
	while (i < _items1.length)

		if (_items1[i] == none || _items1[i].GetFormID() == 0)
			_items1[i] = none
			_itemIds1[i] = 0
		endIf

		i += 1
	endWhile

	i = 0
	while (i < _items2.length)

		if (_items2[i] == none || _items2[i].GetFormID() == 0)
			_items2[i] = none
			_itemIds2[i] = 0
		endIf

		i += 1
	endWhile
endFunction

bool function GroupAdd(int a_groupIndex, int a_itemId, Form a_item)
	int offset = 32 * a_groupIndex

	; Select the target set of arrays, adjust offset
	Form[] items
	int[] itemIds
	bool[] itemInvalidFlags

	if (offset >= 128)
		offset -= 128
		items = _items2
		itemIds = _itemIds2
		itemInvalidFlags = _itemInvalidFlags2
	else
		items = _items1
		itemIds = _itemIds1
		itemInvalidFlags = _itemInvalidFlags1
	endIf

	; Prevent the same itemId being added to a group twice
	if (IsItemIdInGroup(a_groupIndex,a_itemId))
		return true
	endIf
	
	; Pick next free slot
	int index = FindFreeIndex(itemIds, itemInvalidFlags, offset)
	
	; No more space in group?
	if (index == -1)
		return false
	endIf

	; Store received data
	items[index] = a_item
	itemIds[index] = a_itemId
	itemInvalidFlags[index] = false

	; If there's no icon item set yet, use this one
	if (_groupIconItems[a_groupIndex] == none)
		_groupIconItems[a_groupIndex] = a_item
		_groupIconItemIds[a_groupIndex] = a_itemId
	endIf

	return true
endFunction

bool function GroupRemove(int a_groupIndex, int a_itemId)
	int offset = 32 * a_groupIndex

	; Select the target set of arrays, adjust offset
	Form[] items
	int[] itemIds
	bool[] itemInvalidFlags

	if (offset >= 128)
		offset -= 128
		items = _items2
		itemIds = _itemIds2
		itemInvalidFlags = _itemInvalidFlags2
	else
		items = _items1
		itemIds = _itemIds1
		itemInvalidFlags = _itemInvalidFlags1
	endIf

	int i = offset
	int n = offset+32
	while (i < n)
		if (itemIds[i] == a_itemId)
			items[i] = none
			itemIds[i] = 0
			itemInvalidFlags[i] = false
			i = n
		else
			i += 1
		endIf
	endWhile

	if (a_itemId == _groupMainHandItemIds[a_groupIndex])
		_groupMainHandItems[a_groupIndex] = none
		_groupMainHandItemIds[a_groupIndex] = 0
	endIf

	if (a_itemId == _groupOffHandItemIds[a_groupIndex])
		_groupOffHandItems[a_groupIndex] = none
		_groupOffHandItemIds[a_groupIndex] = 0
	endIf

	if (a_itemId == _groupIconItemIds[a_groupIndex])
		ReplaceGroupIcon(a_groupIndex)
	endIf

	return true
endFunction

function GroupUse(int a_groupIndex)
	int offset = 32 * a_groupIndex

	; Select the target set of arrays, adjust offset
	Form[] items
	int[] itemIds
	bool[] itemInvalidFlags

	if (offset >= 128)
		offset -= 128
		items = _items2
		itemIds = _itemIds2
		itemInvalidFlags = _itemInvalidFlags2
	else
		items = _items1
		itemIds = _itemIds1
		itemInvalidFlags = _itemInvalidFlags1
	endIf

	; Reset state
	_usedRightHand		= false
	_usedLeftHand		= false
	_usedVoice			= false
	_usedOutfitMask		= 0

	; These items are equipped later
	form[] deferredItems = new Form[32]
	int deferredIdx = 0

	; Encountered invalid items are removed at the end when speed is no longer an issue
	int[] invalidItemIds = new int[32]
	int invalidIdx = 0

	; Turn off UI sounds to avoid annoying clicking noise while swapping spells
	_audioCategoryUI.Mute()
	
	; Unequip hands first?
	if (GetGroupFlag(a_groupIndex,GROUP_FLAG_UNEQUIP_HANDS))
		UnequipHand(0)
		UnequipHand(1)
	endIf
	
	; Process main and offhand items

	; Left first, to avoid problems when equipping the same weapon twice
	Form offHandItem = _groupOffHandItems[a_groupIndex]
	int offHandItemId = _groupOffHandItemIds[a_groupIndex]
	if (offHandItem)
		int itemType = offHandItem.GetType()
		if (IsItemValid(offHandItem, itemType))
			ProcessItem(offHandItem, itemType, false, true, offHandItemId)
		endIf
	endIf

	Form mainHandItem = _groupMainHandItems[a_groupIndex]
	int mainHandItemId = _groupMainHandItemIds[a_groupIndex]
	if (mainHandItem)
		int itemType = mainHandItem.GetType()
		if (IsItemValid(mainHandItem, itemType))
			ProcessItem(mainHandItem, itemType, false, false, mainHandItemId)
		endIf
	endIf

	; Validate & process items
	int i = offset
	int n = offset + 32
	while (i < n)
		Form item = items[i]
		int itemId = itemIds[i]
		
		if (item && item != mainHandItem && item != offHandItem && !itemInvalidFlags[i])
			int itemType = item.GetType()

			if (! IsItemValid(item, itemType))
				invalidItemIds[invalidIdx] = itemId
				invalidIdx += 1
			elseIf (! ProcessItem(item, itemType, a_itemId = itemId))
				deferredItems[deferredIdx] = item
				deferredIdx += 1
			endIf
		endIf

		i += 1
	endWhile

	; Process deferred items
	i = 0
	while (i < deferredIdx)
		Form item = deferredItems[i]
		int itemType = item.GetType()

		ProcessItem(item, itemType, false)

		i += 1
	endWhile

	; Unequip any armor not belonging to current outfit mask
	if (GetGroupFlag(a_groupIndex,GROUP_FLAG_UNEQUIP_ARMOR))
		int h = 0x00000001
		while (h < 0x80000000)
			Form wornForm = PlayerREF.GetWornForm(h)
			if (wornForm)
				if (!LogicalAND(h, _usedOutfitMask))
					PlayerREF.UnEquipItemEX(wornForm)
				endIf
			endIf
			h = LeftShift(h,1)
		endWhile
	endIf
	
	_audioCategoryUI.UnMute() ; Turn UI sounds back on

	i = 0
	while (i<invalidIdx)
		InvalidateItem(invalidItemIds[i])
		i += 1
	endWhile
endFunction

function UnequipHand(int a_hand)
	int a_handEx = 1
	if (a_hand == 0)
		a_handEx = 2 ; unequipspell and *ItemEx need different hand args
	endIf

	Form handItem = PlayerREF.GetEquippedObject(a_hand)
	if (handItem)
		int itemType = handItem.GetType()
		if (itemType == 22)
			PlayerREF.UnequipSpell(handItem as Spell, a_hand)
		else
			PlayerREF.UnequipItemEx(handItem, a_handEx)
		endIf
	endIf
endFunction

bool function IsItemValid(Form a_item, int a_itemType)
	; Player has removed this item from Favorites, so don't use it and queue it for removal
	if (! Game.IsObjectFavorited(a_item))
		return false
	endIf

	; This is a Spell or Shout and can't be counted like an item
	if (a_itemType == 22 || a_itemType == 119)
		return PlayerREF.HasSpell(a_item)
	; This is an inventory item
	else 
		return PlayerREF.GetItemCount(a_item) > 0
	endIf
endFunction

bool function ProcessItem(Form a_item, int a_itemType, bool a_allowDeferring = true, bool a_offHandOnly = false, int a_itemId = 0)

	; WEAPON ------------
	if (a_itemType == 41)

		; Any weapon needs at least one free hand
		if (_usedRightHand && _usedLeftHand)
			return true
		endIf

		Weapon itemWeapon = a_item as Weapon
		int weaponType = itemWeapon.GetweaponType()

		; It's one-handed and the player has a free hand
		if (weaponType <= 4 || weaponType == 8) ; Fists(0), Swords(1), Daggers(2), War Axes(3), Maces(4), Staffs(8)
			if (!_usedRightHand && !a_offHandOnly)

				if (a_item == PlayerREF.GetEquippedObject(1) && a_itemId != PlayerREF.GetEquippedItemId(1))
					UnequipHand(1) ; avoid damage-related bug when swapping for enhanced item
				endIf
				PlayerREF.EquipItemById(itemWeapon, a_itemId, 1, equipSound = _silenceEquipSounds)
				_usedRightHand = true
			elseIf (!_usedLeftHand)
				if (a_item == PlayerREF.GetEquippedObject(0) && a_itemId != PlayerREF.GetEquippedItemId(0))
					UnequipHand(0)
				endIf
				PlayerREF.EquipItemById(itemWeapon, a_itemId, 2, equipSound = _silenceEquipSounds)
				_usedLeftHand = true
			endIf

		; It's two-handed and both hands are free
		elseIf (weaponType > 4 && !_usedRightHand && !_usedLeftHand)
			; EDC - Changed this line from GetEquippedItemId(0) to GetEquippedItemId(1) since two-handed weapons don't seem to appear in left hand
			;Debug.Trace(self + ": PlayerREF.GetEquippedObject(0) is " + PlayerREF.GetEquippedObject(0) + ", PlayerREF.GetEquippedObject(1) is " + PlayerREF.GetEquippedObject(1))
			;Debug.Trace(self + ": PlayerREF.GetEquippedItemId(0) is " + PlayerREF.GetEquippedItemId(0) + ", PlayerREF.GetEquippedItemId(1) is " + PlayerREF.GetEquippedItemId(1))
			if (a_item == PlayerREF.GetEquippedObject(0) && a_itemId != PlayerREF.GetEquippedItemId(1))
				UnequipHand(0)
			endIf
			PlayerREF.EquipItemById(itemWeapon, a_itemId, equipSlot = 0, equipSound = _silenceEquipSounds)

			_usedRightHand = true
			_usedLeftHand = true
		endIf

		return true

	; ARMOR ------------
	elseIf (a_itemType == 26)
		int slotMask = (a_item as Armor).GetslotMask()

		; It's a shield... 
		if (slotMask == 512)
			if (!_usedLeftHand)
				PlayerREF.EquipItemById(a_item, a_itemId, equipSlot = 0, equipSound = _silenceEquipSounds)
				_usedLeftHand = true
				_usedOutfitMask += slotMask
			endIf
		; It's not a shield, just equip it if slot is free
		elseIf (! LogicalAnd(_usedOutfitMask,slotMask))
			if (a_item == PlayerREF.GetWornForm(slotMask) && a_itemId != PlayerREF.GetWornItemId(slotMask))
				PlayerREF.UnequipItemEx(a_item)
			endIf
			
			PlayerREF.EquipItemById(a_item, a_itemId, equipSlot = 0, equipSound = _silenceEquipSounds)
			_usedOutfitMask += slotMask
		endIf

		return true

	; AMMO ------------
	elseIf (a_itemType == 42) ;kAmmo
		PlayerREF.EquipItemEX(a_item, equipSlot = 0, equipSound = _silenceEquipSounds)
		return true

	; SPELL ------------
	elseIf (a_itemType == 22) 

		Spell itemSpell = a_item as Spell
		EquipSlot spellEquipSlot = itemSpell.GetEquipType()

		if (spellEquipSlot != _voiceSlot)

			; Any non power spell needs at least one free hand
			if (_usedRightHand && _usedLeftHand)
				return true
			endIf

			; spell is eitherhanded
			if (spellEquipSlot == _eitherHandSlot)
				if (!_usedRightHand && !a_offHandOnly)
					PlayerREF.EquipSpell(itemSpell, 1)
					_usedRightHand = true
				elseIf (!_usedLeftHand)
					PlayerREF.EquipSpell(itemSpell, 0)
					_usedLeftHand = true
				endIf

			; Spell requires two hands ...
			elseIf (spellEquipSlot == _bothHandsSlot)
				if (!_usedRightHand && !_usedLeftHand)
					PlayerREF.EquipSpell(itemSpell, 1)
					_usedRightHand = true
					_usedLeftHand = true
				endIf

			; a lot of NPC spells are left-hand only, so if the player is using PSB they'll need this
			elseIf (spellEquipSlot == _leftHandSlot)
				if (!_usedLeftHand)
					PlayerREF.EquipSpell(itemSpell, 0)
					_usedLeftHand = true
				endIf
			endIf

		else
			if (!_usedVoice)
				PlayerREF.EquipSpell(itemSpell, 2)
				_usedVoice = true
			endIf
		endIf

		return true

	; SCROLL ------------
	elseIf (a_itemType == 23)
		Scroll itemScroll = a_item as Scroll
		
		; Any scroll needs at least one free hand
		if (_usedRightHand && _usedLeftHand)
			return true
		endIf
		;FIXME - GetEquipType seems to be broken for scrolls
		;If (itemScroll.GetEquipType() == _bothHandsSlot && !_usedLeftHand && !_usedRightHand)
		;	PlayerREF.EquipItemEX(itemScroll, equipSlot = 0, equipSound = _silenceEquipSounds)
		;	_usedLeftHand = true
		;	_usedRightHand = true
		if (!_usedRightHand && !a_offHandOnly)
			PlayerREF.EquipItemEX(itemScroll, equipSlot = 1, equipSound = _silenceEquipSounds)
			_usedRightHand = true			
		elseIf (!_usedLeftHand)
			PlayerREF.EquipItemEX(itemScroll, equipSlot = 2, equipSound = _silenceEquipSounds)
			_usedLeftHand = true
		endIf
		
		return true
		
	; SHOUT ------------
	elseIf (a_itemType == 119)
		if (!_usedVoice)
			PlayerREF.EquipShout(a_item as Shout)
			_usedVoice = true
		endIf

		return true

	; POTION ------------
	elseIf (a_itemType == 46)
		if ((a_item as Potion).IsHostile()) ; This is a poison and should only be applied after new weapons have been equipped.
			if (a_allowDeferring)
				return false
			endIf

			; This will fail if a poisonable weapon is only equipped in the offhand. That's a Skyrim bug, not my bug.
			PlayerREF.EquipItem(a_item, abSilent = True)

			return true
		endiF

		; This is a non-hostile potion, food, or... something? and can be used immediately
		PlayerREF.EquipItem(a_item as Potion, abSilent = True)

		return true

	; INGREDIENT ------------
	elseIf (a_itemType == 30) ;kIngredient
		PlayerREF.EquipItem(a_item as Ingredient, abSilent = True)
		return true

	; LIGHT (TORCH) ------------
	elseIf (a_itemType == 31)
		if (!_usedLeftHand)
			PlayerREF.EquipItemEX(a_item, equipSlot = 0, equipSound = _silenceEquipSounds)
			_usedLeftHand = true
		endIf

		return true

	; MISC ------------
	elseIf a_itemType == 32
		PlayerREF.EquipItem(a_item, abSilent = True)
		return true
	endIf

	return true
endFunction

function InvalidateItem(int a_itemId, bool redrawIcon = false)
	; EDC - Version 3 implementation only invalidates the first appearance of  
	; an itemID. Any subsequent use in other groups is missed. 
	; This version recursively searches for additional items beyond the first
	;int loop
	int index
	

	; GroupData [1-4]
	;loop = 0
	index = 0
	while index < 128
		index = _itemIds1.Find(a_itemId, index)
		if (index != -1)
			;Debug.Trace(self + ": Setting _itemInvalidFlags1[" + index + "] = true. Loop number " + loop)
			;loop += 1
			_itemInvalidFlags1[index] = true
			index += 1			
		else
			index = 128
		endIf
	endWhile
	
	; GroupData [5-8]
	;loop = 0
	index = 0
	while index < 128
		index = _itemIds2.Find(a_itemId, index)
		if (index != -1)
			;Debug.Trace(self + ": Setting _itemInvalidFlags2[" + index + "] = true. Loop number " + loop)
			;loop += 1
			_itemInvalidFlags2[index] = true
			index += 1
		else
			index = 128
		endIf
	endWhile
	
	; Main hand
	index = 0
	while index < 8
		index = _groupMainHandItemIds.Find(a_itemId, index)
		if (index != -1)
			_groupMainHandItems[index] = none
			_groupMainHandItemIds[index] = 0
			index += 1
		else
			index = 8
		endIf
	endWhile

	; Off hand
	index = 0
	while index < 8
		index = _groupOffHandItemIds.Find(a_itemId, index)
		if (index != -1)
			_groupOffHandItems[index] = none
			_groupOffHandItemIds[index] = 0
			index += 1
		else
			index = 8
		endIf
	endWhile

	; Icon
	index = 0
	while index < 8
		index = _groupIconItemIds.Find(a_itemId, index)
		if (index != -1)
			ReplaceGroupIcon(index)
			if (redrawIcon)
				UpdateMenuGroupData(index)
			endIf			
			index += 1
		else
			index = 8
		endIf
	endWhile
endFunction

int function FindFreeIndex(int[] a_itemIds, bool[] a_itemInvalidFlags, int offset)
	int i = a_itemIds.Find(0,offset)
	
	; First try to find an entry that is 0
	if (i >= offset && i < offset + 32)
		return i
	endIf

	; Failed. Now try to claim an entry flagged as invalid.
	i = offset
	int n = offset + 32
	while (i < n)
		if (a_itemInvalidFlags[i])
			return i
		endIf

		i += 1
	endWhile
	
	return -1
endFunction

function ReplaceGroupIcon(int a_groupIndex)

	; If player has MH or OH set for the group, use it first
	if (_groupMainHandItemIds[a_groupIndex])
		_groupIconItems[a_groupIndex] = _groupMainHandItems[a_groupIndex]
		_groupIconItemIds[a_groupIndex] = _groupMainHandItemIds[a_groupIndex]
		return
	elseIf (_groupOffHandItemIds[a_groupIndex])
		_groupIconItems[a_groupIndex] = _groupOffHandItems[a_groupIndex]
		_groupIconItemIds[a_groupIndex] = _groupOffHandItemIds[a_groupIndex]
		return
	endIf

	int offset = a_groupIndex * 32

	; Select the target set of arrays, adjust offset
	Form[] items
	int[] itemIds
	bool[] itemInvalidFlags

	if (offset >= 128)
		offset -= 128
		items = _items2
		itemIds = _itemIds2
		itemInvalidFlags = _itemInvalidFlags2
	else
		items = _items1
		itemIds = _itemIds1
		itemInvalidFlags = _itemInvalidFlags1
	endIf

	int i = offset
	int n = offset+32

	; Use icon of first found item
	while (i < n)
		if (items[i] != none && !itemInvalidFlags[i])
			_groupIconItems[a_groupIndex] = items[i]
			_groupIconItemIds[a_groupIndex] = itemIds[i]
			return
		else
			i += 1
		endIf
	endWhile

	_groupIconItems[a_groupIndex] = none
	_groupIconItemIds[a_groupIndex] = 0
endFunction

; utility function to see if form is in the specified group. 
bool function IsFormInGroup(int a_groupIndex, form a_item)
	int offset = 32 * a_groupIndex

	; Select the target set of arrays, adjust offset
	Form[] items

	if (offset >= 128)
		offset -= 128
		items = _items2
	else
		items = _items1
	endIf
	
	int i = items.Find(a_item,offset)
	if (i >= offset && i < offset+32)
		return true
	endIf
	
	return false
endFunction

; utility function to see how many of the form are in a group
int function GetNumFormsInGroup(int a_groupIndex,form a_item)
	int offset = 32 * a_groupIndex

	; Select the target set of arrays, adjust offset
	form[] items

	if (offset >= 128)
		offset -= 128
		items = _items2
	else
		items = _items1
	endIf

	int i = offset
	int n = offset + 32
	int count
	while (i < n)
		if (items[i] == a_item)
			count += 1
		endIf
		i += 1
	endWhile
	
	return count
endFunction

form function GetFormFromItemId(int a_groupIndex,int itemId)
	int offset = 32 * a_groupIndex
	; Select the target set of arrays, adjust offset
	form[] items
	int[] itemIds
	
	if (offset >= 128)
		offset -= 128
		items = _items2
		itemIds = _itemIds2
	else
		items = _items1
		itemIds = _itemIds1
	endIf

	int i = itemIds.Find(itemId,offset)
	if (i >= offset && i < offset + 32)
		return items[i]
	else
		return none
	endIf
endFunction

; return the Nth itemId
int function GetNthItemIdInGroup(int a_groupIndex,form a_item,int a_num = 1)
	int offset = 32 * a_groupIndex
	; Select the target set of arrays, adjust offset
	form[] items
	int[] itemIds
	
	if (offset >= 128)
		offset -= 128
		items = _items2
		itemIds = _itemIds2
	else
		items = _items1
		itemIds = _itemIds1
	endIf

	int i = offset
	int n = offset + 32
	int count = 0
	
	int result = offset
	while (result >= offset && result < n && count < a_num)
		result = items.Find(a_item,i)
		i = result + 1
		count += 1
	endWhile
	if (result >= offset && result < n)
		return itemIds[result]
	endIf
	return 0
endFunction

; utility function to see if itemId is in the specified group. 
bool function IsItemIdInGroup(int a_groupIndex, int a_itemId)
	int offset = 32 * a_groupIndex

	; Select the target set of arrays, adjust offset
	int[] itemIds

	if (offset >= 128)
		offset -= 128
		itemIds = _itemIds2
	else
		itemIds = _itemIds1
	endIf
	
	int i = itemIds.Find(a_itemId,offset)
	if (i >= offset && i < offset+32)
		return true
	endIf
	
	return false
endFunction

function RegisterHotkeys()
	int i = 0
	
	while (i < _groupHotkeys.Length)
		
		if (_groupHotkeys[i] != -1)
			RegisterForKey(_groupHotkeys[i])
		endIf

		i += 1
	endWhile
endFunction

function SwapControlKey(int a_newKey, int a_curKey)
	if (a_newKey == _groupAddKey)
		_groupAddKey = a_curKey
	elseIf (a_newKey == _groupUseKey)
		_groupUseKey = a_curKey
	elseIf (a_newKey == _setIconKey)
		_setIconKey = a_curKey
	elseIf (a_newKey == _saveEquipStateKey)
		_saveEquipStateKey = a_curKey
	elseIf (a_newKey == _toggleFocusKey)
		_toggleFocusKey = a_curKey
	endIf
endFunction

; DEBUG ------------------------------------------------------------------------------------------

function PrintGroupItems(int a_groupIndex)
	;This is here so I can see what's in the group, because the UI is currently broken
	Debug.Trace("PrintGroupItems called on group " + a_groupIndex)

	int offset = 32 * a_groupIndex

	; Select the target set of arrays, adjust offset
	Form[] items
	int[] itemIds

	if (offset >= 128)
		offset -= 128
		items = _items2
		itemIds = _itemIds2
	else
		items = _items1
		itemIds = _itemIds1
	endIf

	int i = offset
	int n = offset + 32
	while (i < n)
		if (items[i])
			Debug.Trace(i + " is " + itemIds[i] + ", form is " + items[i] + ": " + items[i].GetName())
		endIf
		i += 1
	endWhile
	if (_groupIconItemIds[a_groupIndex])
		Debug.Trace("Group icon is " + _groupIconItemIds[a_groupIndex] + ", form is " + _groupIconItems[a_groupIndex] + ": " + _groupIconItems[a_groupIndex].GetName())
	endIf
	if (_groupMainHandItemIds[a_groupIndex])
		Debug.Trace("Group MH is " + _groupMainHandItemIds[a_groupIndex] + ", form is " + _groupMainHandItems[a_groupIndex] + ": " + _groupMainHandItems[a_groupIndex].GetName())
	endIf
	if (_groupOffHandItemIds[a_groupIndex])
		Debug.Trace("Group OH is " + _groupOffHandItemIds[a_groupIndex] + ", form is " + _groupOffHandItems[a_groupIndex] + ": " + _groupOffHandItems[a_groupIndex].GetName())
	endIf
endFunction