--[[
	CommonFunctions
	
	This file is for commonly used functions available to any
	Cosmos Mod. Thanks to Chitinous, Skrag and Thott for most of
	these functions. :-)

	by Alexander Brazie

  ]]--

DEBUG_CHAT_FRAME = ChatFrame1;

-- Output functions

	-- NOTE***  ChatTimeStamps is completely overloading this function when enabled
function Print(msg, r, g, b, frame, id) 
	if (not r) then r = 1.0; end
	if (not g) then g = 1.0; end
	if (not b) then b = 1.0; end
	if (not frame) then frame = DEFAULT_CHAT_FRAME; end
	if (frame) then
		if (not id) then
			frame:AddMessage(msg,r,g,b);
		else
			frame:AddMessage(msg,r,g,b,id);
		end
	end
end

-- Prints a table in an organized format
function PrintTable(table, rowname, level) 
	if ( rowname == nil ) then rowname = "ROOT"; end
	if ( level == nil ) then level = 1; end

	local msg = "";
	for i=1,level, 1 do 
		msg = msg .. "   ";	
	end

	if ( table == nil ) then Print (msg.."["..rowname.."] := nil "); return end
	if ( type(table) == "table" ) then
		Print (msg..rowname.." { ");
		for k,v in table do
			PrintTable(v,k,level+1);
		end
		Print(msg.."} ");
	elseif (type(table) == "function" ) then 
		Print(msg.."["..rowname.."] => {{FunctionPtr*}}");
	elseif (type(table) == "userdata" ) then 
		Print(msg.."["..rowname.."] => {{UserData}}");
	elseif (type(table) == "boolean" ) then 
		local value = "true";
		if ( not table ) then
			value = "false";
		end
		Print(msg.."["..rowname.."] => "..value);
	else
		Print(msg.."["..rowname.."] => "..table);
	end
end

-- DebugPrint takes a message and a debug key. 
-- 
-- The default is "COSMOS_DEBUG", but you can set it to any 
-- value you like. The result is that if setglobal(debugkey,1); 
-- has been called, your debug message will be output.
--
function DebugPrint(msg, debugkey) 
	if ( not debugkey ) then debugkey = "COSMOS_DEBUG"; end

	if ( type(msg) == "string" ) then 
		if ( getglobal(debugkey) == 1 ) then
				Print("DBG: "..msg, 1, 1, .5, DEBUG_CHAT_FRAME);
		end
	end
end

-- Prints an error message
function ErrorPrint(msg)
	local info = ChatTypeInfo["YELL"];
	UIErrorsFrame:AddMessage(msg, info.r, info.g, info.b, 1.0, UIERRORS_HOLD_TIME);
end

-- Table functions
function GetIndexInList(list,item) 
	for k,v in list do 
		if ( v == item ) then return k; end
	end
	return -1;
end

-- Function Hooking (From ThottbotUtil.lua)

-- HookFunction("some_blizzard_function","my_function","before|after|hide")
-- calls "my_function" before/after "some_blizzard_function".
-- if type is "hide", calls "my_function" before all others, and only continues if it returns true
-- This method is used so the hook can be later undone without screwing up someone else's later hook.
function HookFunction(orig,new,htype)
  if(not htype) then
    htype = "before";
  end
  DebugPrint("Hooking "..orig.." to "..new..", type "..htype);
  if(not Hooks) then
    Hooks = {};
  end
  if(not Hooks[orig]) then
    Hooks[orig] = {};
    Hooks[orig].before = {};
    Hooks[orig].before.n = 0;
    Hooks[orig].after = {};
    Hooks[orig].after.n = 0;
    Hooks[orig].hide = {};
    Hooks[orig].hide.n = 0;
    Hooks[orig].orig = getglobal(orig);
  else
    for key,value in Hooks[orig] do
      if(key == new) then
	DebugPrint("already hooked "..new..", skipping");
	return;
      end
    end
  end
  -- intentionally will error if bad type is passed
  push(Hooks[orig][htype],getglobal(new));
  setglobal(orig,function(...) HookHandler(orig,arg); end);
end
-- same format as HookFunction
function UnHookFunction(orig,new,htype)
  if(not htype) then
    htype = "before";
  end
  local l,g;
  l = Hooks[orig][htype];
  g = getglobal(new);
  for key,value in l do
    if(value == g) then
      l[key] = nil;
      DebugPrint("found and unhooked "..new);
      return;
    end
  end
end

-- Tooltip Scanning and Changing Functions

-- Clears a tooltip for usage.
function ClearTooltip(TooltipNameBase)
	for i=1, 15, 1 do
		getglobal(TooltipNameBase.."TextLeft"..i):SetText("");
		getglobal(TooltipNameBase.."TextRight"..i):SetText("");
	end

end

