MNT2 = CreateFrame('frame')
local MNT2 = MNT2
MNT2:SetScript("OnEvent", function(self, event, ...) if self[event] then return self[event](self, event, ...) end end)
MNT2:RegisterEvent("ADDON_LOADED")

local L = LibStub("AceLocale-3.0"):GetLocale("MNT2", false)
local Wintergrasp = LibStub("LibBabble-Zone-3.0"):GetLookupTable()['Wintergrasp']

local possibleStates = { -- possible states in order of importants state 1 will always have preferance over state n
	'isMoving', -- resource intensive switch needs to be enabled to work
	'inZone', -- switch that overwrites everything below (i.e. you can assign different macroes per zone)
	'inCombat',
	'isIndoors',
	'isSwimming',
	'isFlyable',
}

local states = {} -- state of all updaters

MNT2.state = {}

setmetatable(MNT2.state, {
	__index = function(t,k)
		return states[k]
	end,
	__newindex = function (t,k,v)
		if v == states[k] then return end
		--print("* MNT2 update of element |cffffd200" .. tostring(k) .. "|r from |cffff2020" .. tostring(states[k]) .."|r to |cff20ff20" .. tostring(v).."|r")
		states[k] = v   -- update original table
		MNT2:GenerateMacro()
	end
})

function MNT2:ADDON_LOADED(event, addon)
	if addon == "Mounted" then

		self:UnregisterEvent("ADDON_LOADED")
		self.ADDON_LOADED = nil
		if IsLoggedIn() then self:PLAYER_LOGIN() else self:RegisterEvent("PLAYER_LOGIN") end
	end
end

function MNT2:PLAYER_LOGIN(event, addon)
	if GetMacroIndexByName("Mounted") == 0 then
		CreateMacro("Mounted", 1, '')
	end

	local level = UnitLevel('player')
	local race = select(2, UnitRace('player'))
	local faction = UnitFactionGroup('player')

	local defaults = {
		['char'] = {
			mounts = {
				['ground'] = {},
				['flying'] = {},
				['water'] = {},
				['aq'] = {},
			},
			mountSets = {
				['ground'] = {},
				['flying'] = {},
				['water'] = {},
				['aq'] = {},
			},
			mountSetsPriority = {
				['ground'] = {},
				['flying'] = {},
				['water'] = {},
				['aq'] = {},
			},
			mountSummonMode = {
			},
			mount_skill = 0,
			level = level,
			race = race,
			faction = faction,
		},
		['profile'] = {
			macrosets = {
				['DEFAULT'] = {
					['inCombat'] = 'Unassigned',
					['isIndoors'] = 'Unassigned',
					['isSwimming'] = 'Default Water Mount',
					['isFlyable'] = 'Default Air Mount',
					['ground'] = 'Default Ground Mount',
				},
				isMoving = {
					['inCombat'] = 'Unassigned',
					['isIndoors'] = 'Unassigned',
					['isSwimming'] = 'Unassigned',
					['isFlyable'] = 'Unassigned',
					['ground'] = 'Unassigned',
				},
			},
			activeMacroSets = {
				['DEFAULT'] = true,
				['isMoving'] = false,
			},
			instanceMacroSets = {},
			alwaysdifferent = true,
		},
		['global'] = {
			['macros'] = self.defaultmacros,
		},
	}
	self.db = LibStub("AceDB-3.0"):New("MNT2DB", defaults)

	-- checks to make sure that this character is not a new one or changed faction/race
	if (self.db.char.level and self.db.char.level > level) or (self.db.char.race and self.db.char.race ~= race) or (self.db.char.faction and self.db.char.faction ~= faction) then
		print(L['Mount Database Reset'])
		self.db.char = defaults.char
	else
		self.db.char.level = level
		self.db.char.race = race
		self.db.char.faction = faction
	end

	self:RegisterModuleOptions('profile', LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db), L['Profile'])

	self.db:RegisterCallback("OnProfileChanged", function() MNT2:ProcessDB() end)
	self.db:RegisterCallback("OnProfileCopied", function() MNT2:ProcessDB() end)
	self.db:RegisterCallback("OnProfileReset", function() MNT2:ProcessDB() end)

	-- import all macros from 1.3 into 2 macro assignments will have to be made manually
	self:ONEtoTWOupgrade()

	self:ProcessDB()

	self:RegisterEvent("PLAYER_REGEN_DISABLED")
	self:RegisterEvent("PLAYER_REGEN_ENABLED")
	self:RegisterEvent("SPELL_UPDATE_USABLE")
	self:RegisterEvent("UPDATE_WORLD_STATES") -- updates our wintergrasp time

	-- /sigh 3 events to get zone data
	self:RegisterEvent("ZONE_CHANGED")
	self:RegisterEvent("ZONE_CHANGED_INDOORS")
	self:RegisterEvent("ZONE_CHANGED_NEW_AREA")

	-- event to track our riding skill
	self:RegisterEvent("ACHIEVEMENT_EARNED")

	-- events to see if we are mounting up
	self:RegisterEvent('UNIT_SPELLCAST_START')
	self:RegisterEvent('UNIT_SPELLCAST_SUCCEEDED')

	-- we learned a new mount (lets update)
	self:RegisterEvent('COMPANION_LEARNED')

	if self.db.profile.activeMacroSets.isMoving then
		self:SetScript("OnUpdate", MNT2.OnUpdate)
	end

	SLASH_Mounted1 = "/mounted";
	SLASH_Mounted2 = "/mnt";
	SlashCmdList["Mounted"] = function()
		InterfaceOptionsFrame_OpenToCategory(self.optframe.MNT2)
	end

	self:UnregisterEvent("PLAYER_LOGIN")
	self.PLAYER_LOGIN = nil
