--[[
	Informant - An addon for World of Warcraft that shows pertinent information about
	an item in a tooltip when you hover over the item in the game.
	Version: <%version%> (<%codename%>)
	Revision: $Id$
	URL: http://auctioneeraddon.com/dl/Informant/

	License:
		This program is free software; you can redistribute it and/or
		modify it under the terms of the GNU General Public License
		as published by the Free Software Foundation; either version 2
		of the License, or (at your option) any later version.

		This program is distributed in the hope that it will be useful,
		but WITHOUT ANY WARRANTY; without even the implied warranty of
		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
		GNU General Public License for more details.

		You should have received a copy of the GNU General Public License
		along with this program(see GPL.txt); if not, write to the Free Software
		Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

	Note:
		This AddOn's source code is specifically designed to work with
		World of Warcraft's interpreted AddOn system.
		You have an implicit license to use this AddOn with these facilities
		since that is its designated purpose as per:
		http://www.fsf.org/licensing/licenses/gpl-faq.html#InterpreterIncompat
]]

local _G = _G
local Informant = _G.Informant
if not Informant then return end

Informant_RegisterRevision("$URL$","$Rev$")

local VERSION = "<%version%>"
if VERSION:byte(1) == 60 then -- '<' character
	VERSION = "6.1.DEV"
end

local type,pairs,ipairs,select = type,pairs,ipairs,select
local tonumber,tostring,strmatch = tonumber,tostring,strmatch
local strsplit,strsub,format = strsplit,strsub,format
local tinsert,wipe = tinsert,wipe
local GetItemInfo = GetItemInfo
-- GLOBALS: LibStub
-- GLOBALS: InformantLocalUpdates, InformantConfig
-- GLOBALS: InformantFrame, InformantFrameScrollBar
-- GLOBALS: UIParent, MerchantFrame
-- GLOBALS: UnitGUID, UnitFactionGroup, UnitName
-- GLOBALS: InRepairMode, GetMerchantItemLink, GetMerchantItemInfo, GetMerchantNumItems

-- Locals to handle C_Container namespace in newer build of clients; expect these to be merged to all clients eventually ### hybrid
local GetContainerNumSlots = C_Container and C_Container.GetContainerNumSlots or GetContainerNumSlots
local GetContainerItemLink = C_Container and C_Container.GetContainerItemLink or GetContainerItemLink
-- Special handling for C_Container.GetContainerItemInfo, which now returns a table instead of multiple return values
-- We only return up to the 6th return value; all returns might not be used
local GetContainerItemInfoSpecial = GetContainerItemInfo
if C_Container and C_Container.GetContainerItemInfo then
	GetContainerItemInfoSpecial = function(bag, slot)
		local info = C_Container.GetContainerItemInfo(bag, slot)
		if info then
			return info.iconFileID, info.stackCount, info.isLocked, info.quality, info.isReadable, info.hasLoot
		end
	end
end

-- LOCAL FUNCTION PROTOTYPES:
local addLine				-- addLine(text, color)
local clear					-- clear()
local debugPrint            -- debugPrint(message, title, errorCode, level)
local frameActive			-- frameActive(isActive)
local frameLoaded			-- frameLoaded()
local getCatName			-- getCatName(catID)
local getItem				-- getItem(itemLink)
local getLocale
local getRowCount			-- getRowCount()
local infDebugPrint         -- debugPrint(message, category, title, errorCode, level)
local onEvent				-- onEvent(event)
local onLoad				-- onLoad()
local onVariablesLoaded		-- onVariablesLoaded()
local onInitialize
local onQuit				-- onQuit()
local scrollUpdate			-- scrollUpdate(offset)
local setDatabase			-- setDatabase(database)
local setRequirements		-- setRequirements(requirements)
local setSkills				-- setSkills(skills)
local setVendors			-- setVendors(vendors)
local setVendorLocation			-- setVendorLocation(vendors)
local setQuestStarts
local setQuestRequires
local setQuestRewards
local setQuestNames
local setZones				--
local setCrafted			-- setCrafted(crafted)
local setCraftCount			-- setCraftCount(craft_counts)
local showHideInfo			-- showHideInfo()
local skillToName			-- skillToName(userSkill)
local split					-- split(str, at)
local Dump
local debugPrintQuick
local idFromLink
local OnTooltipAddMoney


-- LOCAL VARIABLES

local self = {}
local lines = {}
local addonName = "Informant"
local _TRANS = Informant.Locale.Translate
local tooltip = LibStub("nTipHelper:1")

-- GLOBAL VARIABLES

InformantConfig = {} -- ### TODO: do this properly during ADDON_LOADED, move to InfSettings.lua

-- LOCAL DEFINES

CLASS_TO_CATEGORY_MAP = {
	[2] = 1, --Weapon
	[4] = 2, --Armor
	[1] = 3, --Container
	[0] = 4, --Consumable
	[16] = 5, --Glyph
	[7] = 6, --Trade Goods
	[6] = 7, --Projectile
	[11] = 8, --Quiver not used after patch 3.5.5
	[9] = 9, --Recipe
	[3] = 10, --Gem
	[10] = 11, --Miscellaneous(Currency)
	[15] = 11, --Miscellaneous
	[13] = 11, --Miscellaneous
	[5] = 11, --Miscellaneous
	[12] = 12, --Quest
}

local soulbindtypes, specialbindtypes

-- FUNCTION DEFINITIONS

function split(str, at)
	if (not (type(str) == "string")) then
		return
	end

	if (not str) then
		str = ""
	end

	if (not at) then
		return {str}

	else
		return {strsplit(at, str)};
	end
end

-- utility, so we don't have to maintain multiple copies of this
function idFromLink( itemLink )
	return tonumber(strmatch(itemLink, "item:(%d+)"))
end

