tinsert(UISpecialFrames, "CT_ExpenseHistoryFrame");
CT_EH_History = { };
CT_EH_DISPLAYTHRESHOLD = 100; -- 1 Silver
CT_EH_NUMCLASSPILES = 8;
CT_EH_Version = 1.2;
CT_EH_LogSort = {
	["curr"] = 1,
	["way"] = 1 -- Desc
};

function CT_EH_Sort(t1, t2)
	if ( t1 and t2 ) then
		return t1[1] >= t2[1]
	end
end

function CT_EH_SortLogBy(id)
	if ( CT_EH_LogSort["curr"] == id ) then
		CT_EH_LogSort["way"] = abs(CT_EH_LogSort["way"]-1);
	else
		CT_EH_LogSort["curr"] = id;
		CT_EH_LogSort["way"] = 1;
	end
	CT_EH_UpdateLog();
end
function CT_EH_UpdateSummary()
	local _, _, currPlayer, currServer = string.find( CT_ExpenseHistoryFrame.currPlayer or "", "^(.+)@(.+)$");
	if ( currPlayer ) then
		currPlayer = currPlayer .. " @ " .. currServer;
	end
	UIDropDownMenu_SetSelectedName(CT_ExpenseHistoryFrameDropDown, ( currPlayer or CT_EH_ALLCHARACTERS ) );
	UIDropDownMenu_SetWidth(200, CT_ExpenseHistoryFrameDropDown);
	CT_ExpenseHistoryFrameDropDownText:SetText(( currPlayer or CT_EH_ALLCHARACTERS ));
	CT_ExpenseHistoryFrameRecordingText:SetText(format(CT_EH_RECORDINGFROM, date("%m/%d/%Y", CT_EH_History["startUsage"])));
	
	local classCoords = {
		["WARRIOR"]	= {0, 0.25, 0, 0.25},
		["MAGE"]	= {0.25, 0.49609375, 0, 0.25},
		["ROGUE"]	= {0.49609375, 0.7421875, 0, 0.25},
		["DRUID"]	= {0.7421875, 0.98828125, 0, 0.25},
		["HUNTER"]	= {0, 0.25, 0.25, 0.5},
		["SHAMAN"]	= {0.25, 0.49609375, 0.25, 0.5},
		["PRIEST"]	= {0.49609375, 0.7421875, 0.25, 0.5},
		["WARLOCK"]	= {0.7421875, 0.98828125, 0.25, 0.5},
		["PALADIN"]	= {0, 0.25, 0.5, 0.75}
	};
	local costsTable, totalRepair, numRepairs, totalFlight, totalReagent, totalAmmo, totalMail, totalCost, highCost = CT_EH_GetStats(CT_ExpenseHistoryFrame.currPlayer);
	local allCostsTable, _, _, _, _, _, _, _, allHighCost = CT_EH_GetStats(nil);
	table.sort(
		allCostsTable,
		function(a1, a2)
			if ( a1 and a2 ) then
				return a1[1] > a2[1];
			end
		end
	);
	
	-- Summaries
	local numDays = ( time() - CT_EH_History["startUsage"] ) / (24*3600);
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryAverageRepairMoney", floor(totalRepair/numRepairs+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryAvgExpensesPerDayMoney", floor(totalCost/numDays+0.5) );
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryTotalCostMoney", floor(totalCost+0.5));
	
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryAvgExpensesFlightsMoney", floor(totalFlight/numDays+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryAvgExpensesRepairsMoney", floor(totalRepair/numDays+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryAvgExpensesReagentsMoney", floor(totalReagent/numDays+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryAvgExpensesAmmoMoney", floor(totalAmmo/numDays+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryAvgExpensesMailMoney", floor(totalMail/numDays+0.5));
	
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryTotalCostFlightsMoney", floor(totalFlight+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryTotalCostRepairsMoney", floor(totalRepair+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryTotalCostReagentsMoney", floor(totalReagent+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryTotalCostAmmoMoney", floor(totalAmmo+0.5));
	MoneyFrame_Update("CT_ExpenseHistoryFrameSummaryTotalCostMailMoney", floor(totalMail+0.5));
	
	local i = 0;
	CT_ExpenseHistoryFrame.numPiles = 0;
	for playerIndex, val in allCostsTable do
		if ( val[1] >= CT_EH_DISPLAYTHRESHOLD ) then
			i = i + 1;
			if ( i <= CT_EH_NUMCLASSPILES ) then
				local v, formattedCost, class, playerName = val[1], val[2], val[3], val[4];
				local height = 120*(v/allHighCost);
				if ( height < 2 ) then
					height = 2; -- So we can at least see the bar
				end
				CT_ExpenseHistoryFrame.numPiles = i;
				getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i).name = playerName;
				getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i):Show();
				if ( not CT_ExpenseHistoryFrame.isAnimated ) then
					getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "Pile"):SetHeight(height);
				end
				getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "Pile").goalHeight = height;
				getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "PileNumber"):SetText(formattedCost);
				getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "Texture"):SetTexCoord(classCoords[class][1], classCoords[class][2], classCoords[class][3], classCoords[class][4]);
				getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "PileBackground"):SetVertexColor(RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b);
			end
		end
	end
	for i = i+1, CT_EH_NUMCLASSPILES, 1 do
		getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i):Hide();
	end
