--[[
	Auctioneer Addon for World of Warcraft(tm).
	Version: 3.8.0 (Kangaroo)
	Revision: $Id: AucUtil.lua 981 2006-08-31 05:49:45Z mentalpower $

	Auctioneer utility functions.
	Functions to maniuplate items keys, signatures etc

	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.
]]

--Local function prototypes
local getTimeLeftString, getSecondsLeftString, unpackSeconds, getGSC, getTextGSC, nilSafeString, colorTextWhite, getWarnColor, nullSafe, sanifyAHSnapshot, getAuctionKey, getOppositeKey, getNeutralKey, getHomeKey, isValidAlso, breakItemKey, split, findClass, getCatName, getCatNumberByName, getCatForKey, getKeyFromSig, getCatForSig, getItemLinks, getItems, getItemHyperlinks, loadCategories, loadCategoryClasses, loadCategorySubClasses, chatPrint, setFilterDefaults, protectAuctionFrame, priceForOne, round, delocalizeFilterVal, localizeFilterVal, getLocalizedFilterVal, delocalizeCommand, localizeCommand, findEmptySlot, containerFrameItemButtonOnClick

-- return the string representation of the given timeLeft constant
function getTimeLeftString(timeLeft)
	local timeLeftString = "";

	if timeLeft == Auctioneer.Core.Constants.TimeLeft.Short then
		timeLeftString = _AUCT('TimeShort');

	elseif timeLeft == Auctioneer.Core.Constants.TimeLeft.Medium then
		timeLeftString = _AUCT('TimeMed');

	elseif timeLeft == Auctioneer.Core.Constants.TimeLeft.Long then
		timeLeftString = _AUCT('TimeLong');

	elseif timeLeft == Auctioneer.Core.Constants.TimeLeft.VeryLong then
		timeLeftString = _AUCT('TimeVlong');
	end

	return timeLeftString;
end

function getSecondsLeftString(secondsLeft)
	local timeLeft = nil;

	for i = table.getn(Auctioneer.Core.Constants.TimeLeft.Seconds), 1, -1 do

		if (secondsLeft >= Auctioneer.Core.Constants.TimeLeft.Seconds[i]) then
			timeLeft = i;
			break
		end
	end

	return getTimeLeftString(timeLeft);
end

function unpackSeconds(seconds)
	seconds = tonumber(seconds)
	if (not seconds) then
		return
	end

	local weeks
	local days
	local hours
	local minutes

	seconds = math.floor(seconds)

	if (seconds > 604800) then
		weeks = math.floor(seconds / 604800)
		seconds = math.floor(seconds - (weeks * 604800))
	end
	if (seconds > 86400) then
		days = math.floor(seconds / 86400)
		seconds = math.floor(seconds - (days * 86400))
	end
	if (seconds > 3600) then
		hours = math.floor(seconds / 3600)
		seconds = math.floor(seconds - (hours * 3600))
	end
	if (seconds >= 60) then
		minutes = math.floor(seconds / 60)
		seconds = math.floor(seconds - (minutes * 60))
	end

	return (weeks or 0), (days or 0), (hours or 0), (minutes or 0), (seconds or 0)
end

function getGSC(money)
	local g,s,c = EnhTooltip.GetGSC(money);
	return g,s,c;
end

function getTextGSC(money)
	return EnhTooltip.GetGSC(money);
end

-- return an empty string if str is nil
function nilSafeString(str)
	return str or "";
end

function colorTextWhite(text)
	if (not text) then text = ""; end

	local COLORING_START = "|cff%s%s|r";
	local WHITE_COLOR = "e6e6e6";

	return string.format(COLORING_START, WHITE_COLOR, ""..text);
end

