local loc = TransporterFu.locals
local tablet = TabletLib:GetInstance('1.0')
local dewdrop = DewdropLib:GetInstance('1.0')
local compost = CompostLib:GetInstance('compost-1')
local metro = Metrognome:GetInstance('1')
local PT = PeriodicTableEmbed:GetInstance("1")
local SEBags = SpecialEventsEmbed:GetInstance("Bags 1")
local SEEquipped = SpecialEventsEmbed:GetInstance("Equipped 1")
local crayon = CrayonLib:GetInstance("1.0")
local abacus = AbacusLib:GetInstance("1.0")

compost:Reclaim(TransporterFu)
TransporterFu = nil

TransporterFu = FuBarPlugin:GetInstance("1.2"):new({
   name           = "FuBar - Transporter",
   description    = loc.Desc,
   version        = "0.6.4",
   releaseDate    = string.sub("$Date: 2006-05-22 10:02:28 -0400 (Mon, 22 May 2006) $", 8, 17),
   author         = "Prandur",
   website        = "http://prandur.wowinterface.com",
   aceCompatible  = 103,
   category       = "others",
   db             = AceDatabase:new("TransporterFuDB"),
   chardb         = "TransporterFuCharDB",
   defaults       = {
   		showTag = true,
   		showCooldown = true,
   },
   charDefaults   = {
   		SET = "item6948",
   },
   cmd            = AceChatCmd:new({"/transporterfu","/fubar_transporter"}, {}),
   hasIcon        = true,
})

function TransporterFu:Initialize()
	_, self.Class = UnitClass("player")
	if PT then
		self:debug(loc.PT_FOUND)
		self.ItemsToCheck = {"mounts", "mountsaq", "transporteritems", "transporterequips"}
	else
		self.cmd:msg(loc.PT_NOT_FOUND)
	end
	if SEBags and SEEquipped then
		self:debug(loc.SE_FOUND)
	else
		self.cmd:msg(loc.SE_NOT_FOUND)
	end
end

function TransporterFu:Enable()
	self.METHODS = compost:Acquire()
	self.SET1 = compost:Acquire()
	self.NeedsUpdate = compost:Acquire()
	self:RegisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("PLAYER_LEAVING_WORLD")
	SEBags:RegisterEvent(self, "SPECIAL_BAGSLOT_UPDATE", "ParseItem")
	SEEquipped:RegisterEvent(self, "SPECIAL_INVENTORY_UPDATE", "ParseEquipped")
	self:UpdateSpells()
	self:UpdateBind()
	self:PLAYER_ENTERING_WORLD()
	metro:Register(self.name, self.RunTasks, 1, self)
	metro:Start(self.name)
end

function TransporterFu:Disable()
	metro:Unregister(self.name)
	compost:ReclaimMulti(self.METHODS, self.SET1, self.NeedsUpdate)
	self.METHODS, self.SET1, self.NeedsUpdate = nil, nil, nil
end

function TransporterFu:PLAYER_ENTERING_WORLD()
	self:debug("Player Entering World")
	self:RegisterEvent("CHAT_MSG_SYSTEM")
	self:RegisterEvent("SPELLS_CHANGED")
	if PT then
		self:UpdateItems()
		self:UpdateEquipped()
	end
	self:UpdateCooldowns()
	self.NeedsUpdate.SetDisplay = self.SetDisplay
end

function TransporterFu:PLAYER_LEAVING_WORLD()
	self:debug("Player Leaving World")
	self:UnregisterEvent("CHAT_MSG_SYSTEM")
	self:UnregisterEvent("SPELLS_CHANGED")
end

function TransporterFu:CHAT_MSG_SYSTEM()
	self:debug("Scheduling bind update")
	self.NeedsUpdate.UpdateBind = self.UpdateBind
end

function TransporterFu:SPELLS_CHANGED()
	self:debug("Scheduling spell update")
	self.NeedsUpdate.UpdateSpells = self.UpdateSpells
end

function TransporterFu:MenuSettings(level,value)
	if level == 1 then
		for k,v in self:pairsByKeys(self.METHODS) do
			local my_k = k
			dewdrop:AddLine(
				'text', loc[my_k],
				'func', function() self:DoMethod(my_k) end,
				'closeWhenClicked', true
			)
		end
		dewdrop:AddLine()
		dewdrop:AddLine(
			'text', loc.MENU_SET,
			'hasArrow', true,
			'value', "SET1"
		)
		dewdrop:AddLine(
			'text', loc.SHOW_TAG,
			'func', function() self:ToggleOption("showTag", true) end,
			'checked', self.data.showTag
		)
		dewdrop:AddLine(
			'text', loc.SHOW_COOLDOWN,
			'func', function() self:ToggleOption("showCooldown", true) end,
			'checked', self.data.showCooldown
		)
		dewdrop:AddLine()
		dewdrop:AddLine(
			'text', loc.MANUAL,
			'func', function()
				self.METHODS = compost:Erase(self.METHODS)
				if PT then
					self:UpdateItems()
					self:UpdateEquipped()
				end
				self:UpdateSpells()
				self:UpdateBind()
				self:SetDisplay()
				self:UpdateCooldowns()
				self:Update()
			end,
			'closeWhenClicked', true
		)
	elseif level == 2 then
		if value == "SET1" then
			for k,v in self:pairsByKeys(self.METHODS) do
				local my_k = k
				dewdrop:AddLine(
					'text', loc[my_k],
					'func', function() self:SetDisplay(my_k) end,
					'checked', self.SET1[my_k],
					'closeWhenClicked', true
				)
			end
		end
	end