end

function MNT2:ProcessDB()
	self:MountScan()
	if self.LoadDruid then self:LoadDruid() end
	if self.LoadShaman then self:LoadShaman() end
	self:ACHIEVEMENT_EARNED()
	self:SPELL_UPDATE_USABLE()
	self:ZONE_CHANGED('startup')

end

function MNT2:PLAYER_REGEN_DISABLED()
	self.state.inCombat = true
	self:SetScript("OnUpdate", nil)
	self:UnregisterEvent('UNIT_SPELLCAST_START')
	self:UnregisterEvent('UNIT_SPELLCAST_SUCCEEDED')
end

function MNT2:PLAYER_REGEN_ENABLED()
	self.state.inCombat = false
	if self.db.profile.activeMacroSets.isMoving then
		self:SetScript("OnUpdate", MNT2.OnUpdate)
	end
	self:RegisterEvent('UNIT_SPELLCAST_START')
	self:RegisterEvent('UNIT_SPELLCAST_SUCCEEDED')
end

function MNT2:SPELL_UPDATE_USABLE()
	self.state.isIndoors = IsIndoors()
	self.state.isSwimming = IsSwimming()
	if Wintergrasp == GetRealZoneText() and not GetWintergraspWaitTime() then
		self.state.isFlyable = false
	else
		self.state.isFlyable = IsFlyableArea() and IsOutdoors() and (self.db.char.mount_skill > 150)
	end
end
MNT2.UPDATE_WORLD_STATES = MNT2.SPELL_UPDATE_USABLE


function MNT2:ZONE_CHANGED(event)
	self.state.inZone = GetRealZoneText()
end
MNT2.ZONE_CHANGED_INDOORS = MNT2.ZONE_CHANGED
MNT2.ZONE_CHANGED_NEW_AREA = MNT2.ZONE_CHANGED

function MNT2:ACHIEVEMENT_EARNED() -- no need to localize the skill if we can just trach the achivement :)
	self.MaxGroundSpeed = self.MaxGroundSpeed or 0
	self.MaxAirSpeed = self.MaxAirSpeed or 0
	local scannedSkill = 0
	if select(4, GetAchievementInfo(889)) then -- 150 skill
		self.MaxGroundSpeed = 100
		scannedSkill = 150
	elseif select(4, GetAchievementInfo(891)) then -- 75 skill
		self.MaxGroundSpeed = 60
		scannedSkill = 75
	end
	if select(4, GetAchievementInfo(892)) then -- 300 skill
		if self.db.char.mounts.flying[310] then
			self.MaxAirSpeed = 310
		else
			self.MaxAirSpeed = 280
		end
		scannedSkill = 300
	elseif select(4, GetAchievementInfo(890)) then -- 225 skill
		self.MaxAirSpeed = 60
		scannedSkill = 225
	end
	if scannedSkill > self.db.char.mount_skill then
		self.db.char.mount_skill = scannedSkill
		print(string.format(L['Mount Skill'], self.db.char.mount_skill))
	end
	if self.db.char.mount_skill == 300 then
		self:UnregisterEvent("ACHIEVEMENT_EARNED") -- no need to scan this if we are at max
	end
	self:GenerateMacro()
end

local movingState -- use to make the OnUpdate faster if we check with self.state.isMoving we do a tablelookup via a metatable function which is slower than just a local lookup
function MNT2:OnUpdate()
	if GetUnitSpeed('player') > 0 or IsFalling() then
		if not movingState then
			movingState = true
			self.state.isMoving = true
		end
	else
		if movingState then
			movingState = false
			self.state.isMoving = false
		end
	end
end

function MNT2:UNIT_SPELLCAST_START(event, unit, spellName)
	if unit == 'player' and self.db.char.mountSummonMode[self.ReverseLocalizedIDs[spellName]] == 'cast' then
		self:GenerateMacro()
	end
end

function MNT2:UNIT_SPELLCAST_SUCCEEDED(event, unit, spellName)
	if unit == 'player' and self.db.char.mountSummonMode[self.ReverseLocalizedIDs[spellName]] == 'instant' then
		self:GenerateMacro()
	end
end

