-- ------------------------------------------------------------------------------ --
--                           TradeSkillMaster_Auctioning                          --
--           http://www.curse.com/addons/wow/tradeskillmaster_auctioning          --
--                                                                                --
--             A TradeSkillMaster Addon (http://tradeskillmaster.com)             --
--    All Rights Reserved* - Detailed license information included with addon.    --
-- ------------------------------------------------------------------------------ --

local TSM = select(2, ...)
local Scan = TSM:NewModule("Scan", "AceEvent-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_Auctioning") -- loads the localization table

Scan.auctionData = {}
Scan.skipped = {}


local function CallbackHandler(event, ...)
	if event == "QUERY_COMPLETE" then
		local filterList = ...
		Scan.filterList = filterList
		Scan.numFilters = #filterList
		Scan:ScanNextFilter()
	elseif event == "QUERY_UPDATE" then
		local current, total, skipped = ...
		TSM.Manage:UpdateStatus("query", current, total)
		for _, itemString in ipairs(skipped) do
			TSM.Manage:ProcessScannedItem(itemString)
			tinsert(Scan.skipped, itemString)
		end
	elseif event == "SCAN_PAGE_UPDATE" then
		-- Simply forward the "currently received" and "total" page counts
		-- for the current item we're scanning.
		-- NOTE: Private servers sometimes give totally insane "total page counts"
		-- on the first response, such as "page 1/142", but those server errors
		-- always correct themselves to "page 2/REAL" when the scan has reached
		-- page 2. There's nothing we can do to avoid that rare page-count issue
		-- (which happens on many popular private servers, such as Warmane).
		TSM.Manage:UpdateStatus("page", ...)
	elseif event == "SCAN_INTERRUPTED" or event == "INTERRUPTED" then
		-- We've been interrupted by the Auction House closing.
		-- NOTE: "SCAN_INTERRUPTED" is from LibAuctionScan-1.0, which isn't used
		-- by TSM anymore, and "INTERRUPTED" is from "TSM/Auction/AuctionScanning.lua",
		-- which is what this scanner uses nowadays.
		TSM.Manage:ScanComplete(true)
	elseif event == "SCAN_TIMEOUT" then
		tremove(Scan.filterList, 1)
		Scan:ScanNextFilter()
	elseif event == "SCAN_COMPLETE" then
		local data = ...
		for _, itemString in ipairs(Scan.filterList[1].items) do
			-- make sure we haven't already scanned this item (possible with common search terms)
			if not Scan.auctionData[itemString] then
				Scan:ProcessItem(itemString, data[itemString])
				TSM.Manage:ProcessScannedItem(itemString)
			end
		end
		tremove(Scan.filterList, 1)
		Scan:ScanNextFilter()
	end
end

function Scan:StartItemScan(itemList)
	wipe(Scan.auctionData)
	wipe(Scan.skipped)
	TSMAPI:GenerateQueries(itemList, CallbackHandler)
	TSM.Manage:UpdateStatus("query", 0, -1)
end

function Scan:ScanNextFilter()
	-- We must reset the page counter, otherwise the next scan will keep the
	-- page count of the previous item until we receive "SCAN_PAGE_UPDATE".
	-- NOTE: The "nil" signals that we don't know the item's page count yet.
	-- NOTE: The recipient may want to ignore "page" events that have nil values
	-- and not update their status bars based on those, since we send these empty
	-- page events before we start each new scan!
	TSM.Manage:UpdateStatus("page", nil, nil)

	-- Now update the scan counter.
	-- NOTE: Our scan progress counter below starts counting from 0 as the first item.
	if #Scan.filterList == 0 then
		TSM.Manage:UpdateStatus("scan", Scan.numFilters, Scan.numFilters)
		return TSM.Manage:ScanComplete()
	end
	TSM.Manage:UpdateStatus("scan", Scan.numFilters-#Scan.filterList, Scan.numFilters)
	TSMAPI.AuctionScan:RunQuery(Scan.filterList[1], CallbackHandler, true)
end

function Scan:ProcessItem(itemString, auctionItem)
	if not itemString or not auctionItem then return end
	auctionItem:SetRecordParams({"GetItemBuyout", "GetItemDisplayedBid", "seller", "count"})
	auctionItem:PopulateCompactRecords()
	auctionItem:SetAlts(TSM.db.factionrealm.player)
	if #auctionItem.records > 0 then
		auctionItem:SetMarketValue(TSMAPI:GetItemValue(itemString, "DBMarket"))
		Scan.auctionData[itemString] = auctionItem
	end
end

function Scan:ShouldIgnoreAuction(record, operation)
	if type(operation) ~= "table" then return end
	if record.timeLeft <= operation.ignoreLowDuration then
		-- ignoring low duration
		return true
	elseif operation.matchStackSize and record.count ~= operation.stackSize then	
		-- matching stack size
		return true
	else
		local minPrice = TSM.Util:GetItemPrices(operation, record.parent:GetItemString()).minPrice
		if operation.priceReset == "ignore" and minPrice and record:GetItemBuyout() and record:GetItemBuyout() <= minPrice then	
			-- ignoring auctions below threshold
			return true
		end
	end
end

-- This gets how many auctions are posted specifically on this tier, it does not get how many of the items they up at this tier
-- but purely the number of auctions
function Scan:GetPlayerAuctionCount(itemString, findBuyout, findBid, findQuantity, operation)
	findBuyout = floor(findBuyout)
	findBid = floor(findBid)
	
	local quantity = 0
	for _, record in ipairs(Scan.auctionData[itemString].compactRecords) do
		if not Scan:ShouldIgnoreAuction(record, operation) and record:IsPlayer() then
			if record:GetItemBuyout() == findBuyout and record:GetItemDisplayedBid() == findBid and record.count == findQuantity then
				quantity = quantity + record.numAuctions
			end
		end
	end
	
	return quantity
end

-- gets the buyout / bid of the second lowest auction for this item
function Scan:GetSecondLowest(itemString, lowestBuyout, operation)
	local auctionItem = Scan.auctionData[itemString]
	if not auctionItem then return end
	
	local buyout, bid
	for _, record in ipairs(auctionItem.compactRecords) do
		if not Scan:ShouldIgnoreAuction(record, operation) then
			local recordBuyout = record:GetItemBuyout()
			if recordBuyout and (not buyout or recordBuyout < buyout) and recordBuyout > lowestBuyout then
				buyout, bid = recordBuyout, record:GetItemDisplayedBid()
			end
		end
	end
	
	return buyout, bid
end

-- Find out the lowest price for this item
function Scan:GetLowestAuction(auctionItem, operation)
	if type(auctionItem) == "string" or type(auctionItem) == "number" then -- it's an itemString
		auctionItem = Scan.auctionData[auctionItem]
	end
	if not auctionItem then return end
	
	-- Find lowest
	local buyout, bid, owner, invalidSellerEntry
	for _, record in ipairs(auctionItem.compactRecords) do
		if not Scan:ShouldIgnoreAuction(record, operation) then
			local recordBuyout = record:GetItemBuyout()
			if recordBuyout then
				local recordBid = record:GetItemDisplayedBid()
				if not buyout or recordBuyout < buyout or (recordBuyout == buyout and recordBid < bid) then
					buyout, bid, owner = recordBuyout, recordBid, record.seller
				end
			end
		end
	end
	if owner == "?" and next(TSM.db.factionrealm.whitelist) then
		invalidSellerEntry = true
	end

	-- Now that we know the lowest, find out if this price "level" is a friendly person
	-- the reason we do it like this, is so if Apple posts an item at 50g, Orange posts one at 50g
	-- but you only have Apple on your white list, it'll undercut it because Orange posted it as well
	local isWhitelist, isPlayer = true, true
	for _, record in ipairs(auctionItem.compactRecords) do
		if not Scan:ShouldIgnoreAuction(record, operation) then
			local recordBuyout = record:GetItemBuyout()
			if not record:IsPlayer() and recordBuyout and recordBuyout == buyout then
				isPlayer = nil
				if not TSM.db.factionrealm.whitelist[strlower(record.seller)] then
					isWhitelist = nil
				end
				
				-- If the lowest we found was from the player, but someone else is matching it (and they aren't on our white list)
				-- then we swap the owner to that person
				buyout, bid, owner = recordBuyout, record:GetItemDisplayedBid(), record.seller
			end
		end
	end
	if owner == "?" and next(TSM.db.factionrealm.whitelist) then
		invalidSellerEntry = true
	end

	return buyout, bid, owner, isWhitelist, isPlayer, invalidSellerEntry
end

function Scan:GetPlayerLowestBuyout(auctionItem, operation)
	if not auctionItem then return end
	
	-- Find lowest
	local buyout
	for _, record in ipairs(auctionItem.compactRecords) do
		if not Scan:ShouldIgnoreAuction(record, operation) then
			local recordBuyout = record:GetItemBuyout()
			if record:IsPlayer() and recordBuyout and (not buyout or recordBuyout < buyout) then
				buyout = recordBuyout
			end
		end
	end

	return buyout
end