--[[
	DynamicData

	By sarf

	This mod allows you to access dynamic data in WoW without being forced to rely on strange Blizzard functions

	Thanks goes to the Sea people, the Cosmos team and finally the nice (but strange) people at 
	 #cosmostesters and Blizzard.
	
	CosmosUI URL:
	http://www.cosmosui.org/forums/viewtopic.php?t=NOT_YET_ANNOUNCED
	
   ]]

--[[
	Information returned by the DynamicData.item.get*Info() methods

An array is returned with the following values

	name					-- the name of the item
	strings					-- an array with strings that represent the tooltip of the item
	count					-- the number of items like this the user currently possesses
	texture
	cooldown = 				-- the cooldown of the item
		{ 
			start 			-- time when the cooldown started
			duration 		-- duration of the cooldown
			enable 			-- 1 or 0
		};
	
	-- each item may have these values:
	
	durability				-- current durability
	maxDurability			-- maximum durability
	itemType				-- type of item
	locked					-- ?

These values are 0 unless the item info derives from a ContainerInfo call (bag >= 0):

	quality					-- degree of commonness
	readable				-- ?

These values are 0 or "" unless the item info derives from a InventoryInfo call (bag == -1):

	broken					-- true if broken, false if not
	slotTexture 			-- background texture of the slot that the item resides in.


]]--


DYNAMICDATA_ITEM_SCAN_TYPE_TOOLTIP = 1;
DYNAMICDATA_ITEM_SCAN_TYPE_ITEMINFO = 2;
DYNAMICDATA_ITEM_SCAN_TYPE_COOLDOWN = 4;
DYNAMICDATA_ITEM_SCAN_TYPE_EVERYTHING = 7;
DYNAMICDATA_ITEM_SCAN_TYPE_QUICK = 6;