end

function CT_EH_UpdateLog()
	local entries = { };
	for k, v in CT_EH_History do
		if ( type(v) == "table" ) then
			for key, val in v do
				if ( type(val) == "table" ) then
					tinsert(entries, { val[3], k, val[2], val[1] });
				end
			end
		end
	end
	table.sort(
		entries,
		function(a1, a2)
			if ( a1 and a2 ) then
				if ( CT_EH_LogSort["way"] == 1 ) then
					if ( a1[CT_EH_LogSort["curr"]] == a2[CT_EH_LogSort["curr"]] ) then
						return a1[1] > a2[1];
					else
						return a1[CT_EH_LogSort["curr"]] > a2[CT_EH_LogSort["curr"]];
					end
				else
					if ( a1[CT_EH_LogSort["curr"]] == a2[CT_EH_LogSort["curr"]] ) then
						return a1[1] < a2[1];
					else
						return a1[CT_EH_LogSort["curr"]] < a2[CT_EH_LogSort["curr"]];
					end
				end
			end
		end
	);
	local numEntries = getn(entries);
	FauxScrollFrame_Update(CT_ExpenseHistoryFrameLogScrollFrame, numEntries, 22, 20);
	for i=1, 22, 1 do
		local line = getglobal("CT_ExpenseHistoryFrameLogLine" .. i);
		local dateText = getglobal("CT_ExpenseHistoryFrameLogLine" .. i .. "Date");
		local charText = getglobal("CT_ExpenseHistoryFrameLogLine" .. i .. "Char");
		local typeText = getglobal("CT_ExpenseHistoryFrameLogLine" .. i .. "Type");
		local costFrameName = "CT_ExpenseHistoryFrameLogLine" .. i .. "Cost";

		local index = i + FauxScrollFrame_GetOffset(CT_ExpenseHistoryFrameLogScrollFrame); 
		if ( index <= numEntries ) then
			local iStart, iEnd, charName, serverName = string.find(entries[index][2], "^(.+)@(.+)$");
			if ( strlen(charName)+strlen(serverName) > 16 ) then
				-- We have to cut it down
				if ( strlen(serverName) < 8 ) then
					charName = strsub(charName, 0, 16-strlen(serverName));
				elseif ( strlen(charName) < 8 ) then
					serverName = strsub(serverName, 0, 16-strlen(charName));
				else
					charName = strsub(charName, 0, 8);
					serverName = strsub(serverName, 0, 8);
				end
			end
			line:Show();
			dateText:SetText(date("%m/%d/%y", entries[index][1]));
			charText:SetText(charName .. "@" .. serverName);
			typeText:SetText(entries[index][3]);
			MoneyFrame_Update(costFrameName, entries[index][4]);
		else
			line:Hide();
		end
	end
end