function getWarnColor(warn)
	--Make "warn" a required parameter and verify that its a string
	if (not (type(warn) == "string")) then
		return nil
	end

	local cHex, cRed, cGreen, cBlue;

	if (Auctioneer.Command.GetFilter('warn-color')) then
		local FrmtWarnAbovemkt, FrmtWarnUndercut, FrmtWarnNocomp, FrmtWarnAbovemkt, FrmtWarnMarkup, FrmtWarnUser, FrmtWarnNodata, FrmtWarnMyprice

		FrmtWarnToolow = _AUCT('FrmtWarnToolow');
		FrmtWarnNocomp = _AUCT('FrmtWarnNocomp');
		FrmtWarnAbovemkt = _AUCT('FrmtWarnAbovemkt');
		FrmtWarnUser = _AUCT('FrmtWarnUser');
		FrmtWarnNodata = _AUCT('FrmtWarnNodata');
		FrmtWarnMyprice = _AUCT('FrmtWarnMyprice');

		FrmtWarnUndercut = string.format(_AUCT('FrmtWarnUndercut'), tonumber(Auctioneer.Command.GetFilterVal('pct-underlow')));
		FrmtWarnMarkup = string.format(_AUCT('FrmtWarnMarkup'), tonumber(Auctioneer.Command.GetFilterVal('pct-markup')));

		if (warn == FrmtWarnToolow) then
			--Color Red
			cHex = "ffff0000";
			cRed = 1.0;
			cGreen = 0.0;
			cBlue = 0.0;

		elseif (warn == FrmtWarnUndercut) then
			--Color Yellow
			cHex = "ffffff00";
			cRed = 1.0;
			cGreen = 1.0;
			cBlue = 0.0;

		elseif ((warn == FrmtWarnNocomp) or (warn == FrmtWarnAbovemkt)) then
			--Color Green
			cHex = "ff00ff00";
			cRed = 0.0;
			cGreen = 1.0;
			cBlue = 0.0;

		elseif ((warn == FrmtWarnMarkup) or (warn == FrmtWarnUser) or (warn == FrmtWarnNodata) or (warn == FrmtWarnMyprice)) then
			--Color Gray
			cHex = "ff999999";
			cRed = 0.6;
			cGreen = 0.6;
			cBlue = 0.6;
		end

	else
		--Color Orange
		cHex = "ffe66600";
		cRed = 0.9;
		cGreen = 0.4;
		cBlue = 0.0;
	end

	return cHex, cRed, cGreen, cBlue
end

-- Used to convert variables that should be numbers but are nil to 0
function nullSafe(val)
	return tonumber(val) or 0;
end

-- Returns the current faction's auction signature, depending on location
function getAuctionKey()
	local serverName = GetCVar("realmName");
	local currentZone = GetMinimapZoneText();
	local factionGroup;

	--Added the ability to record Neutral AH auctions in its own tables.
	if ((currentZone == "Gadgetzan") or (currentZone == "Everlook") or (currentZone == "Booty Bay")) then
		factionGroup = "Neutral"

	else
		factionGroup = UnitFactionGroup("player");
	end
	return serverName.."-"..factionGroup;
end

-- Returns the current faction's opposing faction's auction signature
function getOppositeKey()
	local serverName = GetCVar("realmName");
	local factionGroup = UnitFactionGroup("player");

	if (factionGroup == "Alliance") then factionGroup="Horde"; else factionGroup="Alliance"; end
	return serverName.."-"..factionGroup;
end

-- Returns the current server's neutral auction signature
function getNeutralKey()
	local serverName = GetCVar("realmName");

	return serverName.."-Neutral";
end

-- Returns the current faction's auction signature
function getHomeKey()
	local serverName = GetCVar("realmName");
	local factionGroup = UnitFactionGroup("player");

	return serverName.."-"..factionGroup;
end

-- function returns true, if the given parameter is a valid option for the also command, false otherwise
function isValidAlso(also)
	if (type(also) ~= "string") then
		return false
	end

	if ((also == 'opposite') or (also == 'off') or (also == 'neutral') or (also == 'home')) then
		return true		-- allow special keywords
	end

	-- check if string matches: "[realm]-[faction]"
	local s, e, r, f = string.find(also, "^(.+)-(.+)$")
	if (s == nil) then
		return false	-- invalid string
	end

	-- check if faction = "Horde" or "Alliance"
	if (f == 'Horde') or (f == 'Alliance')or (f == 'Neutral') then
		return true
	end

	return true