-- 
-- Prints an items' text into the chat window. 
-- 
function PrintItemText( bag, slot, TooltipNameBase )
	if ( TooltipNameBase == nil ) then 
		TooltipNameBase = "CosmosTooltip";
	end

	local tooltip = getglobal (TooltipNameBase);
	
	--ClearToolTip(TooltipNameBase);
	tooltip:SetOwner( getglobal("UIParent"), "ANCHOR_CURSOR" );
	Sea.wow.tooltip.protectTooltipMoney();
	tooltip:SetBagItem( bag, slot );
	Sea.wow.tooltip.unprotectTooltipMoney();
	if( tooltip:IsVisible() ) then
		Print( "Visible tooltip" );
		tooltip:Hide();

		for i=1, tooltip:NumLines(), 1 do
			show = 0;
			str = "Left"..i..": ";
			str2 = getglobal(TooltipNameBase.."TextLeft"..i):GetText();
			if( str2 ~= nil ) then
				show = 1;
				str = str..str2;
			end
			str = str.." Right"..i..": ";
			str2 = getglobal(TooltipNameBase.."TextRight"..i):GetText();
			if( str2 ~= nil ) then
				show = 1;
				str = str..str2;
			end
			if( show == 1 ) then Print( str ); end
		end
	end
end

-- Gets all lines out of a tooltip.
function ScanTooltip(TooltipNameBase)
	if ( TooltipNameBase == nil ) then 
		TooltipNameBase = "CosmosTooltip";
	end
	
	local strings = {};
	for idx = 1, 10 do
		local textLeft = nil;
		local textRight = nil;
		ttext = getglobal(TooltipNameBase.."TextLeft"..idx);
		if(ttext and ttext:IsVisible() and ttext:GetText() ~= nil)
		then
			textLeft = ttext:GetText();
		end
		ttext = getglobal(TooltipNameBase.."TextRight"..idx);
		if(ttext and ttext:IsVisible() and ttext:GetText() ~= nil)
		then
			textRight = ttext:GetText();
		end
		if (textLeft or textRight)
		then
			strings[idx] = {};
			strings[idx].left = textLeft;
			strings[idx].right = textRight;
		end
	end
	
	return strings;
end

function GetItemName(bag, slot)
	local name = "";
	local strings = nil;
	if ( bag > -1 ) then
		strings = GetItemInfoStrings(bag, slot, "CosmosTooltip");
	else
		Sea.wow.tooltip.protectTooltipMoney();
		local hasItem, hasCooldown = CosmosTooltip:SetInventoryItem("player", slot);
		Sea.wow.tooltip.unprotectTooltipMoney();
		strings = ScanTooltip("CosmosTooltip");
		if ( not hasItem) then
			if ( strings[1] ) then
				strings[1].left = "";
			end
		end
	end
	-- Determine if the item is an ore, gem or herb
	if ( strings[1] ) then
		name = strings[1].left;
	end
	return name;
end

--Sets the tooltip relative to the owner in a position
--appropriate for where the owner is on the screen.
--owner - optional, the owner object, defaults to this
--tooltip, optional, the tooltip to set, defaults to GameTooltip
--setX, optional, sets the x offset(flipped based on screen corner)
--setY, optional, sets the y offset(flipped based on screen corner)
function SmartSetOwner(owner, tooltip, setX, setY)
 	if (not owner) then
 		owner = this;
 	end
 	if (not tooltip) then
 		tooltip = GameTooltip;
 	end
 	if (not setX) then
 		setX = 0;
 	end
 	if (not setY) then
 		setY = 0;
 	end
 	if (owner and (owner ~= UIParent)) then
		local x,y = owner:GetCenter();
		local left = owner:GetLeft();
		local right = owner:GetRight();
		local top = owner:GetTop();
		local bottom = owner:GetBottom();
		local screenWidth = UIParent:GetWidth();
		local screenHeight = UIParent:GetHeight();
		local scale = owner:GetScale();
		if (x~=nil and y~=nil and left~=nil and right~=nil and top~=nil and bottom~=nil and screenWidth>0 and screenHeight>0) then
			setX = setX * scale;
			setY = setY * scale;
			x = x * scale;
			y = y * scale;
			left = left * scale;
			right = right * scale;
			width = right - left;
			top = top * scale;
			bottom = bottom * scale;
			local anchorPoint = "";
			if (y <= (screenHeight * (1/2))) then
				top = top + setY;
				anchorPoint = "TOP";
				if (top < 0) then
					setY = setY - top;
				end
			else
				setY = -setY;
				bottom = bottom + setY;
				anchorPoint = "BOTTOM";
				if (bottom > screenHeight) then
					setY = setY + (screenHeight - bottom);
				end
			end
			
			if (x <= (screenWidth * (1/2))) then
				left = left + setX;
				if (anchorPoint == "BOTTOM") then
					anchorPoint = anchorPoint.."RIGHT";
					setX = setX - width;
					if (left < 0) then
						setX = setX - left;
					end
				else
					anchorPoint = anchorPoint.."LEFT";
					if (left < 0) then
						setX = setX - left;
					end
				end
			else
				setX = -setX;
				right = right + setX;
				if (anchorPoint == "BOTTOM") then
					anchorPoint = anchorPoint.."LEFT";
					setX = setX + width;
					if (right > screenWidth) then
						setX = setX - (right - screenWidth);
					end
				else
					anchorPoint = anchorPoint.."RIGHT";
					if (right > screenWidth) then
						setX = setX + (screenWidth - right);
					end
				end
			end
			
			if (anchorPoint == "") then
				anchorPoint = "TOPLEFT";
			end
			scale = tooltip:GetScale();
			if (scale) then
				setX = setX / scale;
				setY = setY / scale;
			end
			tooltip:SetOwner(owner, "ANCHOR_"..anchorPoint, setX, setY);
		end
	elseif (owner == UIParent) then
		local x,y = GetCursorPosition();
		local width = tooltip:GetWidth();
		local height = tooltip:GetHeifht();
		local screenWidth = UIParent:GetWidth();
		local screenHeight = UIParent:GetHeight();
		local scale = owner:GetScale();
		if (x~=nil and y~=nil and width and height and screenWidth>0 and screenHeight>0) then
			setX = setX * scale;
			setY = setY * scale;
			x = x * scale;
			y = y * scale;
			width = width * scale;
			height = height * scale;
			local anchorPoint = "";
			if (y <= (screenHeight * (1/2))) then
				local top = (y + height) + setY;
				if (top < 0) then
					setY = setY - top;
				end
			else
				setY = -setY;
				local bottom = (y - height) + setY;
				setY = setY - height;
				if (bottom > screenHeight) then
					setY = setY + (screenHeight - bottom);
				end
			end
			
			if (x <= (screenWidth * (1/2))) then
				local left = x + setX;
				setX = setX + (width / 2);
				if (left < 0) then
					setX = setX - left;
				end
			else
				setX = -setX;
				local right = x + setX;
				setX = setX - (width / 2);
				if (right > screenWidth) then
					setX = setX - (right - screenWidth);
				end
			end
			
			scale = tooltip:GetScale();
			if (scale) then
				setX = setX / scale;
				setY = setY / scale;
			end
			tooltip:SetOwner(owner, "ANCHOR_CURSOR", setX, setY);
		end
	else
		tooltip:SetOwner(owner, "ANCHOR_TOPLEFT");
	end