function CT_EH_GetStats(player)
	local classFileNames = {
		[CT_EH_WARRIOR] = "WARRIOR",
		[CT_EH_MAGE] = "MAGE",
		[CT_EH_ROGUE] = "ROGUE",
		[CT_EH_DRUID] = "DRUID",
		[CT_EH_HUNTER] = "HUNTER",
		[CT_EH_SHAMAN] = "SHAMAN",
		[CT_EH_PRIEST] = "PRIEST",
		[CT_EH_WARLOCK] = "WARLOCK",
		[CT_EH_PALADIN] = "PALADIN"
	};
	local costs, totalRepair, numRepair, totalFlight, totalReagents, totalAmmo, totalMail, totalCost, highCost = { }, 0, 0, 0, 0, 0, 0, 0, 0;
	for key, tbl in CT_EH_History do
		if ( ( key == player or not player ) and type(tbl) == "table" ) then
			local total, totalRep, numRep, totalFli, totalReg, totalAmm, totalMai = CT_EH_GetMoney(key);
			local _, _, playerName, server = string.find(key, "^(.+)@(.+)$");
			local formattedCost;
			if ( total < 100 ) then
				formattedCost = total .. "c";
			elseif ( total < (100*100) ) then
				formattedCost = floor(total/100) .. "s";
			else
				formattedCost = floor(total/(100*100)) .. "g";
			end
			if ( ( total or 0 ) > highCost ) then
				highCost = total;
			end
			totalRepair = totalRepair + totalRep;
			numRepair = numRepair + numRep;
			totalFlight = totalFlight + totalFli;
			totalReagents = totalReagents + totalReg;
			totalAmmo = totalAmmo + totalAmm;
			totalMail = totalMail + totalMai;
			totalCost = totalCost + total;
			tinsert(costs, { total, formattedCost, classFileNames[CT_EH_History[key].class], playerName .. " @ " .. server });
		end
	end
	return costs, totalRepair, numRepair, totalFlight, totalReagents, totalAmmo, totalMail, totalCost, highCost;
end

function CT_EH_StartAnimate()
	for i = 1, CT_ExpenseHistoryFrame.numPiles, 1 do
		local pile = getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "Pile");
		if ( pile:GetParent():IsVisible() ) then
			CT_ExpenseHistoryFrame.isAnimated = 1;
			getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "Pile"):SetHeight(0);
		end
	end
end

function CT_EH_ProcessAnimation(elapsed)
	this.elapsed = this.elapsed - elapsed;
	if ( this.elapsed <= 0 ) then
		if ( this.isAnimated ) then
			local keepAnimating = false;
			for i = 1, CT_ExpenseHistoryFrame.numPiles, 1 do
				local pile = getglobal("CT_ExpenseHistoryFrameSummaryDiagramClass" .. i .. "Pile");
				local height = pile:GetHeight();
				if ( ( height or 0 ) < pile.goalHeight ) then
					pile:SetHeight(height+(1*((0.017-this.elapsed)/0.017)));
					keepAnimating = true;
				end
			end
			if ( not keepAnimating ) then
				CT_ExpenseHistoryFrame.isAnimated = nil;
			end
		end
		this.elapsed = 0.017;
	end
end

function CT_EH_GetMoney(name)
	local total, totalRep, numRep, totalFli, totalReg, totalAmm, totalMai = 0, 0, 0, 0, 0, 0, 0;
	if ( name and CT_EH_History[name] ) then
		for k, v in CT_EH_History[name] do
			if ( k ~= "class" and k ~= "key" ) then
				total = total + v[1];
				if ( v[2] == CT_EH_REPAIR ) then
					totalRep = totalRep + v[1];
					numRep = numRep + 1;
				elseif ( v[2] == CT_EH_FLIGHT ) then
					totalFli = totalFli + v[1];
				elseif ( v[2] == CT_EH_REAGENT ) then
					totalReg = totalReg + v[1];
				elseif ( v[2] == CT_EH_AMMO ) then
					totalAmm = totalAmm + v[1];
				elseif ( v[2] == CT_EH_MAIL ) then
					totalMai = totalMai + v[1];
				end
			end
		end
	end
	return total, totalRep, numRep, totalFli, totalReg, totalAmm, totalMai;
end

function CT_EH_OnShow()
	PlaySound("UChatScrollButton");
	PanelTemplates_SetTab(CT_ExpenseHistoryFrame, 1);
	CT_ExpenseHistoryFrameSummary:Show();
	CT_ExpenseHistoryFrameLog:Hide();
	CT_EH_UpdateSummary();
	CT_EH_StartAnimate();
end

function CT_EH_OnEvent(event)
	if ( event == "PLAYER_ENTERING_WORLD" ) then
		local player = UnitName("player") .. "@" .. GetCVar("realmName");
		-- Initialize
		if ( not CT_EH_History["startUsage"] ) then
			CT_EH_History["startUsage"] = time();
		elseif ( not CT_EH_History["version"] ) then
			-- We used version 1.0 previously
			CT_EH_History["startUsage"] = CT_EH_History["startUsage"] * (24*3600);
		end
		CT_EH_History["version"] = CT_EH_Version;
		if ( not CT_EH_History[player] ) then
			CT_EH_History[player] = { 
				["class"] = UnitClass("player"),
				["key"] = player
			};
		end
	end
end