-- clean up the link to an item string that applies to all characters and all levels
-- copied (more or less) from BeanCounterAPI.lua function lib.API.getItemString(itemLink)
function cleanStringFromLink( itemLink )
	if not itemLink or not type(itemLink) == "string" then return itemLink end
	local itemString, itemName = itemLink:match("H(item:.-)|h%[(.-)%]")
	if not itemString then return itemLink end
	-- WARNING - this must survive multiple iterations on the same link/string without changing it
	itemString = itemString:gsub("(item:%d+:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*):%d+:%d*:(.*)", "%1:100::%2")
	return itemString
end

function skillToName(userSkill)
	local skillName = self.skills[tonumber(userSkill)]
	local localized = "Unknown"
	if (skillName) then
		localized = _TRANS('INF_Tooltip_Skill'..skillName) or "Unknown: "..skillName
	end
	return localized, skillName
end

local staticDataItem={}
local emptyTable={}
local staticDataLink
local cache = setmetatable({}, {__mode="v"})

function getItem(itemLink, static)
	if (not itemLink) then return end
	if static and staticDataLink == itemLink then return staticDataItem end
	if cache[itemLink] then return cache[itemLink] end

	-- this must use the full itemLink, not just the item ID To get the data correct
	local itemName, _link, itemQuality, itemLevel, itemUseLevel, itemType, itemSubType, itemStackSize, itemEquipLoc,
		itemTexture, itemSell, itemClassID, itemSubClassID, itemBindType, itemExpacID, itemSetID, itemReagent = GetItemInfo(itemLink)

    -- get item level with upgrades
    local effLevel = GetDetailedItemLevelInfo(itemLink)
    if effLevel then
        itemLevel = effLevel
    end

	local incompleteFlag = not itemName -- flag if GetItemInfo returns nils, so we know not to cache it

	local itemID = idFromLink(itemLink)	-- not optimal, but needed for other code
	local baseData = self.database[itemID]	-- old database of items that can be safely indexed by ID - if that changes, this code will have to change as well
	local buy, class, quality, stack, additional, usedby, quantity, limited, merchantlist, soulbind, specialbind
	if (baseData) then
		buy, class, quality, stack, additional, usedby, quantity, limited, merchantlist, soulbind, specialbind = strsplit(":", baseData)
		buy = tonumber(buy)
	end
	local sell = itemSell

	-- work around a blizzard bug where honor tokens return stack size 2147483647
	-- this only seems to happen for the obsolete honor tokens shown in the currency frame
	-- See JIRA INF-41
	if (itemStackSize == 2147483647) then
		itemStackSize = nil
		stack = nil
	end

	-- if we have a local correction for this item, merge in the corrected data
	local itemUpdateData
	if (InformantLocalUpdates and InformantLocalUpdates.items) then
		local cleanString = cleanStringFromLink( itemLink )
		itemUpdateData = InformantLocalUpdates.items[ cleanString ]
		if (itemUpdateData) then
			if (itemUpdateData.buy) then
				buy = tonumber(itemUpdateData.buy)
			end
			if not sell and itemUpdateData.sell then
				sell = tonumber(itemUpdateData.sell)
			end
			if (itemUpdateData.stack) then
				stack = tonumber(itemUpdateData.stack)
			end
			if (itemUpdateData.quantity) then
				quantity = tonumber(itemUpdateData.quantity)
			end
		end
	end

	class = tonumber(class)
	quality = tonumber(quality) or itemQuality
	stack = tonumber(itemStackSize) or tonumber(stack)
	local cat = CLASS_TO_CATEGORY_MAP[class]
	soulbind = tonumber(soulbind)
	specialbind = tonumber(specialbind)

	local dataItem = (static and staticDataItem or {})
	dataItem.buy = buy
	dataItem.sell = sell
	dataItem.class = class
	dataItem.classText = itemType
	dataItem.cat = cat
	dataItem.quality = quality
	dataItem.stack = stack
	dataItem.additional = additional
	dataItem.usedby = usedby
	dataItem.quantity = quantity
	dataItem.limited = limited
	dataItem.texture = itemTexture
	dataItem.itemLevel = itemLevel
	dataItem.reqLevel = itemUseLevel
	dataItem.soulBind = soulbind
	dataItem.specialBind = specialbind
	if soulbind and soulbind > 0 then dataItem.soulBindText = soulbindtypes[soulbind] else dataItem.soulBindText = nil end
	if specialbind and specialbind > 0 then dataItem.specialBindText = specialbindtypes[specialbind] else dataItem.specialBindText = nil end

	local addition = ""
	if (additional and additional ~= "") then
		addition = " - ".._TRANS('INF_Tooltip_Addit'..additional)
	end
	local catName = getCatName(cat)
	if (not catName) then
		if (itemType) then
			dataItem.classText = itemType..addition
		else
			dataItem.classText = "Unknown"..addition
		end
	else
		dataItem.classText = catName..addition
	end

	if (usedby and usedby ~= '') then
		local usedList = split(usedby, ",")
		local skillName, localized, localeString
		local usage = ""
		dataItem.usedList = {}
		if (usedList) then
			for pos, userSkill in pairs(usedList) do
				localized = skillToName(userSkill)
				if (usage == "") then
					usage = localized
				else
					usage = usage .. ", " .. localized
				end
				tinsert(dataItem.usedList, localized)
			end
		end
		dataItem.usageText = usage
	else
		dataItem.usedList = nil
		dataItem.usageText = nil
	end

	local tradeSkillCode = 0
	local tradeSkillLevel = 0
	local tradeSkillName = ""

	local skillsRequired = self.requirements[itemID]
	if (skillsRequired) then
		tradeSkillCode, tradeSkillLevel = strsplit(":", skillsRequired)
		tradeSkillName = skillToName(tradeSkillCode)
	end

	dataItem.isPlayerMade = (tradeSkillCode ~= 0)
	dataItem.tradeSkillLevel = tradeSkillLevel
	dataItem.tradeSkillCode = tradeSkillCode
	dataItem.tradeSkillName = tradeSkillName

	if (merchantlist ~= '') then
		local merchList = split(merchantlist, ",")
		if (itemUpdateData and itemUpdateData.merchants) then
			-- check update list for additional merchants
			local moreMerch = split(itemUpdateData.merchants, ",")
			if (#moreMerch > 0 and not merchList) then merchList = {} end
			for pos, merchID in pairs(moreMerch) do
				tinsert(merchList, merchID)
			end
		end
		local vendList = {}
		local vendLoc = {}
		if (merchList) then
			for pos, merchID in pairs(merchList) do
				merchID = tonumber(merchID)
				local vendName = self.vendors[ merchID ]
				if (not vendName and InformantLocalUpdates and InformantLocalUpdates.vendor) then
					-- can't find it, try the update list
					local vendorInfo = InformantLocalUpdates.vendor[ merchID ]
					if (vendorInfo) then
						vendName = vendorInfo.name
					end
				end
				if (vendName) then
					tinsert(vendList, vendName)
					local zone,xloc,yloc
					if self.vendorLocation[merchID] then
						zone,xloc,yloc = strsplit(",",self.vendorLocation[merchID])
					end
					if zone then
						local location
						zone = tonumber(zone)
						if self.infZones[zone] then
							zone = self.infZones[zone]
						end
						location = zone
						if xloc and yloc then
							location = location.." ("..xloc..","..yloc..")"
						end
						vendLoc[vendName] = location
					else
						vendLoc[vendName] = "No set location for this vendor"
					end
				end
			end
		end
		dataItem.merchantList = merchList
		dataItem.vendors = vendList
		dataItem.vendorLoc = vendLoc
	else
		dataItem.merchantList = nil
		dataItem.vendors = nil
	end

	dataItem.startsQuest = self.questStarts[itemID]

	local questItemUse = self.questRequires[itemID]
	if (questItemUse) then
		local list
		if (type(questItemUse) == 'number') then
			list = { questItemUse }
		else
			list = { strsplit(',', questItemUse) }
		end
		for i=1, #list do
			list[i] = { strsplit('x', list[i]) }
			list[i][1] = tonumber(list[i][1])
			if not list[i][2] then list[i][2] = 1 end
		end
		dataItem.requiredFor = list
	else
		dataItem.requiredFor = (static and emptyTable or {})
	end

	questItemUse = self.questRewards[itemID]
	if (questItemUse) then
		local list
		if (type(questItemUse) == 'number') then
			list = { questItemUse }
		else
			list = { strsplit(',', questItemUse) }
		end
		for i=1, #list do
			list[i] = tonumber(list[i])
		end
		dataItem.rewardFrom = list
	else
		dataItem.rewardFrom = (static and emptyTable or {})
	end

	-- see if this is a recipe, and what it might create
	if (self.crafted) then
		local recipe_number = tonumber(itemID)
		local crafted_item = self.crafted[ recipe_number ]
		if (crafted_item) then
			dataItem.crafts = crafted_item
			-- see if it crafts more than 1 at a time
			if (self.craftCount) then
				local crafted_count = self.craftCount[ recipe_number ]
				dataItem.craftsCount = crafted_count or 1
			end
		end
	end

	if not incompleteFlag then
		-- only save cache/static if data complete
		-- TODO: consider caching partial data, with a flag to try to obtain missing data next time?
		if static then
			-- we adjusted the static table, if called with static = true
			-- so save the link info for future calls
			staticDataLink = itemLink
		else
			cache[itemLink] = dataItem
		end
	end
	return dataItem
end

local function getInformantVendorInfo(itemLink)
	local isVendored,isLimited,itemCost,toSell,buyStack,maxStack
	local itemData = getItem(itemLink)
	if itemData.merchantList then
		isVendored = true
	else
		isVendored = false
	end
	local itemLimited = itemData.limited
	if itemLimited and tonumber(itemLimited) > 0 then
		isLimited = true
	else
		isLimited = false
	end
	itemCost = tonumber(itemData.buy) or 0
	toSell = tonumber(itemData.sell) or 0
	buyStack = tonumber(itemData.quantity) or 1
	maxStack = tonumber(itemData.stack) or 1
	return isVendored,isLimited,itemCost,toSell,buyStack,maxStack
end

--Implementation of GetSellValue API proposed by Tekkub at http://www.wowwiki.com/API_GetSellValue
-- May not be accurate if an itemID is passed in
-- ### TODO: is this still valid or useful?
local origGetSellValue = GetSellValue
function GetSellValue(item)
	local itemName, itemLink, _, _, _, _, _, _, _, _, itemSellPrice = GetItemInfo(item)
	if itemSellPrice then
		return itemSellPrice
	end

	local id
	if type(item) == "number" then
		id = item
	elseif type(item) == "string" then
		id = tonumber(strmatch(item, "item:(%d+)"))
		if not id and itemLink then
			id = tonumber(strmatch(itemLink, "item:(%d+)"))
		end
	end

	-- Return out if we didn't find an id
	if not id then return end

	local itemInfo = Informant.GetItem(itemLink)
	local sellval

-- ccox - TODO - is this correct for items sold in stacks?
-- see Informant.TooltipHandler

	if (itemInfo) then
		sellval = itemInfo.sell
		if (sellval) then
			sellval = tonumber(sellval)
		end
	end

	if sellval then
		return sellval
	elseif origGetSellValue then
		-- Call our hook if present, pass the id to save processing it again
		return origGetSellValue(id)
	end
end

function setSkills(skills)
	self.skills = skills
	Informant.SetSkills = nil -- Set only once
end
function setRequirements(requirements)
	self.requirements = requirements
	Informant.SetRequirements = nil -- Set only once
end
function setVendors(vendors)
	self.vendors = vendors
	Informant.SetVendors = nil -- Set only once
end
function setDatabase(database)
	self.database = database
	Informant.SetDatabase = nil -- Set only once
end
function setQuestStarts(list)
	self.questStarts = list
	Informant.SetQuestStarts = nil -- Set only once
end
function setQuestRewards(list)
	self.questRewards = list
	Informant.SetQuestRewards = nil -- Set only once
end
function setQuestRequires(list)
	self.questRequires = list
	Informant.SetQuestRequires = nil -- Set only once
end
function setQuestNames(list)
	self.questNames = list
	Informant.SetQuestNames = nil -- Set only once
end
function setVendorLocation(list)
	self.vendorLocation = list
	Informant.SetVendorLocation = nil -- Set only once
end
function setZones(zones)
	self.infZones = zones
	Informant.zonestest = self.infZones
	Informant.SetZones = nil -- Set only once
end
function setCrafted(crafted)
	self.crafted = crafted
	Informant.SetCrafted = nil -- Set only once
end
function setCraftCount(craft_counts)
	self.craftCount = craft_counts
	Informant.SetCraftCount = nil -- Set only once
end

function getLocale()
	return Informant.Locale.GetLocale()
end

-- local categories = {GetAuctionItemClasses()} -- GetAuctionItemClasses removed in 7.0.0
-- ### TODO - review categories for different WoW versions...
local categories = {
		AUCTION_CATEGORY_WEAPONS,
		AUCTION_CATEGORY_ARMOR,
		AUCTION_CATEGORY_CONTAINERS,
		AUCTION_CATEGORY_GEMS,
		AUCTION_CATEGORY_ITEM_ENHANCEMENT,
		AUCTION_CATEGORY_CONSUMABLES,
		AUCTION_CATEGORY_GLYPHS,
		AUCTION_CATEGORY_TRADE_GOODS,
		AUCTION_CATEGORY_RECIPES,
		AUCTION_CATEGORY_BATTLE_PETS,
		AUCTION_CATEGORY_QUEST_ITEMS,
		AUCTION_CATEGORY_MISCELLANEOUS
		-- TOKEN_FILTER_LABEL
}
function getCatName(catID)
	for cat, name in ipairs(categories) do
		if (cat == catID) then
			return name
		end
	end
end

local function getQuestName(questID)
	questID = tonumber(questID) or 0
	local questName
	if (self.questNames[questID]) then
		questName = self.questNames[questID]
	else
		questName = _TRANS('INF_Interface_InfWinQuestUnknown'):format(questID)
	end
	-- format as just text, looks better
	return "|cff5599ff"..questName

-- try to format as a link, but we don't make it clickable anywhere
-- if we want a linkable quest, we'll have to redo this
--	return "|HinfQuest:"..questID.."|h|cff5599ff["..questName.."]|r|h"
end

local function showItem(itemInfo)
	if (itemInfo) then
		InformantFrameTitle:SetText(_TRANS('INF_Interface_InfWinTitle'))

		-- Woohoo! We need to provide any information we can from the item currently in itemInfo
		local quality = itemInfo.itemQuality or itemInfo.quality or 0

		local color = "ffffff"
		if (quality == 4) then color = "a335ee"
		elseif (quality == 3) then color = "0070dd"
		elseif (quality == 2) then color = "1eff00"
		elseif (quality == 0) then color = "9d9d9d"
		end

		clear()
		addLine(_TRANS('INF_Interface_InfWinInfoHeader'):format(color, itemInfo.itemName))

		local buy = itemInfo.itemBuy or itemInfo.buy or 0
		local sell = itemInfo.itemSell or itemInfo.sell or 0
		local quant = itemInfo.itemQuant or itemInfo.quantity or 0
		local count = itemInfo.itemCount or 1

		if ((buy > 0) or (sell > 0)) then
			local bgsc = tooltip:Coins(buy)
			local sgsc = tooltip:Coins(sell)

			if (count and (count > 1)) then
				local bqgsc = tooltip:Coins(buy*count)
				local sqgsc = tooltip:Coins(sell*count)
				addLine(_TRANS('INF_Tooltip_ShowVendorBuyMult'):format(count, bgsc)..": "..bqgsc, "ee8822")
				addLine(_TRANS('INF_Tooltip_ShowVendorSellMult'):format(count, sgsc)..": "..sqgsc, "ee8822")
			else
				addLine(_TRANS('INF_Tooltip_ShowVendorBuy'):format()..": "..bgsc, "ee8822")
				addLine(_TRANS('INF_Tooltip_ShowVendorSell'):format()..": "..sgsc, "ee8822")
			end
		end

		if (itemInfo.stack and itemInfo.stack > 1) then
			addLine(_TRANS('INF_Tooltip_StackSize'):format(itemInfo.stack))
		end

		local reagentInfo = ""
		if (itemInfo.classText and itemInfo.classText ~= "") then
			reagentInfo = _TRANS('INF_Tooltip_Class'):format(itemInfo.classText)
			addLine(reagentInfo, "aa66ee")
		end

		if (itemInfo.usageText and itemInfo.usageText ~= "") then
			reagentInfo = _TRANS('INF_Tooltip_Use'):format(itemInfo.usageText)
			addLine(reagentInfo, "aa66ee")
		end

		if (itemInfo.isPlayerMade) then
			addLine(_TRANS('INF_Interface_InfWinPlayerMade'):format(itemInfo.tradeSkillLevel, itemInfo.tradeSkillName), "5060ff")
		end

		local numReq = 0
		local numRew = 0
		local numSta = 0
		if (itemInfo.startsQuest) then numSta = 1 end
		if (itemInfo.requiredFor) then numReq = #itemInfo.requiredFor end
		if (itemInfo.rewardFrom) then numRew = #itemInfo.rewardFrom end

		local questCount = numReq + numRew + numSta

		if (questCount > 0) then
			addLine("")
			addLine(_TRANS('INF_Interface_InfWinQuest'):format(questCount), nil, nil --[[TODO: this used to say: embed. what gives?!]])

			if (numSta > 0) then
				addLine(_TRANS('INF_Interface_InfWinQuestStart'), "70ee90")
				addLine("  ".._TRANS('INF_Interface_InfWinQuestLine'):format(getQuestName(itemInfo.startsQuest)), "80ee80")
			end
			if (numRew > 0) then
				addLine(_TRANS('INF_Interface_InfWinQuestReward'):format(numRew), "70ee90")
				for i=1, numRew do
					local quest = itemInfo.rewardFrom[i]
					addLine("  ".._TRANS('INF_Interface_InfWinQuestLine'):format(getQuestName(quest)), "80ee80")
				end
			end
			if (numReq > 0) then
				addLine(_TRANS('INF_Interface_InfWinQuestRequires'):format(numReq), "70ee90")
				for i=1, numReq do
					local quest = itemInfo.requiredFor[i]
					addLine("  ".._TRANS('INF_Interface_InfWinQuestMultiLine'):format(quest[2], getQuestName(quest[1])), "80ee80")
				end
			end
			addLine(_TRANS('INF_Interface_InfWinQuestSource'):format().." WoWHead.com");
		end

		if (itemInfo.vendors) then
			local vendorCount = #itemInfo.vendors
			if (vendorCount > 0) then
				addLine("")
				addLine(_TRANS('INF_Interface_InfWinVendorCount'):format(vendorCount), "ddff40")
				for pos, merchant in pairs(itemInfo.vendors) do
					addLine("-".._TRANS('INF_Interface_InfWinVendorName'):format(merchant), "dede3c")
					if itemInfo.vendorLoc[merchant] then
						addLine("     ".._TRANS('INF_Interface_InfWinVendorName'):format(itemInfo.vendorLoc[merchant]), "ffff86")
					end
				end
			end
		end
		InformantFrame:Show()
	else
		clear()
		addLine(_TRANS('INF_Interface_InfWinNoItem'), "ff4010")
		InformantFrame:Show()
	end
end

function showHideInfo(iType, iId)
	if not iType then iType = "curitem" end
	iId = tonumber(iId) or 0

	local iTypeCur = tostring(InformantFrame.iType)
	local iIdCur = tonumber(InformantFrame.iId) or 0

	if (InformantFrame:IsVisible() and iType == iTypeCur and iId == iIdCur) then
		return InformantFrame:Hide()
	elseif (iType == "curitem") then
		showItem(Informant.itemInfo)
	elseif (iType == "item") then
	elseif (iType == "quest") then
	end
end





function OnTooltipAddMoney(self, money)
	Informant_ScanTooltip.scanningMoneyFound = money
end


local function TooltipScanBagItem(bag, slot)
	local tt = Informant_ScanTooltip
	tt.scanningMoneyFound = false
	tt:ClearLines()
	local _, count = GetContainerItemInfoSpecial(bag, slot)
	if (not count or count < 1) then return end
	tt.scanningStack = count

	-- magic happens here as OnTooltipAddMoney gets called
	local _, repairCost = tt:SetBagItem(bag, slot)
	if (type(repairCost) == "number" and repairCost > 0) then
		-- don't count price for items that need repair
		tt.scanningMoneyFound = false
		return
	end
end


local function updateSellPricesFromMerchant()
	if (not InformantLocalUpdates.items) then InformantLocalUpdates.items = {} end
	local tt = Informant_ScanTooltip

	for bag = 0, NUM_BAG_FRAMES do
		for slot = 1, GetContainerNumSlots(bag) do
			local scanningLink = C_Container.GetContainerItemLink(bag, slot)
			if (scanningLink) then
				TooltipScanBagItem(bag, slot)
				if (tt.scanningMoneyFound) then
					local informantItemInfo = getItem( scanningLink )
					if (informantItemInfo) then

						local sellPrice = tt.scanningMoneyFound / tt.scanningStack

						if (informantItemInfo.sell ~= sellPrice) then
							-- is this item sell price correct in our database? or missing from our database?
							local itemName, itemLink, itemQuality, itemLevel, itemUseLevel, itemType, itemSubType, itemStackSize, itemEquipLoc, itemTexture = GetItemInfo(scanningLink)

                            -- get item level with upgrades
                            local effLevel = GetDetailedItemLevelInfo(scanningLink)
                            if effLevel then
                                itemLevel = effLevel
                            end

							local cleanString = cleanStringFromLink( scanningLink )
							local newItemInfo = InformantLocalUpdates.items[ cleanString ]
							if (not newItemInfo) then newItemInfo = {} end
							newItemInfo.sell = sellPrice
							newItemInfo.stack = itemStackSize
							newItemInfo.quantity = itemStackSize
							InformantLocalUpdates.items[ cleanString ] = newItemInfo

						end

					end
				end	-- if money found
			end	-- if link
		end	-- for slot
	end	-- for bag
end


local function updateBuyPricesFromMerchant( vendorID )
	if (not InformantLocalUpdates.items) then InformantLocalUpdates.items = {} end
	for index = 1, GetMerchantNumItems() do
		local link = GetMerchantItemLink(index)
		if (link) then
			local name, texture, price, quantity, numAvailable, isUsableFlag, extendedCostFlag = GetMerchantItemInfo(index)
			if (extendedCostFlag) then
				--local honorPoints, arenaPoints, itemCount = GetMerchantItemCostInfo(index);
				-- NOTE - currently not using this information
			end

			local informantItemInfo = getItem( link )
			if (informantItemInfo) then		-- this should always be true

				if (price ~= informantItemInfo.buy) then
					-- is this item buy price correct in our database? or missing from our database?

					local itemName, itemLink, itemQuality, itemLevel, itemUseLevel, itemType, itemSubType, itemStackSize, itemEquipLoc, itemTexture = GetItemInfo(link)

                    -- get item level with upgrades
                    local effLevel = GetDetailedItemLevelInfo(link)
                    if effLevel then
                        itemLevel = effLevel
                    end

-- ccox - currently this will hit often, because we don't take reputation discounts into account for buy pricing
-- should we try to get rep discounts, or just update the price as seen?  Then they'll be wrong for alts!
-- could we account for the rep discounts and calculate a baseline?

					local cleanString = cleanStringFromLink( link )
					local newItemInfo = InformantLocalUpdates.items[ cleanString ]
					if (not newItemInfo) then newItemInfo = {} end
					newItemInfo.buy = price
					newItemInfo.stack = itemStackSize
					newItemInfo.quantity = quantity
					InformantLocalUpdates.items[ cleanString ] = newItemInfo

				end

				local foundMerchant = false
				if (informantItemInfo.merchantList) then
					-- some vendors are known for this item, check the list and see if this vendor is on it
					for pos, merchID in pairs(informantItemInfo.merchantList) do
						merchID = tonumber(merchID)
						if (merchID == vendorID) then
							foundMerchant = true
							break
						end
					end
				end

				-- if no vendors are known, or this vendor isn't on the list, add this vendor
				if (not foundMerchant) then

					local cleanString = cleanStringFromLink( link )
					local newItemInfo = InformantLocalUpdates.items[ cleanString ]
					if (not newItemInfo) then newItemInfo = {} end
					local oldList = newItemInfo.merchants

					if (oldList) then
						local moreMerch = split(oldList, ",")
						for pos, merchID in pairs(moreMerch) do
							if (tonumber(merchID) == vendorID) then
								foundMerchant = true
								break
							end
						end
					end

					if (not foundMerchant) then
						if (oldList) then
							newItemInfo.merchants = oldList..","..tostring( vendorID )
						else
							newItemInfo.merchants = tostring( vendorID )
						end

						InformantLocalUpdates.items[ cleanString ] = newItemInfo
					end
				end

			end	-- if info
		end	-- if link
	end	--	for GetMerchantNumItems
end


local function updateMerchantName()

	if (not InformantLocalUpdates.vendor) then InformantLocalUpdates.vendor = {} end

	local vendorName = UnitName("NPC")
	local vendorFaction = UnitFactionGroup("NPC")
	if (vendorFaction ~= UnitFactionGroup("player")) then
		vendorFaction = "Neutral"
	end
	-- TODO - we are not currently using the faction information for vendors

	local vendorGUID = UnitGUID("NPC")
	if not vendorGUID then return end -- very rarely this may be nil; bail out (and hope we get a valid ID in an update event)
	local vendorID = select(6,strsplit("-", vendorGUID))
	vendorID = tonumber(vendorID)

	if (not self.vendors[ vendorID ] and not InformantLocalUpdates.vendor[ vendorID ]) then
		-- add the new vendor name to our update list
		local vendorInfo = {}
		vendorInfo.name = vendorName;
		vendorInfo.faction = vendorFaction;
		InformantLocalUpdates.vendor[ vendorID ] = vendorInfo
	end

	return vendorID
end


local function doUpdateMerchant()

	if (not InformantLocalUpdates) then InformantLocalUpdates = {} end

	if ((not MerchantFrame:IsVisible()) or InRepairMode()) then return end

	if (not Informant.Settings.GetSetting('auto-update')) then return end

	local vendorID = updateMerchantName()
	if not vendorID then return end
	updateBuyPricesFromMerchant( vendorID )
	--updateSellPricesFromMerchant()

	wipe(cache)
end


-- cleanup data errors
-- INF-77 - duplicate merchant IDs crept in, blowing the length of the string
local function scrubLocalUpdateInfo()

	if (InformantLocalUpdates and InformantLocalUpdates.items
		and not InformantLocalUpdates.scrubbedForDupeMerchants) then

		for itemString, itemInfo in pairs(InformantLocalUpdates.items) do
			if (itemInfo.merchants) then

				-- make sure vendor IDs are unique by regenerating the list into a table
				local newTable = {}
				local merchList = split(itemInfo.merchants, ",")
				for pos, merchID in pairs(merchList) do
					newTable[ tonumber( merchID ) ] = true
				end

				-- then converting that table back into our string format
				local first = true
				local newList
				for merchID, _ in pairs(newTable) do
					if (first) then
						newList = tostring( merchID )
					else
						newList = newList..","..tostring( merchID )
					end
					first = false
				end

				-- finally, replace the existing info with scrubbed info
				itemInfo.merchants = newList
				InformantLocalUpdates.items[ itemString ] = itemInfo

			end
		end

		InformantLocalUpdates.scrubbedForDupeMerchants = true;	-- we should only do this once
	end

end


function onQuit()
	if (not InformantConfig.position) then
		InformantConfig.position = { }
	end
	InformantConfig.position.x, InformantConfig.position.y = InformantFrame:GetCenter()
end

local function slidebarclickhandler(_, button)
	--if we rightclick open the configuration window for the whole addon
	Informant.Settings.MakeGuiConfig()
	local gui = Informant.Settings.Gui
	if (gui:IsVisible()) then
		gui:Hide()
	else
		gui:Show()
	end
end

local function setupSlidebar()
	if LibStub then
		local LibDataBroker = LibStub:GetLibrary("LibDataBroker-1.1", true)
		if LibDataBroker then
			local LDBButton = LibDataBroker:NewDataObject("Informant", {
						type = "launcher",
						icon = "Interface\\AddOns\\Informant\\inficon",
						OnClick = function(self, button) slidebarclickhandler(self, button) end,
						})

			function LDBButton:OnTooltipShow()
				self:AddLine("Informant Configuration",  1,1,0.5, 1)
			end
			function LDBButton:OnEnter()
				GameTooltip:SetOwner(self, "ANCHOR_NONE")
				GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
				GameTooltip:ClearLines()
				LDBButton.OnTooltipShow(GameTooltip)
				GameTooltip:Show()
			end
			function LDBButton:OnLeave()
				GameTooltip:Hide()
			end
		end
	end
end

local function setupStubby()
	Stubby.RegisterEventHook("PLAYER_LEAVING_WORLD", "Informant", onQuit)
	-- Setup the default for stubby to always load (people can override this on a
	-- per toon basis)
	Stubby.SetConfig("Informant", "LoadType", "always", true)
	-- Register our temporary command hook with stubby
	Stubby.RegisterBootCode("Informant", "CommandHandler",
	-- Localize Me!
	[[
		local function cmdHandler(msg)
			local cmd, param = msg:lower():match("^(%w+)%s*(.*)$")
			cmd = cmd or msg:lower() or "";
			param = param or "";
			if (cmd == "load") then
				if (param == "") then
					Stubby.Print("Manually loading Informant...")
					C_AddOns.LoadAddOn("Informant")
				elseif (param == "always") then
					Stubby.Print("Setting Informant to always load for this character")
					Stubby.SetConfig("Informant", "LoadType", param)
					C_AddOns.LoadAddOn("Informant")
				elseif (param == "never") then
					Stubby.Print("Setting Informant to never load automatically for this character (you may still load manually)")
					Stubby.SetConfig("Informant", "LoadType", param)
				else
					Stubby.Print("Your command was not understood")
				end
			else
				Stubby.Print("Informant is currently not loaded.")
				Stubby.Print("  You may load it now by typing |cffffffff/informant load|r")
				Stubby.Print("  You may also set your loading preferences for this character by using the following commands:")
				Stubby.Print("  |cffffffff/informant load always|r - Informant will always load for this character")
				Stubby.Print("  |cffffffff/informant load never|r - Informant will never load automatically for this character (you may still load it manually)")
			end
		end
		SLASH_INFORMANT1 = "/informant"
		SLASH_INFORMANT2 = "/inform"
		SLASH_INFORMANT3 = "/info"
		SLASH_INFORMANT4 = "/inf"
		SlashCmdList["INFORMANT"] = cmdHandler
	]])
	Stubby.RegisterBootCode("Informant", "Triggers", [[
		local loadType = Stubby.GetConfig("Informant", "LoadType")
		if (loadType == "always") then
			C_AddOns.LoadAddOn("Informant")
		else
			Stubby.Print("]].._TRANS('INF_Help_CmdLoadMsg')..[["); -- ### _TRANS
		end
	]])
end

function onLoad()
	InformantFrame:RegisterEvent("ADDON_LOADED")

	--Informant_ScanTooltip:SetScript("OnTooltipAddMoney", OnTooltipAddMoney);

	InformantFrame:RegisterEvent("MERCHANT_SHOW");
	InformantFrame:RegisterEvent("MERCHANT_UPDATE");

	--Informant_ScanTooltip:SetScript("OnTooltipAddMoney", OnTooltipAddMoney);

	setupSlidebar()
end

local ALTCHATLINKTOOLTIP_OPEN
local function callbackAltChatLinkTooltip(link, text, button, chatFrame)
	if button == "LeftButton"
	and Informant.Settings.GetSetting("altchatlink-tooltip")
	and link:sub(1, 4) == "item" then
		return ALTCHATLINKTOOLTIP_OPEN
	end
end

local function frameLoaded()
	tooltip:Activate()
	tooltip:AddCallback(Informant.TooltipHandler, 300)
	tooltip:AltChatLinkRegister(callbackAltChatLinkTooltip)
	ALTCHATLINKTOOLTIP_OPEN = tooltip:AltChatLinkConstants()

	InformantFrame:SetBackdrop({
			bgFile = "Interface/DialogFrame/UI-DialogBox-Background",
			edgeFile = "Interface/TutorialFrame/TutorialFrameBorder",
			tile = true, tileSize = 32, edgeSize = 32,
			insets = { left = 4, right = 4, top = 4, bottom = 4 }
		})

	onLoad()
end

function onVariablesLoaded()
	if (not InformantConfig) then
		InformantConfig = {}
	end
end

function onInitialize()
	-- Translations are now available
	InformantFrameTitle:SetText(_TRANS('INF_Interface_InfWinTitle'))
	BINDING_HEADER_INFORMANT_HEADER = _TRANS('INF_Interface_BindingHeader')
	BINDING_NAME_INFORMANT_POPUPDOWN = _TRANS('INF_Interface_BindingTitle')

	soulbindtypes = {_TRANS("INF_Tooltip_SoulBindUse"),_TRANS("INF_Tooltip_SoulBindEquip"),_TRANS("INF_Tooltip_SoulBindPickup")}
	specialbindtypes = {_TRANS("INF_Tooltip_SpecialBindAccount"),_TRANS("INF_Tooltip_SpecialBindGuild")}

	if (InformantConfig.position) then
		InformantFrame:ClearAllPoints()
		InformantFrame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", InformantConfig.position.x, InformantConfig.position.y)
	end

	if (not InformantConfig.welcomed) then
		clear()
		addLine(_TRANS('INF_Help_FirstUse'))
		InformantConfig.welcomed = true
	end

	Informant.Commands.OnLoad()
	setupStubby()
end

function onEvent(event, addon)

	if (event == "ADDON_LOADED" and addon:lower() == "informant") then
		onVariablesLoaded()
		Informant.Locale.OnLoad() -- Translations are available after this point
		onInitialize()
		InformantFrame:UnregisterEvent("ADDON_LOADED")
		scrubLocalUpdateInfo()		-- to fix up data errors
	end

	if( event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" ) then
		doUpdateMerchant()
	end

end

function frameActive(isActive)
	if (isActive) then
		scrollUpdate(0)
	end
end

function getRowCount()
	return #lines
end

function scrollUpdate()
	local numLines = getRowCount()
	local offset = 0
	if (numLines > 25) then
		offset = FauxScrollFrame_GetOffset(InformantFrameScrollBar)
	end
	local line
	for i=1, 25 do
		line = lines[i+offset]
		local f = _G[format("InformantFrameText%d",i)]
		if (line) then
			f:SetText(line)
			f:Show()
		else
			f:Hide()
		end
	end
	if (numLines > 25) then
		FauxScrollFrame_Update(InformantFrameScrollBar, numLines, 25, numLines)
		InformantFrameScrollBar:Show()
	else
		InformantFrameScrollBar:Hide()
	end
end

local function testWrap(text)
	InformantFrameTextTest:SetText(text)
	if (InformantFrameTextTest:GetWidth() < InformantFrame:GetWidth() - 20) then
		return text, ""
	end

	local pos, test, best, rest
	best = text
	rest = nil
	pos = text:find("%s")
	while (pos) do
		test = text:sub(1, pos-1)
		InformantFrameTextTest:SetText(test)
		if (InformantFrameTextTest:GetWidth() < InformantFrame:GetWidth() - 20) or (not rest) then
			best = test
			rest = test:sub(pos+1)
		else
			break
		end
		pos = text:find("%s", pos+1)
	end
	return best, rest
end

function addLine(text, color, level)
	if (not text) then return end
	if (not level) then level = 1 end
	if (level > 100) then
		return
	end

	if (type(text) == "table") then
		for pos, line in pairs(text) do
			addLine(line, color, level)
		end
		return
	end

	if (not text) then
		tinsert(lines, "nil")
	else
		local best, rest = testWrap(text)
		if (color) then
			tinsert(lines, ("|cff%s%s|r"):format(color, best))
		else
			tinsert(lines, best)
		end
		if (rest) and (rest ~= "") then
			addLine(rest, color, level+1)
		end
	end
	scrollUpdate()
end

function clear()
	lines = {}
	scrollUpdate()
end

-- GLOBAL OBJECT

local DebugLib = LibStub("DebugLib")
local debug, assert, printquick
if DebugLib then
	debug, assert, printquick = DebugLib("Informant")
else
	function debug() end
	assert = debug
	printquick = debug
end

-------------------------------------------------------------------------------
-- Prints the specified message to nLog.
--
-- syntax:
--    errorCode, message = infDebugPrint([message][, category][, title][, errorCode][, level])
--
-- parameters:
--    message   - (string) the error message
--                nil, no error message specified
--    category  - (string) the category of the debug message
--                nil, no category specified
--    title     - (string) the title for the debug message
--                nil, no title specified
--    errorCode - (number) the error code
--                nil, no error code specified
--    level     - (string) nLog message level
--                         Any nLog.levels string is valid.
--                nil, no level specified
--
-- returns:
--    errorCode - (number) errorCode, if one is specified
--                nil, otherwise
--    message   - (string) message, if one is specified
--                nil, otherwise
-------------------------------------------------------------------------------
function infDebugPrint(message, category, title, errorCode, level)
	return debug(message, category, title, errorCode, level)
end

-------------------------------------------------------------------------------
-- Prints the specified message to nLog.
--
-- syntax:
--    errorCode, message = debugPrint([message][, title][, errorCode][, level])
--
-- parameters:
--    message   - (string) the error message
--                nil, no error message specified
--    title     - (string) the title for the debug message
--                nil, no title specified
--    errorCode - (number) the error code
--                nil, no error code specified
--    level     - (string) nLog message level
--                         Any nLog.levels string is valid.
--                nil, no level specified
--
-- returns:
--    errorCode - (number) errorCode, if one is specified
--                nil, otherwise
--    message   - (string) message, if one is specified
--                nil, otherwise
-------------------------------------------------------------------------------
function debugPrint(message, title, errorCode, level)
	return Informant.DebugPrint(message, "InfMain", title, errorCode, level)
end

function Dump(...)
	return debug:Dump(...)
end

function debugPrintQuick(...)
	return printquick(...)
end

local install = {
	version = VERSION,
	GetItem = getItem,
	GetRowCount = getRowCount,
	AddLine = addLine,
	Clear = clear,
	ShowHideInfo = showHideInfo,
	getItemVendorInfo = getInformantVendorInfo,

	-- These functions are only meant for internal use.
	SetSkills = setSkills,
	SetRequirements = setRequirements,
	SetVendors = setVendors,
	SetDatabase = setDatabase,
	SetQuestStarts = setQuestStarts,
	SetQuestRewards = setQuestRewards,
	SetQuestRequires = setQuestRequires,
	SetQuestNames = setQuestNames,
	SetVendorLocation = setVendorLocation,
	SetZones = setZones,
	SetCrafted = setCrafted,
	SetCraftCount = setCraftCount,
	FrameActive = frameActive,
	FrameLoaded = frameLoaded,
	ScrollUpdate = scrollUpdate,
	GetLocale = getLocale,
	GetQuestName = getQuestName,
	OnEvent = onEvent,
	DebugPrint = infDebugPrint,
	DebugPrintQuick = debugPrintQuick,
}

for k, v in pairs(install) do
	Informant[k] = v
end