end

-- Obtains all information about a bag/slot and returns it as an array 
function GetItemInfoStrings(bag,slot, TooltipNameBase)
	if ( TooltipNameBase == nil ) then 
		TooltipNameBase = "CosmosTooltip";
	end

	ClearTooltip(TooltipNameBase);

	local tooltip = getglobal(TooltipNameBase);
	
	-- Open tooltip & read contents
	Sea.wow.tooltip.protectTooltipMoney();
	tooltip:SetBagItem( bag, slot );
	Sea.wow.tooltip.protectTooltipMoney();
	local strings = ScanTooltip(TooltipNameBase);

	-- Done our duty, send report
	return strings;
end

-- Item Classification
-- Thanks to Zatharas, Narands, Xdra, Mirodin and Reima for this list
HerbList = { 
	"Peacebloom", 
	"Silverleaf", 
	"Snakeroot", 
	"Magebloom", 
	"Bruiseweed", 
	"Swiftthistle",
	"Fadeleaf", 
	"Briarthorn", 
	"Kingsblood",
	"Wild Steelbloom",
	"Grave Moss", 
	"Liferoot",
	"Firebloom",
	"Goldthorn",
	"Stranglekelp",
	"Khadgar's Whiskers"
};
-- Baccarus for Truesilver
OreList = {
	"Copper Ore",
	"Tin Ore",
	"Silver Ore",
	"Mithril Ore",
	"Iron Ore",
	"Gold Ore",
	"Truesilver Ore",
	"Thorium Ore"
};
-- Melwein for Malachite
GemList = {
	"Malachite",
	"Pearl",
	"Moss Agate",
	"Tigerseye", 
	"Citrine",
	"Jade",
	"Shadowgem",
	"Aquamarine",
	"Blue Pearl",
	"Star Ruby"
};
-- Potion List ugh...
-- Thanks to Zatharas for linking holy spring water
PotionList = {
	"Potion",
	"Elixir",
	"Oil",
	"Holy Spring Water"
};
-- Tailoring List... ugh...
-- Thanks to Able for Silk (Spider etc)
TailorList = { 
	"Bolt",
	"Cloth",
	"Thread",
	"Dye",
	"Silk",
	"Bleach",
	"Magewave",
	"Runecloth",
	"Felcloth"
}
-- Leathercrafting ... ugh...
LeatherList = {
	"Leather",
	"Hide",
	"Scales"
};
-- Fishing 
FishingList = {
	"Bauble",
	"Nightcrawler",
	"Fishing Pole",
	"Fish Attractor"
};
-- Warlock Items
WarlockList = {
	"Soul Shard",
	"Healthstone",
	"Manastone",
	"Soulstone"
};
-- Shaman Items
ShamanList = {
	"Earth Totem",
	"Fire Totem",
	"Water Totem",
	"Air Totem",
	"Sapta"
};

-- And the worst ones: (Prepare for bad humor)

