Quantcast
local minItem, maxItem = 1, 99999
local CONSECUTIVE_INVALID_TO_IGNORE = 5
local VALID_DELAY = 0.025
local INVALID_DELAY = 5

local function scanItemLink(link)
	local textL, textR

	-- Populate hidden tooltip
	ItemScannerHiddenTooltip:ClearLines()
	ItemScannerHiddenTooltip:SetHyperlink(link)

	local startTime = GetTime()
	while ItemScannerHiddenTooltipTextLeft1:GetText():find(RETRIEVING_ITEM_INFO) do
		coroutine.yield()
		if GetTime() - startTime >= INVALID_DELAY then
			-- If we got anything useful, keep it
			if numLines > 1 then
				break
			end
			return false
		end
	end

	if IS_item_info[link] and #(IS_item_info[link]) >= ItemScannerHiddenTooltip:NumLines() then
		return true
	end

	IS_item_info[link] = {
		itemInfo = {GetItemInfo(link)},
		itemSpell = {GetItemSpell(link)},
		statTable = GetItemStats(link),
		itemUniqueness = {GetItemUniqueness(link)},
		consumableItem = IsConsumableItem(link),
		equippable = IsEquippableItem(link),
		helpful = IsHelpfulItem(link),
		harmful = IsHarmfulItem(link),
	}
	for i = 1, ItemScannerHiddenTooltip:NumLines() do
		IS_item_info[link][i] = {
			left = _G["ItemScannerHiddenTooltipTextLeft" .. i]:GetText(),
			right = _G["ItemScannerHiddenTooltipTextRight" .. i]:GetText(),
		}
	end

	return true
end

local frame = CreateFrame("Frame")
local function OnUpdate(self)
	local status, err
	if self.itemco then
		status, err = pcall(self.itemco)
		if status == false then
			print("ItemScanner: item scan error, aborting.")
			self.itemco = nil
			IS_status.items.scan_active = false
			error(err)
			return
		elseif err == true then
			self.itemco = nil
		end
	else
		frame:SetScript("OnUpdate", nil)
	end
end

local function itemParseCoroutine(start)
	IS_status.items.scan_active = true
	coroutine.yield()
	local startTime = GetTime()
	local roundStartTime = startTime
	local numProcessed, roundNumProcessed, valid, roundValid = 0, 0, 0, 0
	print(string.format("ItemScanner: starting scan at item %d", start))
	for i = start, maxItem do
		if not IS_status.items.invalid[i] or (type(IS_status.items.invalid[i]) == "number" and IS_status.items.invalid[i] < CONSECUTIVE_INVALID_TO_IGNORE) then
			IS_status.items.last_scanned = i
			local itemStartTime = GetTime()
			if scanItemLink("item:" .. i .. ":0:0:0:0:0:0:0:85") then
				IS_status.items.last_valid = i
				-- Reset invalid count
				IS_status.items.invalid[i] = false
				valid, roundValid = valid + 1, roundValid + 1
			else
			-- start with 3 if no previous value
				IS_status.items.invalid[i] = (IS_status.items.invalid[i] or 2) + 1
			end
			if not IS_status.items.scan_active then
				break
			end
			numProcessed = numProcessed + 1
			roundNumProcessed = roundNumProcessed + 1
			while GetTime() - itemStartTime < VALID_DELAY do
				coroutine.yield()
			end
			local currentTime = GetTime()
			if currentTime - roundStartTime >= 120 then
				local elapsedTime = currentTime - startTime
				print(string.format("ItemScanner: at item %d, %d in %.1f sec. (%.2f/min.) (%d valid), %d this round (%d valid)", i, numProcessed, elapsedTime, numProcessed / elapsedTime * 60, valid, roundNumProcessed, roundValid))
				roundStartTime = roundStartTime + 120
				roundNumProcessed, roundValid = 0, 0
			end
		end
	end

	local elapsedTime = GetTime() - startTime
	if elapsedTime == 0 then
		elapsedTime = 0.001
	end
	print(string.format("ItemScanner: scan stopped at item %d, %d in %.1f sec. (%.2f/min.) (%d valid)", IS_status.items.last_scanned, numProcessed, elapsedTime, numProcessed / elapsedTime * 60, valid))
	IS_status.items.scan_active = false

	return true
end

local function commandHandler(msg)
	if string.match(msg, "^items") then
		local start

		if msg == "items" or msg == "items start" then
			start = IS_status.items.last_valid or IS_status.items.last_scanned or minItem
		elseif msg == "items clear" then
			IS_item_info = {}
			IS_status.items.last_scanned = nil
			IS_status.items.last_valid = nil
			return
		elseif msg == "items reset" then
			commandHandler("items clear")
			commandHandler("items restart")
			return
		elseif msg == "items restart" then
			start = minItem
			IS_status.items.last_scanned = nil
			IS_status.items.last_valid = nil
		elseif msg == "items stop" then
			IS_status.items.scan_active = false
			return
		else
			print("Usage: /is items <arg> (or /itemscanner items <arg>")
			print("  clear       clears item data but does not stop a running scan")
			print("  reset       clears item data and starts over")
			print("  restart     restarts the current scan but leaves existing data intact")
			print("  start        (default) starts where you last left off")
			print("  stop        stops scanning but leaves existing data intact")
			return
		end

		frame.itemco = coroutine.wrap(itemParseCoroutine)
		frame.itemco(start)
		frame:SetScript("OnUpdate", OnUpdate)
	else
		print("Usage: /is <arg> (or /itemscanner <arg>")
		print("  items       scans all items")
	end
end

if not IS_item_info then
	IS_item_info = {}
end

if not IS_status then
	IS_status = {}
end

if not IS_status.items then
	IS_status.items = {}
end

if not IS_status.items.invalid then
	IS_status.items.invalid = {}
end

SLASH_ITEMSCANNER1="/is"
SLASH_ITEMSCANNER2="/itemscanner"
SlashCmdList["ITEMSCANNER"] = commandHandler