end

-- Given an item key, breaks it into its itemID, randomProperty and enchantProperty
function breakItemKey(itemKey)
	local i,j, itemID, randomProp, enchant = string.find(itemKey, "(%d+):(%d+):(%d+)");
	return tonumber(itemID or 0), tonumber(randomProp or 0), tonumber(enchant or 0);
end

function split(str, at)
	local splut = {};

	if (type(str) ~= "string") then return nil end
	if (not str) then str = "" end

	if (not at)
		then table.insert(splut, str)

	else
		for n, c in string.gfind(str, '([^%'..at..']*)(%'..at..'?)') do
			table.insert(splut, n);

			if (c == '') then break end
		end
	end
	return splut;
end

function findClass(cName, sName)

	if (AuctionConfig and AuctionConfig.classes) then

		for class, cData in pairs(AuctionConfig.classes) do

			if (cData.name == cName) then
				if (sName == nil) then return class, 0; end

				for sClass, sData in pairs(cData) do
					if (sClass ~= "name") and (sData == sName) then
						return class, sClass;
					end
				end
				return class, 0;
			end
		end
	end
	return 0,0;
end

function getCatName(number)
	if (number == 0) then return "" end;

	if (AuctionConfig.classes[number]) then
		return AuctionConfig.classes[number].name;
	end
	return nil;
end

function getCatNumberByName(name)
	if (not name) then return 0 end
	if (AuctionConfig and AuctionConfig.classes) then

		for cat, class in pairs(AuctionConfig.classes) do
			if (name == class.name) then
				return cat;
			end
		end
	end
	return 0;
end

function getCatForKey(itemKey)
	local info = Auctioneer.Core.GetInfo(itemKey);
	return info.category;
end

function getKeyFromSig(auctSig)
	local id, rprop, enchant = Auctioneer.Core.GetItemSignature(auctSig);
	return id..":"..rprop..":"..enchant;
end

function getCatForSig(auctSig)
	local itemKey = getKeyFromSig(auctSig);
	return getCatForKey(itemKey);
end


function getItemLinks(str)
	if (not (type(str) == "string")) then
		return
	end
	local itemList = {};

	for link, item in string.gfind(str, "|Hitem:([^|]+)|h[[]([^]]+)[]]|h") do
		table.insert(itemList, item.." = "..link)
	end
	return itemList;
end

function getItems(str)
	if (not (type(str) == "string")) then
		return
	end
	local itemList = {};
	local itemKey;

	for itemID, randomProp, enchant, uniqID in string.gfind(str, "|Hitem:(%d+):(%d+):(%d+):(%d+)|h") do
		itemKey = itemID..":"..randomProp..":"..enchant;
		table.insert(itemList, itemKey)
	end
	return itemList;
end

--Many thanks to the guys at irc://irc.datavertex.com/cosmostesters for their help in creating this function
function getItemHyperlinks(str)
	if (not (type(str) == "string")) then
		return
	end
	local itemList = {};

	for color, item, name in string.gfind(str, "|c(%x+)|Hitem:(%d+:%d+:%d+:%d+)|h%[(.-)%]|h|r") do
		table.insert(itemList, "|c"..color.."|Hitem:"..item.."|h["..name.."]|h|r")
	end
	return itemList;
end

function loadCategories()
	if (not AuctionConfig.classes) then AuctionConfig.classes = {} end
	loadCategoryClasses(GetAuctionItemClasses());
end

function loadCategoryClasses(...)
	for c=1, arg.n, 1 do
		AuctionConfig.classes[c] = {};
		AuctionConfig.classes[c].name = arg[c];
		loadCategorySubClasses(c, GetAuctionItemSubClasses(c));
	end
end

function loadCategorySubClasses(c, ...)
	for s=1, arg.n, 1 do
		AuctionConfig.classes[c][s] = arg[s];
	end
end