-- Data collection
CT_EH_oldUseContainerItem = UseContainerItem;
function CT_EH_newUseContainerItem(bag, slot)
	if ( InRepairMode() ) then
		local hasCooldown, repairCost = CT_EHTooltip:SetBagItem(bag,slot);
		if (repairCost and repairCost > 0 and repairCost <= GetMoney() and MerchantFrame.repairCost ) then
			MerchantFrame.repairCost = MerchantFrame.repairCost + repairCost;
		end
	end
	CT_EH_oldUseContainerItem(bag, slot);
end
UseContainerItem = CT_EH_newUseContainerItem;
CT_EH_oldPickupContainerItem = PickupContainerItem;
function CT_EH_newPickupContainerItem(bag, slot)
	if ( InRepairMode() ) then
		local hasCooldown, repairCost = CT_EHTooltip:SetBagItem(bag,slot);
		if (repairCost and repairCost > 0 and repairCost <= GetMoney() and MerchantFrame.repairCost ) then
			MerchantFrame.repairCost = MerchantFrame.repairCost + repairCost;
		end
	end
	CT_EH_oldPickupContainerItem(bag, slot);
end
PickupContainerItem = CT_EH_newPickupContainerItem;

CT_EH_oldRepairAllItems = RepairAllItems;
function CT_EH_newRepairAllItems()
	local repairAllCost, canRepair = GetRepairAllCost();
	if ( canRepair and repairAllCost <= GetMoney() ) then
		if ( MerchantFrame.repairCost ) then
			MerchantFrame.repairCost = MerchantFrame.repairCost + repairAllCost;
		end
	end
	CT_EH_oldRepairAllItems();
end
RepairAllItems = CT_EH_newRepairAllItems;

SlashCmdList["EXPENSEHISTORY"] = function()
	if ( CT_ExpenseHistoryFrame:IsVisible() ) then
		HideUIPanel(CT_ExpenseHistoryFrame);
	else
		ShowUIPanel(CT_ExpenseHistoryFrame);
	end
end

SLASH_EXPENSEHISTORY1 = "/expensehistory";
SLASH_EXPENSEHISTORY2 = "/eh";

function CT_EH_DropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CT_EH_DropDown_Initialize);
end

function CT_EH_DropDown_Initialize()
	local dropdown, info;
	if ( UIDROPDOWNMENU_OPEN_MENU ) then
		dropdown = getglobal(UIDROPDOWNMENU_OPEN_MENU);
	else
		dropdown = this;
	end
	info = { };
	info.text = "All Characters";
	info.value = nil;
	info.checked = ( not CT_ExpenseHistoryFrame.currPlayer );
	info.func = CT_EH_DropDown_OnClick;
	UIDropDownMenu_AddButton(info);
	for k, v in CT_EH_History do
		if ( type(v) == "table" ) then
			local total = CT_EH_GetMoney(k);
			if ( total >= CT_EH_DISPLAYTHRESHOLD ) then
				local _, _, playerName, server = string.find(k, "^(.+)@(.+)$");
				info = { };
				info.text = playerName .. " @ " .. server;
				info.value = k;
				info.checked = ( CT_ExpenseHistoryFrame.currPlayer == k );
				info.func = CT_EH_DropDown_OnClick;
				UIDropDownMenu_AddButton(info);
			end
		end
	end
end

function CT_EH_DropDown_OnClick()
	if ( this:GetID() == 1 ) then
		CT_ExpenseHistoryFrame.currPlayer = nil;
	else
		CT_ExpenseHistoryFrame.currPlayer = this.value;
	end
	CT_EH_UpdateSummary();
	CT_EH_UpdateLog();
end

function CT_EH_Tab_OnClick()
	if ( this:GetID() == 1 ) then
		getglobal("CT_ExpenseHistoryFrameSummary"):Show();
		getglobal("CT_ExpenseHistoryFrameLog"):Hide();
		CT_EH_UpdateSummary();
	elseif ( this:GetID() == 2 ) then
		getglobal("CT_ExpenseHistoryFrameSummary"):Hide();
		getglobal("CT_ExpenseHistoryFrameLog"):Show();
		CT_EH_UpdateLog();
	end
	PlaySound("igCharacterInfoTab");
	PanelTemplates_SetTab(CT_ExpenseHistoryFrame, this:GetID());
end

-- Find out if vendor is reagent vendor
function CT_EH_IsVendor(tbl)
	for i = 1, GetMerchantNumItems(), 1 do
		local name, texture, price, quantity, numAvailable, isUsable = GetMerchantItemInfo(i);
		if ( name and tbl[strlower(name)] ) then
			return true;
		end
	end
	return false;
end