-- HP restoring items
FoodList = {
	"Bread", -- Boring but important first
	"Cornbread",
	"Haunch",
	"Mutton Chop",
	"Hog Shank",
	"Tough Jerky",
	"Stormwind Brie", -- What is Brie anyways?
	"Aged Cheddar",
	"Alterac Swiss",
	"Apple",
	"Watermelon",
	"Banana",
	"Ham Steak", -- I Love Ham Steak!
	"Dalaran Sharp", -- Ouch
	"Bleu", -- Ewwww Bleu cheese
	"Dwarven Mild", -- About time them dorfs got it right
	"Wolf Meat", -- Hawooooo
	"Roasted Boar Meat",
	"Mackerel", 
	"Smallfish",
	"Kaldorei Caviar",
	"Scorpid Surprise",
	"Beer Basted",
	"Smoked Bear Meat",
	"Roasted Kodo Meat",
	"Mud Snapper",
	"Rainbow Fin Albacore",
	"Halibut",
	"Strider Stew",
	"Fillet of Frenzy",
	"Boiled Clams",
	"Goretusk Liver Pie",
	"Loch Frenzy Delights",
	"Coyote Steak",
	"Blood Sausage",
	"Westfall Stew",
	"Crab Cake",
	"Crispy Lizard",
	"Pork Ribs",
	"Croclist Steak",
	"Savory Deviate",
	"Scorpid Surprise",
	"Cooked Crab Claw",
	"Dig Rat Stew",
	"Murloc Fin Soup",
	"Clam Chowder",
	"Seasoned Wolf",
	"Spider Cake",
	"Bear Steak",
	"Venison",
	"Pork Ribs",
	"Gumbo",
	"Lion Chops",
	"Goblin Deviled",
	"Omelet",
	"Tasty Lion Steak",
	"Barbecued Buzzard",
	"Giant Clam Scorcho",
	"Soothing Turtle",
	"Rockscale Cod",
	"Cave Mold",
	"eckled Mushroom",
	"Mushroom Cap",
	"Bolete",
	"Morel",
	"Truffle",
	"Strider Stew",
	"Mystery Stew",
	"Roast Raptor",
	"Carrion Surprise",
	"Dragonbreath Chi",
	"Spiced Chi",
	"Monster Om",
	"Spiced Wolf",
	"Goldthorn Tea",
	"Squid",
	"Pumpkin",
	"Ice Cream" -- Tigule's specialty
};
-- Mana restoring items
DrinkList = {
	"Cherry Grog", -- The ONE AND ONLY BABY!	
	"Moonberry Juice",
	"Cold Milk",
	"Melon Juice",
	"Sweet Nectar",
	"Spring Water",
	"Glory Dew"
};

-- Checks if any item from a list is in a specified word

function IsListItemInWord(list, word) 
	for k,v in list do
		if ( string.find(word,v) ~= nil ) then
			return true;
		end
	end
	return false;
end