function chatPrint(text, cRed, cGreen, cBlue, cAlpha, holdTime)
	local frameIndex = Auctioneer.Command.GetFrameIndex();

	if (cRed and cGreen and cBlue) then
		if getglobal("ChatFrame"..frameIndex) then
			getglobal("ChatFrame"..frameIndex):AddMessage(text, cRed, cGreen, cBlue, cAlpha, holdTime);

		elseif (DEFAULT_CHAT_FRAME) then
			DEFAULT_CHAT_FRAME:AddMessage(text, cRed, cGreen, cBlue, cAlpha, holdTime);
		end

	else
		if getglobal("ChatFrame"..frameIndex) then
			getglobal("ChatFrame"..frameIndex):AddMessage(text, 0.0, 1.0, 0.25);

		elseif (DEFAULT_CHAT_FRAME) then
			DEFAULT_CHAT_FRAME:AddMessage(text, 0.0, 1.0, 0.25);
		end
	end
end

function setFilterDefaults()
	if (not AuctionConfig.filters) then
		AuctionConfig.filters = {};
	end

	for k,v in ipairs(Auctioneer.Core.Constants.FilterDefaults) do
		if (AuctionConfig.filters[k] == nil) then
			AuctionConfig.filters[k] = v;
		end
	end
end

-- Pass true to protect the Auction Frame from being undesireably closed, not true to disable this
function protectAuctionFrame(enable)
	--Make sure we have an AuctionFrame before doing anything
	if (AuctionFrame) then
		--Handle enabling of protection

		if (enable and not Auctioneer_ProtectionEnabled and AuctionFrame:IsShown()) then
			--Remember that we are now protecting the frame
			Auctioneer_ProtectionEnabled = true;
			--If the frame is the current doublewide frame, then clear the doublewide

			if ( GetDoublewideFrame() == AuctionFrame ) then
				SetDoublewideFrame(nil)
			end
			--Remove the frame from the UI frame handling system
			UIPanelWindows["AuctionFrame"] = nil
			--If mobile frames is around, then remove AuctionFrame from Mobile Frames handling system

			if (MobileFrames_UIPanelWindowBackup) then
				MobileFrames_UIPanelWindowBackup.AuctionFrame = nil;
			end

			if (MobileFrames_UIPanelsVisible) then
				MobileFrames_UIPanelsVisible.AuctionFrame = nil;
			end
			--Hook the function to show the WorldMap, WorldMap has internal code that forces all these frames to close
			--so for it, we have to prevent it from showing at all

			if (not Auctioneer_ToggleWorldMap) then
				Auctioneer_ToggleWorldMap = ToggleWorldMap;
			end
			ToggleWorldMap = function ()

				if ( ( not Auctioneer_ProtectionEnabled ) or ( not ( AuctionFrame and AuctionFrame:IsVisible() ) ) ) then
					Auctioneer_ToggleWorldMap();

				else
					UIErrorsFrame:AddMessage(_AUCT('GuiNoWorldMap'), 0, 1, 0, 1.0, UIERRORS_HOLD_TIME)
				end
			end

		elseif (Auctioneer_ProtectionEnabled) then
			--Handle disabling of protection
			Auctioneer_ProtectionEnabled = nil;
			--If Mobile Frames is around, then put the frame back under its control if it is proper to do so

			if ( MobileFrames_UIPanelWindowBackup and MobileFrames_MasterEnableList and MobileFrames_MasterEnableList["AuctionFrame"] ) then
				MobileFrames_UIPanelWindowBackup.AuctionFrame = { area = "doublewide", pushable = 0 };

				if ( MobileFrames_UIPanelsVisible and AuctionFrame:IsVisible() ) then
					MobileFrames_UIPanelsVisible.AuctionFrame = 0;
				end

			else
				--Put the frame back into the UI frame handling system
				UIPanelWindows["AuctionFrame"] = { area = "doublewide", pushable = 0 };

				if ( AuctionFrame:IsVisible() ) then
					SetDoublewideFrame(AuctionFrame)
				end
			end
		end
	end
end

function priceForOne(price, count)
	price = nullSafe(price)
	count = math.max(nullSafe(count), 1)
	return math.ceil(price / count)
end

function round(x)
	local y = math.floor(x);

	if (x - y >= 0.5) then
		return y + 1;
	end
	return y;