-- Item information - what the player is wearing and so on.
DynamicData.item = {

-- public functions	

	-- 
	-- addOnInventoryUpdateHandler (func)
	--
	--  Adds a function name that will be called on inventory updates.
	--  Function will have two parameters:
	--   bag, which may be nil.
	--   scanType, which may be DYNAMICDATA_ITEM_SCAN_TYPE_*
	--
	addOnInventoryUpdateHandler = function (func)
		return DynamicData.util.addOnWhateverHandler(DynamicData.item.OnInventoryUpdateHandlers, func);
	end;

	-- 
	-- removeOnInventoryUpdateHandler (func)
	--
	--  Removes the specified function, so that it will not be called on inventory updates.
	--
	removeOnInventoryUpdateHandler = function (func)
		return DynamicData.util.removeOnWhateverHandler(DynamicData.item.OnInventoryUpdateHandlers, func);
	end;

	-- LEGACY FUNCTION - covered in addOnInventoryUpdateHandler
	--
	-- addOnInventoryCooldownUpdateHandler (func)
	--
	--  Adds a function name that will be called on InventoryCooldown updates.
	--  Function will have one paremeter - bag, which may be nil.
	--
	addOnInventoryCooldownUpdateHandler = function (func)
		return DynamicData.util.addOnWhateverHandler(DynamicData.item.OnInventoryCooldownUpdateHandlers, func);
	end;

	-- LEGACY FUNCTION - covered in removeOnInventoryUpdateHandler
	-- 
	-- removeOnInventoryCooldownUpdateHandler (func)
	--
	--  Removes the specified function, so that it will not be called on InventoryCooldown updates.
	--
	removeOnInventoryCooldownUpdateHandler = function (func)
		return DynamicData.util.removeOnWhateverHandler(DynamicData.item.OnInventoryCooldownUpdateHandlers, func);
	end;

	-- 
	-- getEquippedSlotInfo (slot)
	--
	--  Retrieves information about the slot.
	--
	getEquippedSlotInfo = function (slot) 
		if ( ( DynamicData.item.player ) and ( DynamicData.item.player.itemsByBag )  and ( DynamicData.item.player.itemsByBag[-1] ) and ( DynamicData.item.player.itemsByBag[-1][slot] ) ) then
			return DynamicData.item.player.itemsByBag[-1][slot];
		else
			return DynamicData.item.defaultItem;
		end
	end;
	
	-- 
	-- getItemInfoByName (itemName)
	--
	--  Retrieves information about an item by name. 
	--  Adds a new variable called position with the position(s) of the item.
	--
	getItemInfoByName = function (itemName) 
		if ( ( DynamicData.item.player ) and ( DynamicData.item.player.itemsByBag ) ) then
			local element = nil;
			for bagNumber, bag in DynamicData.item.player.itemsByBag do
				for slotNumber, slot in bag do
					if ( slot.name == itemName ) then
						if ( not element ) then
							element = DynamicData.item.createBaseItem(slot);
						else
							if ( slot.count ) then
								element.count = element.count + slot.count;
							end
						end
						if ( not element.position ) then
							element.position = {};
						end
						local positionArray = {};
						positionArray.bag = bagNumber;
						positionArray.slot = slotNumber;
						table.insert(element.position, positionArray);
					end
				end
			end
			return element;
		else
			return DynamicData.item.defaultItem;
		end
	end;
	
	-- 
	-- getItemInfoByType (itemType)
	--
	--  Retrieves information about items with the specific type. Includes a new cute variable called totalCount.
	--
	getItemInfoByType = function (itemName) 
		local bagNumber, bag;
		local slotNumber, itemInfo;
		local items = {};
		for bagNumber, bag in DynamicData.item.player.itemsByBag do
			for slotNumber, itemInfo in bag do
				if ( itemInfo.itemType == itemType ) then
					table.insert(items, DynamicData.item.getItemInfoByName(itemInfo.name));
				end
			end
		end
		return items;
	end;
	
	-- 
	-- getEquippedSlotCooldown (slot)
	--
	--  Retrieves cooldown information about the slot.
	--
	getEquippedSlotCooldown = function (slot) 
		if ( ( DynamicData.item.player ) and ( DynamicData.item.player.itemsByBag )  and ( DynamicData.item.player.itemsByBag[-1] ) and ( DynamicData.item.player.itemsByBag[-1][slot] ) ) then
			local cooldown = DynamicData.item.player.itemsByBag[-1][slot].cooldown;
			return cooldown[1], cooldown[2], cooldown[3];
		else
			return 0, 0, 0;
		end
	end;
	
	-- 
	-- getInventoryInfo (bag, slot)
	--
	--  Retrieves information about the bag and slot.
	--
	getInventoryInfo = function (bag, slot)
		DynamicData.item.scanInventorySlotIfTime(bag, slot);
		local element = DynamicData.item.player.itemsByBag[bag][slot];
		if ( not element ) then
			element = DynamicData.item.defaultItem;
		end
		return element;
	end;

	-- 
	-- getInventoryName (bag, slot)
	--
	--  Retrieves the name of the item in the bag and slot.
	--
	getInventoryName = function (bag, slot) 
		if ( ( DynamicData.item.player ) and ( DynamicData.item.player.itemsByBag ) and ( DynamicData.item.player.itemsByBag[bag] ) and ( DynamicData.item.player.itemsByBag[bag][slot] ) ) then
			local itemInfo = DynamicData.item.getInventoryInfo(bag, slot);
			if ( ( not itemInfo ) or ( ( itemInfo.texture ) and ( strlen(itemInfo.texture) > 0 ) and ( ( not itemInfo.name ) or ( strlen(itemInfo.name) <= 0 ) ) ) ) then
				DynamicData.item.scanItem(bag, slot);
				itemInfo = DynamicData.item.player.itemsByBag[bag][slot];
			end
			return itemInfo.name;
		else
			return DynamicData.item.defaultItem.name;
		end
	end;

	-- 
	-- getInventoryCooldownInfo (bag, slot)
	--
	--  Retrieves cooldown information about the bag and slot.
	--
	getInventoryCooldown = function (bag, slot) 
		if ( ( DynamicData.item.player ) and ( DynamicData.item.player.itemsByBag ) and ( DynamicData.item.player.itemsByBag[bag] ) and ( DynamicData.item.player.itemsByBag[bag][slot] ) ) then
			local cooldown = DynamicData.item.player.itemsByBag[bag][slot].cooldown;
			return cooldown[1], cooldown[2], cooldown[3];
		else
			return 0, 0, 0;
		end
	end;

	-- 
	-- getEquippedSlotInfoBySlotName (slotName)
	--
	--  Retrieves information about the slot with the specified name.
	--
	getEquippedSlotInfoBySlotName = function (slotName) 
		for key, value in DynamicData.item.inventorySlotNames do
			if ( value.name == slotName ) then
				return DynamicData.item.getEquippedSlotInfo(value.id);
			end
		end
		return DynamicData.item.defaultItem;
	end;
	
	-- 
	-- updateItems ()
	--
	--  Updates the inventory of the player. 
	--
	updateItems = function (bag) 
		params = {
			func = DynamicData.item.doUpdateItems,
			params = { bag },
			allowInitialUpdate = 1,
			schedulingName = "DynamicData_item_UpdateItems",
		};
		--DynamicData.util.postpone(params);
		DynamicData.item.doUpdateItems(bag);
	end;
	
	-- 
	-- updateItemCooldowns ()
	--
	--  Updates the cooldowns of the players items.
	--
	updateItemCooldowns = function ()
		local safe = true; --DynamicData.util.safeToUseTooltips({"MerchantFrame", "TradeSkillFrame", "AuctionFrame"});
		if ( not safe ) then
			Cosmos_ScheduleByName("DynamicData_item_updateItemCooldownsUnspecified", 0.1, DynamicData.item.updateItemCooldowns);
			return;
		end
		DynamicData.item.doQuickScan();
		--[[
		local bag;
		for bag = 0,4 do
			DynamicData.item.doUpdateBagItemsCooldown(bag);
		end
		DynamicData.item.doUpdateBagItemsCooldown(-1);
		bag = nil;
		DynamicData.item.notifyUpdateHandlers(nil, DYNAMICDATA_ITEM_SCAN_TYPE_COOLDOWN);
		DynamicData.item.notifyItemCooldownUpdateHandlers();
		]]--
	end;
	
	-- 
	-- updateItemLocks ()
	--
	--  Updates the locks of the players items.
	--
	updateItemLocks = function ()
		DynamicData.item.updateItems();
	end;
	
	-- 
	-- updateItemAlerts ()
	--
	--  Updates the alerts of the players items.
	--  You can never have too many lerts.
	--
	updateItemAlerts = function ()
		DynamicData.item.updateItems();
	end;

-- protected functions


	-- 
	-- doUpdateSeperateBagItems (bag)
	--
	--  Updates a bag seperately from the rest of the inventory of the player. 
	--  Saves some time.
	--
	doUpdateSeperateBagItems = function (bag) 
		if ( not bag ) then
			return;
		end
		if ( DynamicData.item.player.itemsByBag ) then
			table.remove(DynamicData.item.player.itemsByBag, bag);
		end
		DynamicData.item.doUpdateBagItems(bag);
	end;

	-- 
	-- doUpdateBagItems (bag)
	--
	--  Updates a bag of the inventory of the player. 
	--
	doUpdateBagItems = function (bag) 
		DynamicData.item.scanItemsInBag(bag);
	end;

	-- 
	-- doUpdateItems ()
	--
	--  Initiates updates of the inventory of the player. 
	--
	doUpdateItems = function (bag) 
		local safe = true; --DynamicData.util.safeToUseTooltips({"MerchantFrame", "TradeSkillFrame", "AuctionFrame"});
		if ( not safe ) then
			if ( bag ) then
				Cosmos_ScheduleByName("DynamicData_item_doUpdateItems", 0.1, DynamicData.item.doUpdateItems, bag);
			else
				Cosmos_ScheduleByName("DynamicData_item_doUpdateItemsUnspecified", 0.1, DynamicData.item.doUpdateItems);
			end
			return;
		end
		DynamicData.item.doQuickScan();
		if ( ( bag ) and ( DynamicData.item.player ) ) then
			--DynamicData.item.doQuickScan(bag);
			--local scanLists = DynamicData.item.generateScanLists(nil, bag);
			--DynamicData.item.addScans(scanLists);
			DynamicData.item.makeScanLists(bag);
			if ( not DynamicData.item.itemScanInProgress ) then
				--DynamicData.item.doScans(nil, nil, bag);
				DynamicData.item.doScans(nil, nil, bag);
				--Cosmos_ScheduleByName("DynamicData_Item_doScans", 0.1, DynamicData.item.doScans);
			end
			--DynamicData.item.doUpdateSeperateBagItems(bag);
			--DynamicData.item.notifyUpdateHandlers(bag);
			--DynamicData.item.notifyItemCooldownUpdateHandlers();
		else
			--DynamicData.item.player = {};
			--local scanLists = DynamicData.item.generateScanLists();
			--DynamicData.item.addScans(scanLists);
			DynamicData.item.makeScanLists();
			--DynamicData.item.doQuickScan();
			if ( not DynamicData.item.itemScanInProgress ) then
				DynamicData.item.doScans(nil);
				--Cosmos_ScheduleByName("DynamicData_Item_doScans", 0.1, DynamicData.item.doScans);
			end
		end
	end;

	--
	-- addScans(scanLists)
	--
	--  Adds scan lists to the global scanlist thingy.
	--
	addScans = function (scanLists)
		for k, v in scanLists do
			for bag, slot in v do
				if ( not DynamicData.item.scanList ) then
					DynamicData.item.scanList = {};
				end
				if ( not DynamicData.item.scanList[bag] ) then
					DynamicData.item.scanList[bag] = {};
				end
				if ( not DynamicData.item.scanList[bag][slot] ) then
					DynamicData.item.scanList[bag][slot] = 0;
				end
				DynamicData.item.scanList[bag][slot] = 1;
			end
			--table.insert(DynamicData.item.scanList, v);
		end
	end;
	
	

	--
	-- doScans (scanLists, currentIndex)
	--
	--  scans the currently indicated list and schedules next scan list.
	--  when completed, generates update notification.
	--
	doScans = function (scanLists, currentIndex, bag)
		scanLists = nil;
		--[[
		if ( not scanLists ) then
			scanLists = DynamicData.item.scanList;
		end
		]]--
		local scheduleName = "DynamicData_Item_doScans";
		if ( bag ) then
			--scheduleName = scheduleName.."_"..bag;
		end
		DynamicData.item.itemScanInProgress = true;
		if ( not currentIndex ) then
			-- if first time, then we schedule a new scan - this is to prevent the stuttering effect when receiving loads of updates in short succession.
			--[[
			if ( scanLists == DynamicData.item.scanList ) then
				scanLists = nil;
			end
			]]--
			Cosmos_ScheduleByName(scheduleName, 0.1, DynamicData.item.doScans, scanLists, 1, bag);
			return;
		else
			-- prevents old scans from continuing.
			Cosmos_ScheduleByName(scheduleName, 0.1, DynamicData.item.doScans);
		end
		local scanType = DYNAMICDATA_ITEM_SCAN_TYPE_TOOLTIP;
		if ( DynamicData.item.scanItems(scanType) ) then
			if ( scanLists == DynamicData.item.scanList ) then
				scanLists = nil;
			end
			Cosmos_ScheduleByName(scheduleName, 0.1, DynamicData.item.doScans, scanLists, currentIndex, bag);
			return;
		else
			if ( scanLists == DynamicData.item.scanList ) then
				--DynamicData.item.scanList = {};
			end
			DynamicData.item.itemScanInProgress = false;
			DynamicData.item.notifyUpdateHandlers(bag, scanType);
			--DynamicData.item.notifyItemCooldownUpdateHandlers();
		end
	end;


	-- 
	-- doUpdateBagItemsCooldown (bag)
	--
	--  Updates the cooldown a bag of the inventory of the player. 
	--
	doUpdateBagItemsCooldown = function (bag) 
		if ( ( DynamicData.item.player ) and ( DynamicData.item.player.itemsByBag ) and ( DynamicData.item.player.itemsByBag[bag] ) ) then
			if ( bag > -1 ) then
				local element = nil;
				for slot = 1,GetContainerNumSlots(bag) do
					if ( DynamicData.item.player.itemsByBag[bag][slot] ) then
						local ic_start, ic_duration, ic_enable = GetContainerItemCooldown(bag, slot);
						local itemCooldown = DynamicData.item.getOldCooldown(bag, slot);
						itemCooldown[1] = ic_start;
						itemCooldown[2] = ic_duration;
						itemCooldown[3] = ic_enable;
						itemCooldown.start = ic_start;
						itemCooldown.duration = ic_duration;
						itemCooldown.enable = ic_enable;
						DynamicData.item.player.itemsByBag[bag][slot].cooldown = itemCooldown;
					end
				end
			else
				for k, v in DynamicData.item.inventorySlotNames do
					slot = v.id;
					if ( DynamicData.item.player.itemsByBag[bag][slot] ) then
						local ic_start, ic_duration, ic_enable = GetInventoryItemCooldown("player", slot);
						local itemCooldown = DynamicData.item.getOldCooldown(bag, slot);
						itemCooldown[1] = ic_start;
						itemCooldown[2] = ic_duration;
						itemCooldown[3] = ic_enable;
						itemCooldown.start = ic_start;
						itemCooldown.duration = ic_duration;
						itemCooldown.enable = ic_enable;
						DynamicData.item.player.itemsByBag[bag][slot].cooldown = itemCooldown;
					end
				end
			end
		end
	end;

-- private functions	

	-- 
	-- notifyUpdateHandlers (bag)
	--
	-- Args: 
	--  bag - if not nil, the bag that has been updated
	--  scanType - the type of scan that has completed
	-- 
	--  Notifies all update handlers that an update has occurred.
	--
	notifyUpdateHandlers = function (bag, scanType) 
		DynamicData.util.notifyWhateverHandlers(DynamicData.item.OnInventoryUpdateHandlers, bag, scanType);
	end;

	-- LEGACY FUNCTION
	-- 
	-- notifyItemCooldownUpdateHandlers ()
	--
	--  Notifies all item cooldown update handlers that an item cooldown has occurred.
	--
	notifyItemCooldownUpdateHandlers = function () 
		DynamicData.util.notifyWhateverHandlers(DynamicData.item.OnInventoryCooldownUpdateHandlers);
	end;

	--
	-- OnLoad ()
	--
	--  Sets up the DynamicData.item for operation.
	--  In this case, it retrieves the IDs for the inventory slots.
	--
	OnLoad = function ()
		for key, value in DynamicData.item.inventorySlotNames do
			DynamicData.item.inventorySlotNames[key].id = GetInventorySlotInfo(value.name);
		end
		DynamicData.item.doUpdateItems();
		Cosmos_AfterInit(DynamicData.item.doUpdateItems);
		Cosmos_AfterInit(Cosmos_Schedule, 30, DynamicData.item.doUpdateItems);
		Cosmos_AfterInit(Cosmos_Schedule, 60, DynamicData.item.doUpdateItems);
		Cosmos_AfterInit(Cosmos_Schedule, 90, DynamicData.item.doUpdateItems);
	end;

-- variables

	-- Taken from ItemBuff - thanks, Telo!
	-- Used for mapping inventory (equipment) ids and slot names
	inventorySlotNames = {
		{ name = "HeadSlot" },
		{ name = "NeckSlot" },
		{ name = "ShoulderSlot" },
		{ name = "BackSlot" },
		{ name = "ChestSlot" },
		{ name = "ShirtSlot" },
		{ name = "TabardSlot" },
		{ name = "WristSlot" },
		{ name = "HandsSlot" },
		{ name = "WaistSlot" },
		{ name = "LegsSlot" },
		{ name = "FeetSlot" },
		{ name = "Finger0Slot" },
		{ name = "Finger1Slot" },
		{ name = "Trinket0Slot" },
		{ name = "Trinket1Slot" },
		{ name = "MainHandSlot" },
		{ name = "SecondaryHandSlot" },
		{ name = "RangedSlot" },
	};

	-- Contains lists of items that should be scanned.
	scanList = {};
	
	-- Contains lists of when item slots were scanned.
	scanListScanned = {};
	
	-- Whether a scan is in progress or not.
	itemScanInProgress = false;

	-- Contains item data about the player's items.
	player = {};

	-- Contains the function pointers to functions that want to be called whenever the inventory updates.
	-- Will be called AFTER the DynamicData has parsed the inventory.
	OnInventoryUpdateHandlers = {};	

	-- Contains the function pointers to functions that want to be called whenever the inventory cooldown updates.
	-- Will be called AFTER the DynamicData has parsed the inventory.
	OnInventoryCooldownUpdateHandlers = {};	

	defaultItem = {
		name = "",
		strings = {
--[[
			[1] = {},
			[2] = {},
			[3] = {},
			[4] = {},
			[5] = {},
			[6] = {},
			[7] = {},
			[8] = {},
			[9] = {},
			[10] = {},
			[11] = {},
			[12] = {},
			[13] = {},
			[14] = {},
			[15] = {}
]]--
		},
		count = 0,
		slotCount = 0,
		texture = "",
		cooldown = {
			[1] = 0,
			[2] = 0,
			[3] = 0,
			start = 0,
			duration = 0,
			enable = 0
		},
		itemType = "",
		locked = 0,
		durability = nil,
		maxDurability = nil,
		quality = 0,
		readable = 0,
		broken = 0,
		slotTexture = ""
	};

	--
	-- createBaseItem ()
	-- 
	--  creates a basic item with all attributes set to defaults.
	--
	createBaseItem = function (baseItem)
		local newItem = {};
		for k, v in DynamicData.item.defaultItem do
			newItem[k] = v;
		end
		if ( baseItem ) then
			for k, v in baseItem do
				newItem[k] = v;
			end
		end
		return newItem;
	end;


	--
	-- makeScanList (numberOfScansPerList, bag)
	--
	--  Makes a list of scans.
	--
	makeScanLists = function (bag)
		if ( not DynamicData.item.scanList ) then
			DynamicData.item.scanList = {};
		end
		if ( not bag ) then
			for bag = 0, 4, 1 do
				for slot = 1, GetContainerNumSlots(bag), 1 do
					if ( not DynamicData.item.scanList[bag] ) then
						DynamicData.item.scanList[bag] = {};
					end
					DynamicData.item.scanList[bag][slot] = 1;
				end
			end
			bag = -1;
			for slot = 1, 19, 1 do
				if ( not DynamicData.item.scanList[bag] ) then
					DynamicData.item.scanList[bag] = {};
				end
				DynamicData.item.scanList[bag][slot] = 1;
			end
		elseif ( bag <= -1 ) then
			bag = -1;
			if ( not DynamicData.item.scanList[bag] ) then
				DynamicData.item.scanList[bag] = {};
			end
			for slot = 1, 19, 1 do
				DynamicData.item.scanList[bag][slot] = 1;
			end
		else
			if ( not DynamicData.item.scanList[bag] ) then
				DynamicData.item.scanList[bag] = {};
			end
			for slot = 1, GetContainerNumSlots(bag), 1 do
				DynamicData.item.scanList[bag][slot] = 1;
			end
		end
	end;

	--
	-- generateScanLists (numberOfScansPerList, bag)
	--
	--  Generates a list of scans.
	--
	generateScanLists = function (numberOfScansPerList, bag)
		if ( not numberOfScansPerList ) then
			numberOfScansPerList = DYNAMICDATA_DEFAULT_NUMBER_OF_TOOLTIP_SCANS_PER_UPDATE;
		end
		local lists = {};
		local numberOfCurrentScans = 0;
		local currentList = {};
		local slot;
		if ( not bag ) then
			for bag = 0, 4, 1 do
				for slot = 1, GetContainerNumSlots(bag), 1 do
					if ( not currentList[bag] ) then
						currentList[bag] = {};
					end
					table.insert(currentList[bag], slot);
					numberOfCurrentScans = numberOfCurrentScans + 1;
					if ( numberOfCurrentScans >= numberOfScansPerList) then
						table.insert(lists, currentList);
						currentList = {};
						numberOfCurrentScans = 0;
					end
				end
			end
			bag = -1;
			for slot = 1, 19, 1 do
				if ( not currentList[bag] ) then
					currentList[bag] = {};
				end
				table.insert(currentList[bag], slot);
				numberOfCurrentScans = numberOfCurrentScans + 1;
				if ( numberOfCurrentScans >= numberOfScansPerList) then
					table.insert(lists, currentList);
					currentList = {};
					numberOfCurrentScans = 0;
				end
			end
		elseif ( bag <= -1 ) then
			bag = -1;
			for slot = 1, 19, 1 do
				if ( not currentList[bag] ) then
					currentList[bag] = {};
				end
				table.insert(currentList[bag], slot);
				numberOfCurrentScans = numberOfCurrentScans + 1;
				if ( numberOfCurrentScans >= numberOfScansPerList) then
					table.insert(lists, currentList);
					currentList = {};
					numberOfCurrentScans = 0;
				end
			end
		else
			for slot = 1, GetContainerNumSlots(bag), 1 do
				if ( not currentList[bag] ) then
					currentList[bag] = {};
				end
				table.insert(currentList[bag], slot);
				numberOfCurrentScans = numberOfCurrentScans + 1;
				if ( numberOfCurrentScans >= numberOfScansPerList) then
					table.insert(lists, currentList);
					currentList = {};
					numberOfCurrentScans = 0;
				end
			end
		end
		if ( numberOfCurrentScans > 0) then
			table.insert(lists, currentList);
		end
		return lists;
	end;
	
	--
	-- doQuickScan (bag)
	--
	--  does a quick scan of the bag (or of the whole inventory if bag is not specified).
	--
	doQuickScan = function(bag)
		if ( bag ) then
			DynamicData.item.scanItemsInBag(bag, DYNAMICDATA_ITEM_SCAN_TYPE_QUICK);
		else
			for i = 0, 4, 1 do
				DynamicData.item.scanItemsInBag(i, DYNAMICDATA_ITEM_SCAN_TYPE_QUICK);
			end
			DynamicData.item.scanItemsInBag(-1, DYNAMICDATA_ITEM_SCAN_TYPE_QUICK);
		end
		DynamicData.item.notifyUpdateHandlers(bag, DYNAMICDATA_ITEM_SCAN_TYPE_QUICK);
		DynamicData.item.notifyItemCooldownUpdateHandlers();
	end;
	
	--
	-- scanItemsInBag (bag, scanType)
	--
	--  scans all items in bag and puts the info into the database
	--  can be retailored to only scan the tooltip, everything but the tooltip and everything.
	--
	scanItemsInBag = function(bag, scanType)
		local bag, slot, v;
		if ( not bag ) then
			return;
		end
		if ( bag <= -1 ) then
			for slot = 1, 19, 1 do
				DynamicData.item.scanItem(-1, slot, scanType);
			end
		else
			for slot = 1, GetContainerNumSlots(bag), 1 do
				DynamicData.item.scanItem(bag, slot, scanType);
			end
		end
	end;
	
	--
	-- scanItems (list, scanType)
	--
	--  scans a list of item slots item and puts the info into the database
	--  can be retailored to only scan the tooltip, everything but the tooltip and everything.
	--  list format : list[bag] = { slot1, slot2, slot3 ... };
	--
	scanItems = function(scanType)
		local curTime = GetTime();
		local numberScanned = 0;
		local scanned = false;
		for bag, bagValue in DynamicData.item.scanList do
			for slot, value in bagValue do
				if ( value == 1 ) then
					DynamicData.item.scanItem(bag, slot, scanType);
					DynamicData.item.scanList[bag][slot] = 0;
					if ( not DynamicData.item.scanListScanned ) then
						DynamicData.item.scanListScanned = {};
					end
					if ( not DynamicData.item.scanListScanned[bag] ) then
						DynamicData.item.scanListScanned[bag] = {};
					end
					DynamicData.item.scanListScanned[bag][slot] = curTime;
					numberScanned = numberScanned + 1;
					scanned = true;
					if ( numberScanned >= DYNAMICDATA_DEFAULT_NUMBER_OF_TOOLTIP_SCANS_PER_UPDATE ) then
						return scanned;
					end
				end
			end
		end
		return scanned;
		--[[
		for bag, v in list do
			for key, slot in v do
				DynamicData.item.scanItem(bag, slot, scanType);
			end
		end
		]]--
	end;
	
	--
	-- scanItem (bag, slot)
	--
	--  scans an item and puts the info into the item database
	--
	scanItem = function(bag, slot, scanType)
		if ( not scanType ) then
			scanType = DYNAMICDATA_ITEM_SCAN_TYPE_EVERYTHING;
		end
		local strings = nil;
		local itemName = nil;
		local element = nil;
		if ( not DynamicData.item.scanListScanned ) then
			DynamicData.item.scanListScanned = {};
		end
		if ( not DynamicData.item.scanListScanned[bag] ) then
			DynamicData.item.scanListScanned[bag] = {};
		end
		if ( not DynamicData.item.scanListScanned[bag][slot] ) then
			DynamicData.item.scanListScanned[bag][slot] = GetTime();
		end
		if ( bag > -1 ) then
			if ( not DynamicData.item.player ) then
				DynamicData.item.player = {};
			end
			if ( not DynamicData.item.player.itemsByBag ) then
				DynamicData.item.player.itemsByBag = {};
			end
			if ( not DynamicData.item.player.itemsByBag[bag] ) then
				DynamicData.item.player.itemsByBag[bag] = {};
			end
			if ( GetContainerNumSlots(bag) < slot ) then
				return nil;
			end
			if ( not DynamicData.item.player.itemsByBag[bag][slot] ) then
				DynamicData.item.player.itemsByBag[bag][slot] = DynamicData.item.createBaseItem();
			end
			element = DynamicData.item.player.itemsByBag[bag][slot];
			if ( not element.previousElement ) then
				element.previousElement = {};
			end
			if ( ( scanType and DYNAMICDATA_ITEM_SCAN_TYPE_COOLDOWN ) > 0 ) then
				local ic_start, ic_duration, ic_enable = GetContainerItemCooldown(bag, slot);
				local itemCooldown = DynamicData.item.getOldCooldown(bag, slot);
				itemCooldown[1] = ic_start;
				itemCooldown[2] = ic_duration;
				itemCooldown[3] = ic_enable;
				itemCooldown.start = ic_start;
				itemCooldown.duration = ic_duration;
				itemCooldown.enable = ic_enable;
				element.cooldown = itemCooldown;
			end
			if ( ( scanType and DYNAMICDATA_ITEM_SCAN_TYPE_ITEMINFO ) > 0 ) then
				local textureName, itemCount, itemLocked, itemQuality, itemReadable = GetContainerItemInfo(bag, slot);
				local name = DynamicData.util.getItemNameFromLink(GetContainerItemLink(bag, slot));
				if ( name ) then
					element.name = name;
				end
				element.count = itemCount;
				element.texture = textureName;
				element.locked = itemLocked;
				element.quality = itemQuality;
				element.readable = itemReadable;
			end
			if ( ( scanType and DYNAMICDATA_ITEM_SCAN_TYPE_TOOLTIP ) > 0 ) then
				if ( scanType == DYNAMICDATA_ITEM_SCAN_TYPE_TOOLTIP ) or ( not element.previousElement ) or ( element.previousElement.name ~= element.name ) or ( element.previousElement.cooldown.start ~= 0 ) or ( element.cooldown.start ~= 0 ) then
					strings = DynamicData.util.getItemTooltipInfo(bag, slot);
					if ( strings[1] ) and ( strings[1].left ) then
						itemName = strings[1].left;
					else
						itemName = "";
					end
					element.name = itemName;
					element.strings = strings;
					element.itemType = DynamicData.util.getItemStringType(strings);
	
					local itemCurrentDurability, itemMaxDurability = DynamicData.util.getCurrentAndMaxDurability(strings);
					element.durability = itemCurrentDurability;
					element.maxDurability = itemMaxDurability;
					if ( not element.previousElement ) then
						element.previousElement = {};
					end
					for k, v in element do
						if ( type(v) == "table" ) then
							if ( not element.previousElement[k] ) then
								element.previousElement[k] = {};
							end
							for key, value in v do
								if ( type(value) == "table" ) then
									if ( not element.previousElement[k][key] ) then
										element.previousElement[k][key] = {};
									end
									for keyKey, valueValue in value do
										element.previousElement[k][key][keyKey] = valueValue;
									end
								else
									element.previousElement[k][key] = value;
								end
							end
						else
							element.previousElement[k] = v;
						end
					end
				end
			end
		else
			bag = -1;
			if ( not DynamicData.item.player ) then
				DynamicData.item.player = {};
			end
			if ( not DynamicData.item.player.itemsByBag ) then
				DynamicData.item.player.itemsByBag = {};
			end
			if ( not DynamicData.item.player.itemsByBag[bag] ) then
				DynamicData.item.player.itemsByBag[bag] = {};
			end
			if ( not DynamicData.item.player.itemsByBag[bag][slot] ) then
				DynamicData.item.player.itemsByBag[bag][slot] = DynamicData.item.createBaseItem();
			end
			element = DynamicData.item.player.itemsByBag[bag][slot];

			if ( ( scanType and DYNAMICDATA_ITEM_SCAN_TYPE_COOLDOWN ) > 0 ) then
				local ic_start, ic_duration, ic_enable = GetInventoryItemCooldown("player", slot);
				local itemCooldown = DynamicData.item.getOldCooldown(bag, slot);
				itemCooldown[1] = ic_start;
				itemCooldown[2] = ic_duration;
				itemCooldown[3] = ic_enable;
				itemCooldown.start = ic_start;
				itemCooldown.duration = ic_duration;
				itemCooldown.enable = ic_enable;
				element.cooldown = itemCooldown;
			end
			if ( ( scanType and DYNAMICDATA_ITEM_SCAN_TYPE_ITEMINFO ) > 0 ) then
				local itemCount = GetInventoryItemCount("player", slot);
				local textureName = GetInventoryItemTexture("player", slot);
				element.count = itemCount;
				element.slotTexture = slotTextureName;
				element.texture = textureName;
				element.locked = IsInventoryItemLocked(slot);
				element.broken = GetInventoryItemBroken("player", slot);
				--element.quality = GetInventoryItemQuality("player", slot);
			end
			if ( ( scanType and DYNAMICDATA_ITEM_SCAN_TYPE_TOOLTIP ) > 0 ) then
				strings = DynamicData.util.getItemTooltipInfo(-1, slot);
				element.strings = strings;
				if ( strings[1] ) and ( strings[1].left ) then
					itemName = strings[1].left;
				else
					itemName = "";
				end
				element.name = itemName;
				element.itemType = DynamicData.util.getItemStringType(strings);
				
				local itemCurrentDurability, itemMaxDurability = DynamicData.util.getCurrentAndMaxDurability(strings);
				element.durability = itemCurrentDurability;
				element.maxDurability = itemMaxDurability;
			end
		end
		if ( not element ) then
		elseif ( not DynamicData.item.player.itemsByBag[bag][slot] ) then
			DynamicData.item.player.itemsByBag[bag][slot] = element;
		else
			for k, v in element do
				DynamicData.item.player.itemsByBag[bag][slot][k] = v;
			end
		end
		return element;
	end;
	
	getOldCooldown = function(bag, slot)
		if ( DynamicData.item.player.itemsByBag ) 
			and ( DynamicData.item.player.itemsByBag[bag] ) 
			and ( DynamicData.item.player.itemsByBag[bag][slot] ) 
			and ( DynamicData.item.player.itemsByBag[bag][slot].cooldown ) then
			itemCooldown = DynamicData.item.player.itemsByBag[bag][slot].cooldown;
		else
			itemCooldown = { };
		end
		return itemCooldown;
	end;
	
	--
	-- getInventorySlotScanned(bag, slot) 
	--
	--  retrieves when the slot was last scanned or nil if never
	--
	getInventorySlotScanned = function (bag, slot) 
		if ( not DynamicData.item.scanListScanned ) then
			DynamicData.item.scanListScanned = {};
		end
		if ( not DynamicData.item.scanListScanned[bag] ) then
			DynamicData.item.scanListScanned[bag] = {};
		end
		return DynamicData.item.scanListScanned[bag][slot];
	end;
	
	--
	-- scanInventorySlotIfTime(bag, slot) 
	--
	--  scans the slot if necessary.
	--
	scanInventorySlotIfTime = function(bag, slot)
		local curTime = GetTime();
		local lastScanned = DynamicData.item.getInventorySlotScanned(bag, slot);
		if ( not lastScanned ) or ( ( lastScanned + 0.2 ) < GetTime() ) then
			DynamicData.item.scanItem(bag, slot, DYNAMICDATA_ITEM_SCAN_TYPE_QUICK);
		end
	end;

};