end

function TransporterFu:UpdateText()
	self:debug("Updating Text")
	local method = self.charData.SET
	if method and self.METHODS[method] then
		local t = compost:Acquire()
		
		if self.data.showTag then
			if method == "item6948" or method == "ASTRAL" then
				table.insert(t,self.BLOC)
			else
				table.insert(t,loc[method])
			end
		end
		
		if self.data.showCooldown then
			if not self.METHODS[method].cooldown then
				local cd = self:GetCooldown(method)
				table.insert(t,cd)
			end
		end
		
		self:SetText(table.concat(t, " "))
		compost:Reclaim(t)
	else
		self:SetText(loc.NA)
	end
end

function TransporterFu:UpdateTooltip()
	self:debug("Updating Tooltip")
	local cat = tablet:AddCategory(
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0,
		'child_text2R', 1,
		'child_text2G', 1,
		'child_text2B', 1
	)
	if self.charData.SET and loc[self.charData.SET] then
		cat:AddLine('text', loc.METHOD1, 'text2', loc[self.charData.SET])
	end
	local bloc = self.BLOC
	if bloc then
		cat:AddLine('text', loc.INN, 'text2', bloc)
	end
	local cat = tablet:AddCategory(
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0,
		'child_text2R', 0,
		'child_text2G', 1,
		'child_text2B', 0
	)
	for k,v in self:pairsByKeys(self.METHODS) do
		if v.cooldown then
			cat:AddLine('text', loc[k], 'text2', loc.READY)
		else
			cat:AddLine('text', loc[k], 'text2', self:GetCooldown(k))
		end
	end
	tablet:SetHint(loc.HINT)
end

function TransporterFu:RunTasks()
	for k,v in pairs(self.NeedsUpdate) do
		self:debug(string.format("Executing scheduled function: %s", k))
		v(self)
		self.NeedsUpdate[k] = nil
	end
	if self.charData.SET and self.METHODS[self.charData.SET] then
		if not self.METHODS[self.charData.SET].cooldown and self.data.showCooldown then
			self:UpdateDisplay()
		end
	end
end

function TransporterFu:OnClick()
	if self.charData.SET and self.METHODS[self.charData.SET] then
		self:DoMethod(self.charData.SET)
	end
end

function TransporterFu:FindSpell(spellname)
	for i = 1,180 do
		local s = GetSpellName(i, BOOKTYPE_SPELL)
		if s == spellname then
			return i
		end
	end
end

function TransporterFu:GetLinkInfo(link, PTSets)
	if type(link) == "string" then
		local _, _, itemid, desc = string.find(link, "item:(%d+):%d+:%d+:%d+|h%[(.+)%]")
		if itemid and desc then
			if PTSets then
				local _, itemset = PT:ItemInSet(tonumber(itemid), PTSets)
				return itemid, desc, itemset
			else
				return itemid, desc
			end
		end
	end
end

function TransporterFu:ParseItem(b, s, link)
	if PT and link and b and s then
		local itemid, desc, itemset = self:GetLinkInfo(link, self.ItemsToCheck)
		if itemid and desc and itemset then
			local ITEM = "item"..itemid
			if (itemset == "mounts") or (itemset == "mountsaq") then
				self.METHODS[ITEM] = {bag = b, slot = s, mount = true}
			else
				self.METHODS[ITEM] = {bag = b, slot = s}
			end
			loc[ITEM] = desc
			_, self.METHODS[ITEM].cooldown = self:GetCooldown(ITEM)
		end
	end
end

function TransporterFu:UpdateItems()
	self:debug("Updating Items")
	if PT then
		for b = 0, NUM_BAG_FRAMES do
			local SLOTS = GetContainerNumSlots(b)
			if SLOTS > 0 then
				for s = 1, SLOTS do
					self:ParseItem(b, s, GetContainerItemLink(b, s))
				end
			end
		end
	end
end

function TransporterFu:ParseEquipped(s, link)
	if PT and s and link then
		local itemid, desc, itemset = self:GetLinkInfo(link, "transporterequips")
		if itemid and desc and itemset then
			local ITEM = "item"..itemid
			self.METHODS[ITEM] = {slot = s}
			loc[ITEM] = desc
			_, self.METHODS[ITEM].cooldown = self:GetCooldown(ITEM)	
		end
	end