function MNT2:COMPANION_LEARNED()
	self:MountScan() -- lets make sure one of the new mounts will be then next to summon
	self:ACHIEVEMENT_EARNED() -- update our maxspeeds (incase we got a 310 mount
	self:GenerateMacro() -- update macro
end

function MNT2:MountScan()
	local newMounts = 0
	for id = 1,GetNumCompanions("MOUNT") do
		local mountSpellID = select(3, GetCompanionInfo("MOUNT", id))
		--make sure its not already found
		if not self:MountExists(mountSpellID) then
			newMounts = newMounts + 1
			local numVals = select('#', self:GetMountType(mountSpellID))
			local mountType, mountSpeed
			for i=1,numVals,2 do
				mountType, mountSpeed = select(i, self:GetMountType(mountSpellID))
				self.db.char.mounts[mountType][mountSpeed] = self.db.char.mounts[mountType][mountSpeed] or {}
				self.db.char.mounts[mountType][mountSpeed][mountSpellID] = 5
				self.db.char.mountSummonMode[mountSpellID] = 'cast'
			end
		end
	end
	if newMounts > 0 then
		print(string.format(L['New Mounts'], newMounts))
	end
	self:LocalizeIDs()
	self:UpdateMountOptions()
	self:UpdateMainOptions()
end

function MNT2:MountExists(mountSpellID)
	for mountType, typeTable in pairs(self.db.char.mounts) do
		for mountSpeed, speedTable in pairs(typeTable) do
			if speedTable[mountSpellID] then
				return true
			end
		end
	end
	return false
end

function MNT2:LocalizeIDs() -- should do this everytime since we dont care what locale we are in exept for when we are casting (damn you blizzard)
	self.LocalizedIDs = {}
	self.ReverseLocalizedIDs = {}
	local TempOrderMounts = {}
	self.OrderMounts = {}
	for id in pairs(self.db.char.mountSummonMode) do
		local name = GetSpellInfo(id)
		self.LocalizedIDs[id] = name
		self.ReverseLocalizedIDs[name] = id
		TempOrderMounts[#TempOrderMounts+1] = name
	end
	table.sort(TempOrderMounts)
	for i, name in ipairs(TempOrderMounts) do
		self.OrderMounts[self.ReverseLocalizedIDs[name]] = i+300
	end
end

function MNT2:GenerateMacro()
	if InCombatLockdown() then return end
	local macrotable = (self.state.isMoving and self.db.profile.activeMacroSets.isMoving and self.db.profile.macrosets.isMoving) or (self.db.profile.activeMacroSets[self.state.inZone] and (not self.db.profile.instanceMacroSets[self.state.inZone] == not IsInInstance()) and self.db.profile.macrosets[self.state.inZone]) or self.db.profile.macrosets.DEFAULT
	local macroname
	for i=3,6 do
		if self.state[possibleStates[i]] and macrotable[possibleStates[i]] then
			macroname = macrotable[possibleStates[i]]
			break
		end
	end
	if not macroname then
		macroname = macrotable['ground']
	end
	local valid, macrotext = self:ParseMacro(self.db.global.macros[macroname])
	if valid then
		self:ApplyMacro(macrotext)
	else
		self:ApplyMacro('/script print("'..string.format(L['macro error'], tostring(macroname))..'")' )
	end
end

function MNT2:ApplyMacro(macrotext)
	EditMacro(GetMacroIndexByName("Mounted"), "Mounted", 1, macrotext)
end

--[==[
-- /script MNT2:StartSpeedScan()
local speed = 0-- store our speed here
local basespeed = 7 -- speed when running
local numSame =  15 -- how many consecutive frames of same speed do we need before we accept the speed
local frameCounter = 0
local flighttest = false -- ground vs flying
MNT2.SpeedScanFrame = CreateFrame('frame')
function MNT2:StartSpeedScan(fly)
	MNT2.SpeedScanFrame:SetScript("OnUpdate", MNT2.SpeedScanner)
	speed = 0
	frameCounter = 0
	if fly then
		flighttest = true
	else
		flighttest = false
	end
end

function MNT2:SpeedScanner()
	local curSpeed = GetUnitSpeed('player')
	if not InCombatLockdown() and curSpeed > 0 and IsMounted() and ((flighttest and  IsFlying()) or not flighttest)then
		if curSpeed > speed then
			speed = curSpeed
			frameCounter = 0
		elseif curSpeed < speed then
			frameCounter = 0
		elseif frameCounter == numSame then
			self:SetScript("OnUpdate", nil)
			local realspeed = math.ceil(speed/basespeed*100)-100
			local normalizedSpeed  = 0
			for speed in pairs(self.mountSpeeds) do
				if speed > normalizedSpeed and speed <= realspeed then
					normalizedSpeed = speed
				end
			end
			print('MNT2: Top Speed = '..self.mountSpeeds[normalizedSpeed])
		else
			frameCounter = frameCounter + 1
		end
	else
		frameCounter = 0
	end
end
]==]--
