local tablet = AceLibrary("Tablet-2.0")
local abacus = AceLibrary("Abacus-2.0")

local L = AceLibrary("AceLocale-2.2"):new("FuBar_ToFu")
ToFu = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceHook-2.0", "AceConsole-2.0", "AceDB-2.0", "FuBarPlugin-2.0")

ToFu:RegisterDB("ToFuDB")
ToFu:RegisterDefaults('account', {
	paths = {
		Alliance = {['*']={}},
		Horde = {['*']={}},
	},
})
ToFu:RegisterDefaults('profile', {
	hook = {
		oCD = false,
		oCB = false,
		BigWigs = false,
		Chronometer = false,
	},
})
local optionsTable = {
	handler = ToFu,
	type = 'group',
	args = {
		hook = {
			type = 'group', name = L['Hooks'],
			desc = L['Other addons to hook into'],
			args = {
				oCD = {
					type = 'toggle', name = 'oCD',
					desc = 'otravi_CoolDown',
					get = function() return ToFu.db.profile.hook.oCD end,
					set = function(t) ToFu.db.profile.hook.oCD = t end,
					disabled = function()
						if oCD then return false
						else return true end
					end,
				},
				oCB = {
					type = 'toggle', name = 'oCB',
					desc = 'otravi_CastBar',
					get = function() return ToFu.db.profile.hook.oCB end,
					set = function(t) ToFu.db.profile.hook.oCB = t end,
					disabled = function()
						if oCB then return false
						else return true end
					end,
				},
				BigWigs = {
					type = 'toggle', name = 'BigWigs',
					desc = 'BigWigs_CustomBar',
					get = function() return ToFu.db.profile.hook.BigWigs end,
					set = function(t) ToFu.db.profile.hook.BigWigs = t end,
					disabled = function()
						if BigWigs and BigWigsCustomBar then return false
						else return true end
					end,
				},
				Chronometer = {
					type = 'toggle', name = 'Chronometer',
					desc = 'Chronometer',
					get = function() return ToFu.db.profile.hook.Chronometer end,
					set = function(t) ToFu.db.profile.hook.Chronometer = t end,
					disabled = function()
						if Chronometer then return false
						else return true end
					end,
				},
			},
		},
		data = {
			type = 'group', name = L['Data'],
			desc = L["Various options to do with saved flight data"],
			args = {
				clear = {
					type = 'execute', name = L['Clear Data'],
					desc = L["Delete *ALL* saved flight path data for your faction."],
					func = "ClearData",
				},
				default = {
					type = 'execute', name = L['Default Data'],
					desc = L["Load the default flight-time dataset."],
					func = function() ToFu:LoadDefaults() end, disabled = function() return type(ToFu.LoadDefaults) ~= 'function' end,
				},
			},
		},
	},
}

ToFu:RegisterChatCommand({"/tofu",}, optionsTable)
ToFu.OnMenuRequest = optionsTable

ToFu.version = "2.0." .. string.sub("$Revision: 15647 $", 12, -3)
ToFu.date = string.sub("$Date: 2006-10-31 20:34:49 -0500 (Tue, 31 Oct 2006) $", 8, 17)
ToFu.hasIcon = "Interface\\TaxiFrame\\UI-Taxi-Icon-Green"
ToFu.hideWithoutStandby = true