-- This function obtains all the information you could ever want about an item... 
--  ... if its available.
--
--  classification, name, quality, itemCount, leftString, rightString, minLevel, unique, soulbound, bindsOnPickup, bindsOnEquip, questItem;
function ClassifyItem(bag, slot, TooltipNameBase)
	if ( TooltipNameBase == nil ) then 
		TooltipNameBase = "CosmosTooltip";
	end
	
	local strings = GetItemInfoStrings(bag, slot, TooltipNameBase);
	local classification = "Misc";
	local leftString, rightString, minLevel;
	local unique = false;
	local soulbound = false;
	local bindsOnPickup = false;
	local bindsOnEquip = false;
	local questItem = false;
	local isHerb = false;
	local isGem = false;
	local isOre = false;
	local isLeather = false;
	local isTailor = false;
	local isFishing = false;
	local isFood = false;
	local isDrink = false;
	local isShaman = false;
	local isWarlock = false;
	local isJunk = false;
	local engineering = false;
	local firstaid = false;
	local classitem = false;
	local class = "";
	local texture, itemCount, locked, quality = GetContainerItemInfo(bag,slot);
	local name = "";
	-- Look for the target line that identifies an item; it'll either be line 2, 3, or 4.

	-- Determine if the item is an ore, gem or herb
	if ( strings[1] ) then
		name = strings[1].left;
	end
	
	isHerb = IsListItemInWord(HerbList, name);
	isGem  = IsListItemInWord(GemList, name);
	isOre  = IsListItemInWord(OreList, name);
	isLeather = IsListItemInWord(LeatherList, name);
	isFishing = IsListItemInWord(FishingList, name);
	isTailor = IsListItemInWord(TailorList, name);
	isPotion = IsListItemInWord(PotionList, name);
	isFood = IsListItemInWord(FoodList, name);
	isDrink = IsListItemInWord(DrinkList, name);
	isShaman = IsListItemInWord(ShamanList, name);
	isWarlock = IsListItemInWord(WarlockList, name);

	if ( ( quality ) and ( quality < 0 ) ) then 
		isJunk = true;
	end

	for i = 2, 5, 1 do
		if (not strings[i])
		then
			break;
		end
		if (strings[i].left == ITEM_UNIQUE or strings[i].left == ITEM_UNIQUE_MULTIPLE ) then unique = true; end
		if (strings[i].left == ITEM_SOULBOUND ) then soulbound = true; end
		if (strings[i].left == ITEM_BIND_ON_PICKUP ) then bindsOnPickup = true; end
		if (strings[i].left == ITEM_BIND_ON_EQUIP ) then bindsOnEquip = true; end
		if (strings[i].left == ITEM_BIND_QUEST ) then questItem = true; end

		if (string.find(strings[i].left,"First Aid", 0, true ) ) then firstaid = true; end
		if (string.find(strings[i].left,"Engineering", 0, true ) ) then engineering = true; end
		if (string.find(strings[i].left,"Classes:", 0, true ) ) then classitem = true; class = string.sub(strings[i].left, string.find(strings[i].left,":", 0, true )+2); end
		
		if (strings[i].left and
			strings[i].left ~= ITEM_UNIQUE and
			strings[i].left ~= ITEM_SOULBOUND and
			strings[i].left ~= ITEM_BIND_ON_EQUIP and
			strings[i].left ~= ITEM_BIND_ON_PICKUP and
			strings[i].left ~= ITEM_BIND_QUEST)
		then
			leftString = strings[i].left;
			rightString = strings[i].right;
			break;
		end
	end
	
	-- Find last line
	local lastLine;
	for i = 1, 10, 1 do
		if (strings[i] and strings[i].left)
		then
			lastLine = strings[i].left;
		else
			break;
		end
	end

	-- look at last line to see if it's a level requirement
	local minLevel = 0;
	if (lastLine)
	then
		local index, length, levelString = string.find(lastLine, "^Requires Level (%d+)$");
		if (index)
		then
			minLevel = MakeIntFromString(levelString);
		end
	end
	
	-- classify item based on found strings
	if (leftString)
	then
		if (leftString == "Main Hand" or
			leftString == "Two-Hand" or
			leftString == "One-Hand")
		then
			classification = "Weapon";
		elseif (leftString == "Head" or
			leftString == "Hand" or
			leftString == "Waist" or
			leftString == "Shoulders" or
			leftString == "Legs" or
			leftString == "Back" or
			leftString == "Feet" or
			leftString == "Chest" or
			leftString == "Wrist")
		then
			classification = "Armor";
		elseif (leftString == "Off Hand")
		then
			classification = "Shield";
		elseif (leftString == "Wand" or
			leftString == "Ranged" or
			leftString == "Gun" )
		then
			classification = "Ranged";
		elseif (leftString == "Projectile") 
		then
			classification = "Ammo";
		elseif (leftString == "Shirt" or
			leftString == "Tabard" or
			leftString == "Finger" or
			leftString == "Neck" or
			leftString == "Trinket" or
			leftString == "Held In Hand")
		then
			classification = "Clothing";
		end
	end
	if ( classification == "Misc" ) then
		if ( isGem ) then classification = "Gem"; end
		if ( isOre ) then classification = "Ore"; end
		if ( isHerb ) then classification = "Herb"; end
		if ( engineering ) then classification = "Engineering"; end
		if ( firstaid ) then classification = "First Aid"; end
		if ( isLeather ) then classification = "Leather"; end
		if ( isTailor ) then classification = "Thread"; end
		if ( isPotion ) then classification = "Potion"; end
		if ( isFishing ) then classification = "Fishing"; end
		if ( isFood ) then classification = "Food"; end
		if ( isDrink ) then classification = "Drink"; end
		if ( isShaman ) then classification = "Shaman"; end
		if ( isWarlock ) then classification = "Warlock"; end
		if ( classitem ) then classification = class; end
		if ( questItem ) then classification = "QuestItem"; end
		if ( isJunk ) then classification = "Junk"; end
	end 

	return classification, name, quality, itemCount, leftString, rightString, minLevel, unique, soulbound, bindsOnPickup, bindsOnEquip, questItem;
end

-- String Functions

function MakeIntFromString(str)
	DebugPrint("MakeIntFromString("..str..")", 4);
	local remain = str;
	local amount = 0;
	while (remain ~= "") do
		amount = amount * 10;
		amount = amount + (string.byte(strsub(remain, 1, 1)) - string.byte("0"));
		remain = strsub(remain, 2);
	end
	DebugPrint("MakeIntFromStr("..str..") = "..amount, 4);
	return amount;
end

function MakeIntFromHexString(str)
	DebugPrint("MakeIntFromHexString("..str..")", 4);
	local remain = str;
	local amount = 0;
	while (remain ~= "") do
		amount = amount * 16;
		local byteVal = string.byte(strupper(strsub(remain, 1, 1)));
		if (byteVal >= string.byte("0") and byteVal <= string.byte("9"))
		then
			amount = amount + (byteVal - string.byte("0"));
		elseif (byteVal >= string.byte("A") and byteVal <= string.byte("F"))
		then
			amount = amount + 10 + (byteVal - string.byte("A"));
		end
		remain = strsub(remain, 2);
	end
	DebugPrint("MakeIntFromHexStr("..str..") = "..amount, 4);
	return amount;
end

function MakeHexStringFromInt(intval, minlength)
	return string.format("%"..minlength.."x", intval );
end