end

-------------------------------------------------------------------------------
-- Localization functions
-------------------------------------------------------------------------------

Auctioneer.Command.CommandMap = nil;
Auctioneer.Command.CommandMapRev = nil;

function delocalizeFilterVal(value)
	if (value == _AUCT('CmdOn')) then
		return 'on';

	elseif (value == _AUCT('CmdOff')) then
		return 'off';

	elseif (value == _AUCT('CmdDefault')) then
		return 'default';

	elseif (value == _AUCT('CmdToggle')) then
		return 'toggle';

	else
		return value;
	end
end

function localizeFilterVal(value)
	local result

	if (value == 'on') then
		result = _AUCT('CmdOn');

	elseif (value == 'off') then
		result = _AUCT('CmdOff');

	elseif (value == 'default') then
		result = _AUCT('CmdDefault');

	elseif (value == 'toggle') then
		result = _AUCT('CmdToggle');
	end

	if (result) then return result; else return value; end
end

function getLocalizedFilterVal(key)
	return localizeFilterVal(Auctioneer.Command.GetFilterVal(key))
end

-- Turns a localized slash command into the generic English version of the command
function delocalizeCommand(cmd)
	if (not Auctioneer.Command.CommandMap) then Auctioneer.Command.BuildCommandMap();end
	local result = Auctioneer.Command.CommandMap[cmd];

	if (result) then return result; else return cmd; end
end

-- Translate a generic English slash command to the localized version, if available
function localizeCommand(cmd)
	if (not Auctioneer.Command.CommandMapRev) then Auctioneer.Command.BuildCommandMap(); end
	local result = Auctioneer.Command.CommandMapRev[cmd];

	if (result) then return result; else return cmd; end
end

-------------------------------------------------------------------------------
-- Inventory modifying functions
-------------------------------------------------------------------------------

function findEmptySlot()
	local name, i
	for bag = 0, 4 do
		name = GetBagName(bag)
		i = string.find(name, '(Quiver|Ammo|Bandolier)')
		if not i then
			for slot = 1, GetContainerNumSlots(bag),1 do
				if not (GetContainerItemInfo(bag,slot)) then
					return bag, slot;
				end
			end
		end
	end
end