end

function TransporterFu:UpdateEquipped()
	self:debug("Updating Equipped")
	if PT then
		for s = 0, 19 do
			self:ParseEquipped(s, GetInventoryItemLink("player", s))
		end
	end
end

function TransporterFu:UpdateSpells()
	self:debug("Updating Spells")
	local Class = self.Class
	local methods
		
	if (Class == "SHAMAN") then
		methods = {"ASTRAL"}
	elseif (Class == "DRUID") then
		methods = {"PORT_MG"}
	elseif (Class == "MAGE") then
		local City
		methods = compost:Acquire()
		if UnitFactionGroup("player") == "Alliance" then
			City = {"DN","IF","SW"}
		else
			City = {"OG","TB","UC"}
		end
		for i,j in ipairs({"TELEPORT_","PORTAL_"}) do
			for k,v in ipairs(City) do
				table.insert(methods,j..v)
			end
		end
	elseif (Class == "PALADIN") then
		methods = {"WARHORSE","CHARGER"}
	elseif (Class == "WARLOCK") then
		methods = {"FELSTEED","DREADSTEED"}
	end
	
	if methods then
		for i,method in ipairs(methods) do
			local sid = self:FindSpell(loc[method])
			if sid then
				self.METHODS[method] = {id = sid}
				_, self.METHODS[method].cooldown = self:GetCooldown(method)
			end
		end
	end
	compost:Reclaim(methods)
end

function TransporterFu:SetDisplay(method)
	self:debug("Setting Display")
	self.SET1 = compost:Erase(self.SET1)
	if method then
		self.charData.SET = method
	else
		method = self.charData.SET
		if not method then
			self:SetIcon(true)
			self:UpdateDisplay()
			return
		end
	end
	self.SET1[method] = true
	if self.METHODS[method] then
		if string.find(method, "item", 1, true) then
			local _, _, _, _, _, _, _, _, T = GetItemInfo(tonumber(string.sub(method, 5)))
			if T then
				self:SetIcon(T)
			else
				self:SetIcon(true)
			end
		else
			self:SetIcon(GetSpellTexture(self.METHODS[method].id, BOOKTYPE_SPELL))
		end
	else
		self:SetIcon(true)
	end
	self:UpdateDisplay()
end

function TransporterFu:DoMethod(method)
	if string.find(method, "item", 1, true) then
		local I = self.METHODS[method]
		if I and (I.slot and not I.bag) then
			UseInventoryItem(I.slot)
		elseif (I.bag and I.slot) then
			UseContainerItem(I.bag, I.slot)
		end
	else
		CastSpellByName(loc[method])
	end
end

function TransporterFu:GetCooldown(method)
	local t, d, cat

	if string.find(method, "item", 1, true) then
		local I = self.METHODS[method]
		if (I.bag and I.slot) then
			cat = "UpdateItems"
			t, d = GetContainerItemCooldown(I.bag, I.slot)
		elseif (I.slot and not I.bag) then
			cat = "UpdateEquipped"
			t, d = GetInventoryItemCooldown("player", I.slot)
		end
	else
		cat = "UpdateSpells"
		local sid = self.METHODS[method].id
		if sid then
			t, d = GetSpellCooldown(sid, BOOKTYPE_SPELL)
		end
	end

	if t and d then
		local cd = d - (GetTime() - t - 60)
		if cd < 1 then
			return crayon:Green(loc.READY), true
		elseif (cd < 60) then
			return abacus:FormatDurationFull(cd, TRUE, FALSE), false
		else
			return abacus:FormatDurationFull(cd, TRUE, TRUE), false
		end
	else
		self.NeedsUpdate[cat] = self[cat]
		return crayon:Red(loc.NA), FALSE
	end
end

function TransporterFu:UpdateBind()
	self:debug("Updating bind location")
	local bloc = GetBindLocation()
	if bloc then
		self.BLOC = bloc
	else
		self.NeedsUpdate.UpdateBind = self.UpdateBind
	end
end

function TransporterFu:UpdateCooldowns()
	self:debug("Updating cooldowns")
	for k,v in pairs(self.METHODS) do
		_, v.cooldown = self:GetCooldown(k)
	end
end

function TransporterFu:ToggleOption(var, doUpdate)
	self.data[var] = not self.data[var]
	if doUpdate then
		self:Update()
	end
	return self.data[var]
end

function TransporterFu:pairsByKeys (t, f)
--taken from an example in the Programming in Lua book
	local a = compost:Acquire()
	for n in pairs(t) do
		table.insert(a, n)
	end
	table.sort(a, f)
	local i = 0      -- iterator variable
	local iter = function ()   -- iterator function
		i = i + 1
		if a[i] == nil then
			compost:Reclaim(a)
			return nil
		else
			return a[i], t[a[i]]
		end
	end
	return iter
end

TransporterFu:RegisterForLoad()