function ToFu:OnInitialize()
	self.start = nil
	self.destination = nil
	self.inFlight = false
	self.timeFlown = 0
	self.avgTime = nil
	self.last, self.nodes, self.steps = {}, {}, {}

	--Stolen from AceDB
	local _,race = UnitRace("player")
	if race == "Orc" or race == "Scourge" or race == "Troll" or race == "Tauren" then
		self.faction = 'Horde'
	else
		self.faction = 'Alliance'
	end

	if not self.db.account.version then
		self:ScheduleEvent(function()
			--Check to see whether we need to shrink the database.
			for faction, data in self.db.account.paths do
				self:Print('Upgrading', faction, 'flight data')
				--Convert the old data to the new format, and clear out the old DB in the process.
				if type(data) ~= 'table' then data = nil
				else
					local newdata = {}
					for start,dests in pairs(data) do
						local newstart = self:LessName(start)
						newdata[newstart] = {}
						if type(dests) == 'table' then
							for dest, info in pairs(dests) do
								newdata[newstart][self:LessName(dest)] = info
								data[start][dest] = nil
							end
						end
						data[start] = nil
					end
					--Now apply the new data to the DB.
					--(Can't merge this with the previous step, as it would be editing the table while looping over it.)
					for start, dests in pairs(newdata) do
						for dest, info in pairs(dests) do
							if not self.db.account.paths[start] then self.db.account.paths[start] = {} end
							self.db.account.paths[start][dest] = info
						end
					end
				end
			end
			
			self.db.account.version = 1
		end, 1)
	end
end

function ToFu:OnEnable()
	self:RegisterEvent("TAXIMAP_OPENED")
	self:Hook("TakeTaxiNode")
	self:Hook("TaxiNodeOnButtonEnter")
end


function ToFu:OnTextUpdate()
	if self.inFlight then
		if self.timeAvg ~= 0 then
			--Time remaining, if we've taken this path before.
			self:SetText(abacus:FormatDurationCondensed(self.timeAvg - self.timeFlown, true))
		else
			--Time flown so far.
			self:SetText(abacus:FormatDurationCondensed(self.timeFlown, true))
		end
	else
		self:SetText('')
	end
end

function ToFu:OnTooltipUpdate()
	local cat = tablet:AddCategory(
		'columns', 2,
		'text', L["Current Flight"],
		'child_textR', 1, 'child_textG', 0.82, 'child_textB', 0,
		'child_text2R', 1, 'child_text2G', 1, 'child_text2B', 1
	)
	if self.inFlight then
		cat:AddLine('text', L["From"], 'text2', self.start)
		cat:AddLine('text', L["To"], 'text2', self.destination)
		cat:AddLine('text', L["Time Taken"], 'text2', abacus:FormatDurationCondensed(self.timeFlown, true))
		cat:AddLine('text', L["Average Time"], 'text2', abacus:FormatDurationCondensed(self.timeAvg, true))
		cat:AddLine('text', L["Cost"], 'text2', abacus:FormatMoneyCondensed(self.cost, true))
	else
		cat:AddLine('text', L["Not in flight"])
	end

	cat = tablet:AddCategory(
		'columns', 2,
		'text', L["Previous Flight"],
		'child_textR', 1, 'child_textG', 0.82, 'child_textB', 0,
		'child_text2R', 1, 'child_text2G', 1, 'child_text2B', 1
	)
	if self.last.start ~= nil then
		cat:AddLine('text', L["From"], 'text2', self.last.start)
		cat:AddLine('text', L["To"], 'text2', self.last.destination)
		cat:AddLine('text', L["Time Taken"], 'text2', abacus:FormatDurationCondensed(self.last.time, true))
		cat:AddLine('text', L["Cost"], 'text2', abacus:FormatMoneyCondensed(self.last.cost, true))
	else
		cat:AddLine('text', L["No previous flight"])
	end

	if self.inFlight then
		tablet:SetHint(L["Click to copy the time remaining in flight to the chatbox."])
	end
end

function ToFu:OnClick()
	-- Paste time remaining to chatbox.
	if ChatFrameEditBox:IsVisible() and self.inFlight then
		local text = ""
		local _, time = self:GetFlightData(self.start, self.destination)

		if time ~= 0 then
			--Time remaining, if we've taken this path before.
			text = abacus:FormatDurationFull(time - self.timeFlown, false)
		else
			--Time flown so far.
			text = string.format("??? (%s %s)", abacus:FormatDurationFull(self.timeFlown, false), L["So Far"])
		end

		ChatFrameEditBox:Insert(text)
	end
end


function ToFu:OnUpdate(timeSinceLast)
	self.timeFlown = self.timeFlown + timeSinceLast
	if (UnitOnTaxi("player") ~= 1) and (self.timeFlown > 5) then
		-- Cheap test to make sure that we don't do all this right at the beginning of a flight
		self:CancelScheduledEvent(self.name)
		self.inFlight = false

		local cost, time, taken = self:GetFlightData(self.start, self.destination)

		--Average time this route has taken (there's a chance we'll be getting inaccuracies, etc.):
		if time ~= 0 then
			time = (time + self.timeFlown) / 2
			
			if self.db.profile.hook.oCB and oCB then
				oCB:SpellStop(true)
			end
		else
			time = self.timeFlown
		end

		self:SaveFlightData(self.start, self.destination, cost, time, taken + 1)

		self.last.start = self.start
		self.last.destination = self.destination
		self.last.time = self.timeFlown
		self.last.cost = cost

		self.start = nil
		self.destination = nil
		self.timeFlown = nil
	end
	self:UpdateDisplay()
end


function ToFu:TAXIMAP_OPENED()
	local numNodes = NumTaxiNodes()
	-- Have to scan all the slots first to get the "CURRENT" slot.
	for i=1, numNodes do
		if TaxiNodeGetType(i) == "CURRENT" then
			self.start = TaxiNodeName(i)
		else
			local x,y = TaxiNodePosition(i)
			self.nodes[x..':'..y] = i
		end
	end
end

function ToFu:TakeTaxiNode(slot)
	if TaxiNodeGetType(slot) == "REACHABLE" then
		self.destination = TaxiNodeName(slot)
		self.timeFlown = 0
		self.inFlight = true

		self.cost, self.timeAvg = self:GetFlightData(self.start, self.destination)

		if self.timeAvg ~= 0 then
			if self.db.profile.hook.oCD and oCD then
				--oCD:StartBar(id, time, text, icon)
				oCD:StartBar('ToFu', self.timeAvg, self:LessName(self.destination), "Interface\\TaxiFrame\\UI-Taxi-Icon-Green")
			end
			if self.db.profile.hook.oCB and oCB then
				--oCB:SpellStart(text, time, inSeconds, dontRegister)
				oCB:SpellStart(self:LessName(self.destination), self.timeAvg, true, true)
			end
			if self.db.profile.hook.BigWigs and BigWigs and BigWigsCustomBar then
				--BWCB(seconds, message)
				BWLCB(self.timeAvg, "Flying to "..self:LessName(self.destination))
			end
			if self.db.profile.hook.Chronometer and Chronometer then
				-- Chronometer:AddTimer(kind, name, duration, targeted, isgain, selforselect, extra)
				if not Chronometer.timers[Chronometer.SPELL]['ToFu'] then Chronometer:AddTimer(Chronometer.SPELL, 'ToFu', self.timeAvg, 0, 0, 0) end
				Chronometer:StartTimer(Chronometer.timers[Chronometer.SPELL]['ToFu'], self:LessName(self.destination))
			end
		end

		for key in pairs(self.nodes) do self.nodes[key] = nil end

		self:ScheduleRepeatingEvent(self.name, self.OnUpdate, 1, self, 1)
	end
	self.hooks["TakeTaxiNode"].orig(slot)
end

function ToFu:TaxiNodeOnButtonEnter(button)
	self.hooks["TaxiNodeOnButtonEnter"].orig(button)

	local index = button:GetID()
	if TaxiNodeGetType(index) == "REACHABLE" then
		local destination, cost = TaxiNodeName(index), TaxiNodeCost(index)
		local oldcost, time, taken = self:GetFlightData(self.start, destination)
		if oldcost ~= cost then self:SaveFlightData(self.start, destination, cost, time, taken) end

		local estimate = ''
		if time == 0 then
			--Try to estimate.  This is mainly stolen from Blizzard's TaxiFrame.lua, which uses a much more in-depth version of this to draw the lines.
			local _,reversetime = self:GetFlightData(destination, self.start)
			if reversetime > 0 then
				time = reversetime
				estimate = L['reversed']
			else
				local numRoutes = GetNumRoutes(index)
				if numRoutes > 1 then
					TaxiNodeSetCurrent(index) --This is magic -- it renumbers the 'hops' between index and current so the for loop below works.
					self.steps[1] = self.start

					local length = 0
					for i=1, numRoutes do
						local l, dx, dy = self:LineLength(index, i)
						length = length + l 
						
						self.steps[i+1] = TaxiNodeName(self.nodes[dx..':'..dy])
					end

					local skipped = 0
					local node = 1
					while node <= numRoutes do
						--self.steps[node] is the 'current' node.
						--Step backwards through self.steps to find the longest interval that gives us a time.
						local nnode = numRoutes+1
						while nnode > node do
							local _,t = self:GetFlightData(self.steps[node], self.steps[nnode])
							if t==0 then _,t = self:GetFlightData(self.steps[nnode], self.steps[node]) end
							nnode = nnode - 1
							if t > 0 then
								-- We know this!  Add it to the time and skip the rest of the loops.
								time = time + t
								break
							elseif nnode == node then
								-- We have no idea.  Mark down that we skipped this.
								skipped = skipped + self:LineLength(index, node-1) -- Is 'node-1' right here?
								break
							end
						end
						node = nnode + 1
					end
					if time > 0 then
						if skipped > 0 then time = time + ((time/(length-skipped)) * skipped) end
						estimate = ' '..L['estimated']
					end

					for key in pairs(self.steps) do self.steps[key] = nil end
				end
			end
		end

		GameTooltip:AddLine(L["Takes"]..": "..abacus:FormatDurationCondensed(time)..estimate, 1, 1, 1)
		GameTooltip:AddLine(string.format(L["Flown %s times"], taken), 1, 1, 1)
		GameTooltip:SetHeight(GameTooltip:GetHeight() + 28)
	end
end

function ToFu:LessName(s)
	--For "Thelsamar, Loch Modan" return "Thelsamar".
    if not s then return '' end
	if ( GetLocale() ~= "koKR" ) then
		local c = string.find(s, ', ')
		if c then s = string.sub(s, 1, c-1) end
	else
		local c = string.find(s, ' %(')
		if c then _,_,s = string.find(s, "(.-) %(") end
	end
	return s
end

-- Data in format: ":1000:120:3:"
-- Means: "cost 1000 copper, taking 120 seconds, taken 3 times."

function ToFu:GetFlightData(start, destination, faction)
	-- cost, time, taken = ToFu:GetFlightData(start, destination, faction)
	-- Fetches the saved information about a flight path, from [start] to [destination].
	-- If any arguments are left as nil, the current value of self.<<argument>> will be used.
	local s = self.db.account.paths[faction or self.faction][self:LessName(start or self.start)][self:LessName(destination or self.destination)]
	if type(s) ~= 'string' then return 0,0,0 end
	local _, _, cost, time, taken = string.find(s, ":(%d+):(%d+):(%d+):")
	return (tonumber(cost) or 0), (tonumber(time) or 0), (tonumber(taken) or 0), self:LessName(start or self.start), self:LessName(destination or self.destination)
end

function ToFu:SaveFlightData(start, destination, cost, time, taken, faction)
	self.db.account.paths[faction or self.faction][self:LessName(start)][self:LessName(destination)] = ':'..(cost or 0)..':'..math.floor((time and tonumber(time) or 0))..':'..(taken or 0)..':'
	return true
end

function ToFu:ClearData()
	self.db.account.paths[self.faction] = {}
end

function ToFu:LineLength(index, i)
	--Start node:
	local sx = TaxiGetSrcX(index, i)
	local sy = TaxiGetSrcY(index, i)
	--End node:
	local ex = TaxiGetDestX(index, i)
	local ey = TaxiGetDestY(index, i)
	-- Determine dimensions
	local dx,dy = ex - sx, ey - sy;
	-- Normalize direction if necessary
	if (dx < 0) then
		dx,dy = -dx,-dy;
	end
	-- Return length and endpoint coords.
	return sqrt((dx * dx) + (dy * dy)), ex, ey
end