local CT_EH_oldMerchantFrame_OnShow = MerchantFrame:GetScript("OnShow");
local CT_EH_oldMerchantFrame_OnHide = MerchantFrame:GetScript("OnHide");
MerchantFrame:SetScript("OnShow", function()
	CT_EH_oldMerchantFrame_OnShow();
	if ( CT_EH_IsVendor(CT_EH_SCANFORREAGENTS) ) then
		MerchantFrame.reagentCost = 0;
	else
		MerchantFrame.reagentCost = nil;
	end
	if ( CT_EH_IsVendor(CT_EH_SCANFORAMMO) ) then
		MerchantFrame.ammoCost = 0;
	else
		MerchantFrame.ammoCost = nil;
	end
	local repairAllCost, canRepair = GetRepairAllCost();
	if ( canRepair ) then
		MerchantFrame.repairCost = 0;
	else
		MerchantFrame.repairCost = nil;
	end
end);
MerchantFrame:SetScript("OnHide", function()
	CT_EH_oldMerchantFrame_OnHide();
	if ( MerchantFrame.reagentCost and MerchantFrame.reagentCost > 0 ) then
		tinsert(CT_EH_History[UnitName("player") .. "@" .. GetCVar("realmName")], { MerchantFrame.reagentCost, CT_EH_REAGENT, time() });
	end
	if ( MerchantFrame.ammoCost and MerchantFrame.ammoCost > 0 ) then
		tinsert(CT_EH_History[UnitName("player") .. "@" .. GetCVar("realmName")], { MerchantFrame.ammoCost, CT_EH_AMMO, time() });
	end
	if ( MerchantFrame.repairCost and MerchantFrame.repairCost > 0 ) then
		tinsert(CT_EH_History[UnitName("player") .. "@" .. GetCVar("realmName")], { MerchantFrame.repairCost, CT_EH_REPAIR, time() });
	end
	MerchantFrame.repairCost = nil;
	MerchantFrame.reagentCost = nil;
	MerchantFrame.ammoCost = nil;
	CT_EH_UpdateSummary();
	CT_EH_UpdateLog();
end);

local CT_EH_oldBuyMerchantItem = BuyMerchantItem;
function BuyMerchantItem(id, qty)
	local name, texture, price, quantity, numAvailable, isUsable = GetMerchantItemInfo(id);
	local realPrice = price*(qty or 1);
	CT_EH_oldBuyMerchantItem(id, qty);
	if ( MerchantFrame.reagentCost and name and CT_EH_SCANFORREAGENTS[strlower(name)] and realPrice <= GetMoney() ) then
		MerchantFrame.reagentCost = MerchantFrame.reagentCost + realPrice;
	end
	if ( MerchantFrame.ammoCost and name and CT_EH_SCANFORAMMO[strlower(name)] and realPrice <= GetMoney() ) then
		MerchantFrame.ammoCost = MerchantFrame.ammoCost + realPrice;
	end
end

local CT_EH_oldSendMail = SendMail;
function SendMail(target, subject, body)
	CT_EH_oldSendMail(target, subject, body);
	local price = SendMailCostMoneyFrame.staticMoney;
	if ( price <= GetMoney() ) then
		tinsert(CT_EH_History[UnitName("player") .. "@" .. GetCVar("realmName")], { price, CT_EH_MAIL, time() });
		CT_EH_UpdateSummary();
		CT_EH_UpdateLog();
	end
end

CT_EH_oldTakeTaxiNode = TakeTaxiNode;
function CT_EH_newTakeTaxiNode(id)
	if ( GetMoney() >= TaxiNodeCost(id) ) then
		tinsert(CT_EH_History[UnitName("player") .. "@" .. GetCVar("realmName")], { TaxiNodeCost(id), CT_EH_FLIGHT, time() });
		CT_EH_UpdateSummary();
		CT_EH_UpdateLog();
	end
	CT_EH_oldTakeTaxiNode(id);
end

TakeTaxiNode = CT_EH_newTakeTaxiNode;

if ( CT_RegisterMod ) then
	CT_RegisterMod(CT_EH_MODINFO[1], CT_EH_MODINFO[2], 5, "Interface/Icons/INV_Misc_Note_05", CT_EH_MODINFO[3], "switch", "", function() if ( CT_ExpenseHistoryFrame:IsVisible() ) then HideUIPanel(CT_ExpenseHistoryFrame) else ShowUIPanel(CT_ExpenseHistoryFrame) end end);
else
	DEFAULT_CHAT_FRAME:AddMessage("<CTMod> Expense History loaded. Type /expensehistory or /eh to display the window.", 1, 1, 0);
end