function EscapeString(plainString, disallowedChars)
	-- yay URL-encoding
	local str = "";
	local remain = plainString;
	disallowedChars = disallowedChars.."%";
	while (remain ~= "") do
		local char = strsub(remain, 1, 1);
		if (string.find(disallowedChars, char, 1, true))
		then
			str = str.."%";
			local hexRepresentation = string.format("%02x", string.byte(char));
			str = str..hexRepresentation;
		else
			str = str..char;
		end
		remain = strsub(remain, 2);
	end
	return str;
end

function UnescapeString(escapedString)
	local str = "";
	local remain = escapedString;
	while (remain ~= "") do
		local char = strsub(remain, 1, 1);
		if (char == "%")
		then
			str = str..string.char(MakeIntFromHexString(strsub(remain, 2, 3)));
			remain = strsub(remain, 4);
		else
			str = str..char;
			remain = strsub(remain, 2);
		end
	end
	return str;
end

-- Converts numeric time into readable time.
function GetTimeString(time)
	if (time < 0)
	then
		time = 0;
	end

	local seconds = mod(floor(time), 60);
	if (seconds < 10)
	then
		seconds = "0"..seconds;
	end
	local minutes = mod(floor(time/60), 60);
	local hours = floor(time/(60*60));
	
	local timeString;
	if (hours > 0)
	then
		if (minutes < 10)
		then
			minutes = "0"..minutes;
		end
		timeString = hours..":"..minutes..":"..seconds;
	else
		timeString = minutes..":"..seconds;
	end
	return timeString;
end


function GetRGBAFromHexColor(hexColor)
	local alpha = MakeIntFromHexString(strsub(hexColor, 1, 2)) / 256;
	local red = MakeIntFromHexString(strsub(hexColor, 3, 4)) / 256;
	local green = MakeIntFromHexString(strsub(hexColor, 5, 6)) / 256;
	local blue = MakeIntFromHexString(strsub(hexColor, 7, 8)) / 256;
	return red, green, blue;
end		

-- Mathematical Functions (yuck!)
function CheckBits(field, bits)
	local result = true;
	while (bits > 0) do
		if (mod(bits, 2) > 0)
		then
			if (mod(field, 2) == 0)
			then
				result = false;
				break;
			end
		end
		field = floor(field / 2);
		bits = floor(bits / 2);
	end
	return result;
end


function SetBit(field, bit)
	local source = 2^31; -- 1 in highest-order bit
	local shiftCount = 1;
	local result = 0;
	while (bit > 0) do
		result = floor(result / 2);
		-- if we're about to shift off a 1, put a 1 on the destination.
		if ((mod(bits, 2) == 1) or (mod(field, 2) == 1))
		then
			result = result + source;
		end
		field = floor(field / 2);
		bits = floor(bits / 2);
		shiftCount = shiftCount + 1;
	end
	for i = shiftCount, 32, 1 do
		result = floor(result / 2);
		-- if we're about to shift off a 1, put a 1 on the destination.
		if (mod(bits, 2) == 1)
		then
			result = result + source;
		end
		field = floor(field / 2);
	end
	return result;
end

function ClearBit(field, bit)
	local source = 2^31; -- 1 in highest-order bit
	local shiftCount = 1;
	local result = 0;
	while (bit > 0) do
		result = floor(result / 2);
		-- if we're about to shift off a 1, put a 1 on the destination. AS LONG AS this is not the bit to clear
		if ((mod(field, 2) == 1) and not (mod(bit, 2) == 1))
		then
			result = result + source;
		end
		field = floor(field / 2);
		bits = floor(bits / 2);
		shiftCount = shiftCount + 1;
	end
	for i = shiftCount, 32, 1 do
		result = floor(result / 2);
		-- if we're about to shift off a 1, put a 1 on the destination.
		if (mod(bits, 2) == 1)
		then
			result = result + source;
		end
		field = floor(field / 2);
	end
	return result;
end

function UnitFaction(who)
	local faction;
	local race = UnitRace(who);
	local HordeRaces = {"Orc", "Tauren", "Troll", "Undead" };

	for index, hordeRace in HordeRaces do
		if (race == hordeRace)
		then
			return 1; -- Horde
		end
	end
	return 0; -- Alliance
end