function containerFrameItemButtonOnClick(hookParams, returnValue, button, ignoreShift) --Auctioneer_ContainerFrameItemButton_OnClick
	local bag = this:GetParent():GetID()
	local slot = this:GetID()

	local texture, count, noSplit = GetContainerItemInfo(bag, slot)
	local link = GetContainerItemLink(bag, slot)
	if (count and count > 1 and not noSplit) then
		if (button == "RightButton") and (IsControlKeyDown()) then
			local splitCount = math.floor(count / 2)
			local emptyBag, emptySlot = findEmptySlot()
			if (emptyBag) then
				SplitContainerItem(bag, slot, splitCount)
				PickupContainerItem(emptyBag, emptySlot)
			else
				chatPrint("Can't split, all bags are full")
			end
			return "abort";
		end
	end

	if (AuctionFrame and AuctionFrame:IsVisible()) then
		if (link) then
			if (button == "RightButton") and (IsAltKeyDown()) then
				AuctionFrameTab_OnClick(1)
				local itemID = EnhTooltip.BreakLink(link)
				if (itemID) then
					local itemName = GetItemInfo(tostring(itemID))
					if (itemName) then
						BrowseName:SetText(itemName)
						BrowseMinLevel:SetText("")
						BrowseMaxLevel:SetText("")
						AuctionFrameBrowse.selectedInvtype = nil
						AuctionFrameBrowse.selectedInvtypeIndex = nil
						AuctionFrameBrowse.selectedClass = nil
						AuctionFrameBrowse.selectedClassIndex = nil
						AuctionFrameBrowse.selectedSubclass = nil
						AuctionFrameBrowse.selectedSubclassIndex = nil
						AuctionFrameFilters_Update()
						IsUsableCheckButton:SetChecked(0)
						UIDropDownMenu_SetSelectedValue(BrowseDropDown, -1)
						AuctionFrameBrowse_Search()
						BrowseNoResultsText:SetText(BROWSE_NO_RESULTS)
					end
				end
				return "abort";
			end
		end
	end

	if (not CursorHasItem() and AuctionFrameAuctions and AuctionFrameAuctions:IsVisible() and IsAltKeyDown()) then
		PickupContainerItem(bag, slot)
		if (CursorHasItem() and Auctioneer.Command.GetFilter('auction-click')) then
			ClickAuctionSellItemButton()
			AuctionsFrameAuctions_ValidateAuction()
			local start = MoneyInputFrame_GetCopper(StartPrice)
			local buy = MoneyInputFrame_GetCopper(BuyoutPrice)
			local duration = AuctionFrameAuctions.duration
			local warn = AuctionInfoWarnText:GetText()
			if (AuctionsCreateAuctionButton:IsEnabled() and IsShiftKeyDown()) then
				local cHex, cRed, cGreen, cBlue = getWarnColor(warn)
				warn = ("|c"..cHex..warn.."|r")
				StartAuction(start, buy, duration);
				chatPrint(string.format(_AUCT('FrmtAutostart'), link, EnhTooltip.GetTextGSC(start), EnhTooltip.GetTextGSC(buy), duration/60, warn));
			end
			return "abort";
		end
	end

	if (not CursorHasItem() and AuctionFramePost and AuctionFramePost:IsVisible() and button == "LeftButton" and IsAltKeyDown()) then
		local _, count = GetContainerItemInfo(bag, slot);
		if (count) then
			if (count > 1 and IsShiftKeyDown()) then
				this.SplitStack = function(button, split)
					local _, _, _, _, name = EnhTooltip.BreakLink(link);
					AuctionFramePost:SetAuctionItem(bag, slot, split);
				end
				OpenStackSplitFrame(count, this, "BOTTOMRIGHT", "TOPRIGHT");
			else
				local _, _, _, _, name = EnhTooltip.BreakLink(link);
				AuctionFramePost:SetAuctionItem(bag, slot, 1);
			end
			return "abort";
		end
	end
end

Auctioneer.Util = {
	GetTimeLeftString = getTimeLeftString,
	GetSecondsLeftString = getSecondsLeftString,
	UnpackSeconds = unpackSeconds,
	GetGSC = getGSC,
	GetTextGSC = getTextGSC,
	NilSafeString = nilSafeString,
	ColorTextWhite = colorTextWhite,
	GetWarnColor = getWarnColor,
	NullSafe = nullSafe,
	SanifyAHSnapshot = sanifyAHSnapshot,
	GetAuctionKey = getAuctionKey,
	GetOppositeKey = getOppositeKey,
	GetNeutralKey = getNeutralKey,
	GetHomeKey = getHomeKey,
	IsValidAlso = isValidAlso,
	BreakItemKey = breakItemKey,
	Split = split,
	FindClass = findClass,
	GetCatName = getCatName,
	GetCatNumberByName = getCatNumberByName,
	GetCatForKey = getCatForKey,
	GetKeyFromSig = getKeyFromSig,
	GetCatForSig = getCatForSig,
	GetItemLinks = getItemLinks,
	GetItems = getItems,
	GetItemHyperlinks = getItemHyperlinks,
	LoadCategories = loadCategories,
	LoadCategoryClasses = loadCategoryClasses,
	LoadCategorySubClasses = loadCategorySubClasses,
	ChatPrint = chatPrint,
	SetFilterDefaults = setFilterDefaults,
	ProtectAuctionFrame = protectAuctionFrame,
	PriceForOne = priceForOne,
	Round = round,
	DelocalizeFilterVal = delocalizeFilterVal,
	LocalizeFilterVal = localizeFilterVal,
	GetLocalizedFilterVal = getLocalizedFilterVal,
	DelocalizeCommand = delocalizeCommand,
	LocalizeCommand = localizeCommand,
	FindEmptySlot = findEmptySlot,
	ContainerFrameItemButtonOnClick = containerFrameItemButtonOnClick,
}