function BaseConversion(input, inputBase, outputBase)
-----------------------------------------------------------
--               Function made by KaTTaNa !              -- 
--               --------------------------              --
--   http://www.wc3sear.ch/index.php?p=JASS&ID=37&sid=   --
--               --------------------------              --
--               Converted in LUA by vjeux               --
--                                                       --
-- Usage : BaseConversion(255, 10, 16)                   --
-- => Return "ff"                                        --
--                                                       --
-- Usage : BaseConversion("ff", 16, 10)                  --
-- => Return "10"                                        --
-----------------------------------------------------------
    local charMap = "0123456789abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+-=[]";
    local s;
    local result = "";
    local val = 0;
    local i;
    local p = 0;
    local pow = 1;
    local sign = "";

    if ( inputBase < 2 or inputBase > strlen(charMap) or outputBase < 2 or outputBase > strlen(charMap) ) then
        -- Bases are invalid or out of bounds
        return "Invalid bases given";
    end
    if ( strsub(input, 1, 1) == "-" ) then
        sign = "-";
        input = strsub(input, 1, strlen(input));
    end
    i = strlen(input);
    -- Get the integer value of input
    while (i > 0) do
        s = strsub(input, i, i);
        p = 0;
		local bool = false;
        while (bool == false) do
            if ( p >= inputBase ) then
                -- Input cannot match base
                return "Input does not match base!\n P = "..p;
            end
			if ( s == strsub(charMap, p+1, p+1) ) then
                val = val + pow * p;
                pow = pow * inputBase;
                bool = true;
            end
            p = p + 1;
        end
        i = i - 1;
    end
    while (val > 0) do
        p = mod(val, outputBase);
        result = strsub(charMap, p+1, p+1)..result;
        val = val / outputBase;
    end

	for i = 1, strlen(result), 1 do
		if (strsub(result, 1, 1) == "0") then
			result = strsub(result, 2, strlen(result));
		else
			return sign..result
		end
	end

	if (strlen(sign..result) == 0) then 
		return "0";
	else
		return sign..result.."-"..strlen(sign..result);
	end
end


-- Thott's personalized functions
function dbanner(...)
  if ( (Thottbot) and (Thottbot.Debug) ) then
    UIErrorsFrame:AddMessage(join(arg,""), 0.9, 0.9, 0.0, 1.0, UIERRORS_HOLD_TIME);			  					
    dprint(join(arg,""));
  end
end
function banner(...)
  UIErrorsFrame:AddMessage(join(arg,""), 0.9, 0.9, 0.0, 1.0, UIERRORS_HOLD_TIME);			  					
end
function dbyte(c)
  return string.format("<%02X>",string.byte(c));
end
--function dprint_runqueue()
--  if(dprint_nextmsg) then
--    print2(dprint_nextmsg);
--    dprint_nextmsg = nil;
--  end
--  if(dprint_queue and dprint_queue[1]) then
--    local msg = table.remove(dprint_queue,1);
--    dprint_nextmsg = msg;
--    msg = string.gsub(msg,"([^%w%s])",dbyte)
--    print2("Next: ",msg);
--    Cosmos_Schedule(0.01,dprint_runqueue);
--    dprint_queue_scheduled = true;
--  else
--    dprint_queue_scheduled = false;
--  end
--end
function dprint(...)
  if ( ( Thottbot ) and (Thottbot.Debug) ) then
    local msg = join(arg,"");
    msg = string.gsub(msg,"|","<pipe>");
    msg = string.gsub(msg,"([^%w%s%a%p])",dbyte);
    if(Thottbot.DebugFrame) then
      printframe(Thottbot.DebugFrame,msg);
    else
      print2(msg);
    end
--    if(not dprint_queue) then
--      dprint_queue = {};
--    end
--    table.insert(dprint_queue,join(arg,""));
--    if(not dprint_queue_scheduled) then
--      dprint_queue_scheduled = true;
--      Cosmos_Schedule(0.01,dprint_runqueue);
--      -- 68.94.173.94
--    end
  end
end
--    Thottbot.PrintCount = Thottbot.PrintCount + 1;
--    if(Thottbot.PrintCount < 60) then
--      for key,value in arg do
--        arg[key] = string.gsub(value,"[^%w%s%p]",".");
--      end
--      print2(Thottbot.PrintCount,":",join(arg,""));
function dprint1(...)
  if ( (Thottbot) and (Thottbot.Debug) ) then
    print1(join(arg,""));
  end
end
function split(s,seperator)
  local t = {};
  t.n = 0;
  for value in string.gfind(s,"[^"..seperator.."]+") do
    t.n = t.n + 1;
    t[t.n] = value;
  end
  return t;
end
function join(list,seperator)
  local i;
  local c = "";
  local msg = "";
  if(type(list) ~= "table") then
    dbanner("unknown type ",type(list)," table passed to join, seperator ",seperator);
    return;
  end
  if(not list.n) then
    dbanner("ERROR: no .n variable in list!");
    return "";
  end
  for i=1, list.n, 1 do
    if(list[i]) then
      if(type(list[i]) ~= "string" and type(list[i]) ~= "number") then
        dbanner("found ",type(list[i])," in list!");
        msg = msg .. c .. "(" .. type(list[i]) .. ")";
      else
	msg = msg .. c .. list[i];
      end
    else
      msg = msg .. c .. "(nil)";
    end
    c = seperator;
  end
  return msg;
end
function dprintlist(list)
  if ( (Thottbot) and (Thottbot.Debug) ) then
    if(list) then
      dprint(join(list,","));
    else
      dprint("nil list");
    end
  end
end
function dprintcomma(...)
  dprint(join(arg,","));
end
function printframe(frame,...)
  if(frame) then
    frame:AddMessage(join(arg,""), 1.0, 1.0, 0.0);
  end
end
function print2(...)
  if(ChatFrame2) then
    ChatFrame2:AddMessage(join(arg,""), 1.0, 1.0, 0.0);
  end
end
function print1(...)
  if(ChatFrame1) then
    ChatFrame1:AddMessage(join(arg,""), 1.0, 1.0, 0.0);
  end
end
function push(t,v)
  if(not t or not t.n) then
    dbanner("Bad table passed to push");
    return nil;
  end
  t.n = t.n+1;
  t[t.n] = v;
end
function pop(t)
  if(not t or not t.n) then
    dbanner("Bad table passed to push");
    return nil;
  end
  local v = t[t.n];
  t.n = t.n - 1;
  return v;
end
function push2(t,x,y)
  if(not t or not t.n) then
    dbanner("Bad table passed to push2");
    return nil;
  end
  t.n = t.n+1;
  t[t.n] = x;
  t.n = t.n+1;
  t[t.n] = y;
end
function pop2(t)
  if(not t or not t.n) then
    --dbanner("Bad table passed to pop2");
    return nil;
  end
  if(t.n < 2) then
    return nil;
  end
  local tt = {};
  tt.n = 2;
  tt[1] = t[t.n-1];
  tt[2] = t[t.n];
  t.n = t.n - 2;
  return tt;
end
function fixnil(...)
  for i=1, arg.n, 1 do
    if(not arg[i]) then
      arg[i] = "(nil)";
    end
  end
  return arg;
end
function fixnilempty(...)
  for i=1, arg.n, 1 do
    if(not arg[i]) then
      arg[i] = "";
    end
  end
  return arg;
end
function fixnilzero(...)
  for i=1, arg.n, 1 do
    if(not arg[i]) then
      arg[i] = 0;
    end
  end
  return arg;
end
function HookHandler(name,arg)
  --dprint("HookHandler ",name);
  local called = false;
  local continue = true;
  local retval;
  for key,value in Hooks[name].hide do
    if(type(value) == "function") then
      --dprint("calling before ",name);
      if(not value(unpack(arg))) then
        continue = false;
      end
      called = true;
    end
  end
  if(not continue) then
    dprint("hide returned false, aborting call to ",name);
    return;
  end
  for key,value in Hooks[name].before do
    if(type(value) == "function") then
      --dprint("calling before ",name);
      value(unpack(arg));
      called = true;
    end
  end
  --dprint("calling original ",name);
  retval = Hooks[name].orig(unpack(arg));
  for key,value in Hooks[name].after do
    if(type(value) == "function") then
      --dprint("calling after ",name);
      value(unpack(arg));
      called = true;
    end
  end
  if(not called) then
    dprint("no hooks left for ",name,", clearing");
    setglobal(name,Hooks[name].orig);
    Hooks[name] = nil;
  end
  return retval;
end

-- Thanks to George Warner for suggesting this refactoring suggestion... as always, I modified it a bit :) /sarf
-- Source : http://www.cosmosui.org/cgi-bin/bugzilla/show_bug.cgi?id=158
function Cosmos_IsCosmosUser(name)
	if ( not name ) then return false; end
	if ( CosmosMaster_CosmosUsers ) then
		if ( CosmosMaster_CosmosUsers[name] ) then 
			return true;
		end
	end
	return false;
end

-- helper function - returns value as a byte
function Cosmos_GetByteValue(pValue)
	local value = tonumber(pValue);
	if ( value <= 0 ) then return 0; end
	if ( value >= 255 ) then return 255; end
	return value;
end


-- Yet another function from George Warner, modified a bit to fit my own nefarious purposes. 
-- It can now accept r, g and b specifications, too (leaving out a), as well as handle 255 255 255
-- Source : http://www.cosmosui.org/cgi-bin/bugzilla/show_bug.cgi?id=159
function Cosmos_GetColorFormatString(a, r, g, b)
	local percent = false;
	if ( ( ( not b ) or ( b <= 1 ) ) and ( a <= 1 ) and ( r <= 1 ) and ( g <= 1) ) then percent = true; end
	if ( ( not b ) and ( a ) and ( r ) and ( g ) ) then b = g; g = r; r = a; if ( percent ) then a = 1; else a = 255; end end
	if ( percent ) then a = a * 255; r = r * 255; g = g * 255; b = b * 255; end
	a = Cosmos_GetByteValue(a); r = Cosmos_GetByteValue(r); g = Cosmos_GetByteValue(g); b = Cosmos_GetByteValue(b);
	
	return format("|c%02X%02X%02X%02X%%s|r", a, r, g, b);
end


-- global localization string functions START

function loc_format(...)
  ret = arg[1];
  for i=2, arg.n, 1 do
    ret = string.gsub (ret, "<"..(i-1)..">", arg[i]);
  end
  return ret;
end

function loc_read(str, fmt, ord, n)
  local ret = {};
  fmt1 = string.gsub (fmt, "%b<>", "(.+)");
  for i=1, n, 1 do
    ret[ord[i]] = string.gsub(str, fmt1, "%"..i);
  end
  return unpack(ret);
end

-- global localization string functions END

--Returns true if variable is not nill and a table
function isTable(var)
	if (var and (type(var) == "table")) then
		return true;
	end
	return false;	
end

function MakeHyperLink(type, name, color)
	local link = "["..name.."]";
	if (color) then
		link = "|cFF"..color.."["..name.."]|r";
	end
	return "|H"..type.."|h"..link.."|h";
end

