

--------------------
-- What's Next? ----
----- by Strucker --
--------------------
local _
local WhatsNext = LibStub('AceAddon-3.0'):NewAddon('WhatsNext', 'AceEvent-3.0', 'AceConsole-3.0');
_G.WhatsNext = WhatsNext;
local SML = LibStub("LibSharedMedia-3.0");
local inCombat = false
local optionsFrames = {}

WhatsNext.CLASSES = {
	"WARRIOR",
	"MONK",
	"DEATHKNIGHT",
	"SHAMAN",
	"PRIEST",
	"ROGUE",
	"HUNTER",
	"PALADIN",
	"WARLOCK",
	"MAGE",
	"DRUID"
}
WhatsNext.CLASS_COLOR_CODES = {
	["WARRIOR"] = "|cFFC79C6E",
	["MONK"] = "|cFF558A84",
	["DEATHKNIGHT"] = "|cFFC41F3B",
	["SHAMAN"] = "|cFF0070DE",
	["PRIEST"] = "|cFFFFFFFF",
	["ROGUE"] = "|cFFFFF569",
	["HUNTER"] = "|cFFABD473",
	["PALADIN"] = "|cFFF58CBA",
	["WARLOCK"] = "|cFF9482C9",
	["MAGE"] = "|cFF69CCF0",
	["DRUID"] = "|cFFFF7D0A"
}
function WhatsNext_AddOptionsScreen(frame)
	tinsert(optionsFrames,frame)
end

function WhatsNext_ShowOptionsScreen(frame)
	for i, optionFrame in ipairs(optionsFrames) do
		local hideScript = optionFrame:GetScript("OnHide")
		optionFrame:SetScript("OnHide", function() end)
		optionFrame:Hide()
		optionFrame:SetScript("OnHide", hideScript)
	end
	
	WhatsNext.showFrame = frame
end

function WhatsNext_ClearFocus()
	if GetCurrentKeyBoardFocus() and GetCurrentKeyBoardFocus().ClearFocus then
		GetCurrentKeyBoardFocus():ClearFocus()
	end
end
WhatsNext.defaultCastSequences = {}
for _, class in pairs(WhatsNext.CLASSES) do
	WhatsNext.defaultCastSequences[class] = {}
	for i = 1,(class=="DRUID" and 4 or 3) do
		WhatsNext.defaultCastSequences[class][i] = {}
	end
end

function GetItemID(itemName)
	return select(5, string.find(select(2, GetItemInfo(itemName) ) or "", "|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?"))
end

function WhatsNext:SetupLogicSets()
	
	self.db.profile.castSequences = self.db.profile.castSequences or {}
	local castSequences = self.db.profile.castSequences
	
	for _, class in pairs(self.CLASSES) do
		castSequences[class] = castSequences[class] or {}
		for i = 1,(class=="DRUID" and 4 or 3) do
			castSequences[class][i] = castSequences[class][i] or {}
			
			-- This is to save any user modifications made before the logic set manager was implemented.
			if castSequences[class][i][1] and castSequences[class][i][1].spell then
				castSequences[class][i] = {[1] = nil, [2] = castSequences[class][i]}
				castSequences[class][i][2].name = "Unnamed"
			end
			
			castSequences[class][i][1] = self.defaultCastSequences[class][i]
			castSequences[class][i][1].name = "Default"
		end
	end
end

function WhatsNext:OnInitialize()
	self:RegisterOptions()
	self:SetupLogicSets()
end

local function GetBattleType()
	local inInstance, instanceType = IsInInstance()
	if not inInstance then
		return "Open World"
	elseif instanceType == "pvp" then
		return "Battleground"
	elseif instanceType == "arena" then
		return "Arena"
	elseif instanceType == "party" then
		return "Dungeon"
	elseif instanceType == "raid" then
		return "Raid"
	end
end

function WhatsNext:UpdateActiveLogicSet()
	if not self.db.profile.castSequences then
		self:SetupLogicSets()
	end
	
	local class, spec = select(2,UnitClass("player")), GetSpecialization();
	local sets = self.db.profile.castSequences[class][spec]
	if sets then
		local battleType = GetBattleType()
		self.castSequence = sets[1] 
		for i = 2, #sets do
			if sets[i].use == battleType then
				self.castSequence = sets[i]
				break
			elseif sets[i].use == "General" then
				self.castSequence = sets[i]
			end
		end
	else
		self.castSequence = {}
	end
	WhatsNext_ExamineLogicSet(self.castSequence)
	self:ApplyExtensionSettings()
end


function WhatsNext:ApplyMainSettings()
	self:UpdateActiveLogicSet()
	self:ApplyDisplaySettings()
end

function WhatsNext:ApplyDisplaySettings()
	local frame = WhatsNext.frame
	if not self.db.profile.enabled or (not inCombat and self.db.profile.hideOutOfCombat) then
		frame:Hide()
	else
		frame:Show()
	end
	
	frame:EnableMouse(not self.db.profile.locked)
	frame:ClearAllPoints()
	local color = self.db.profile.backgroundColor
	frame.bg:SetTexture(color.r,color.g,color.b,color.a)
	
	frame:SetScale(self.db.profile.scale)
	local scale = frame:GetEffectiveScale()
	local pos = self.db.profile.pos
	frame:SetPoint(pos.p1,nil,pos.p2,pos.x/scale,pos.y/scale)
	
	scale = WhatsNext_CastLogicDisplay:GetEffectiveScale()
	pos = self.db.profile.configPos
	WhatsNext_CastLogicDisplay:ClearAllPoints()
	WhatsNext_CastLogicDisplay:SetPoint(pos.p1,nil,pos.p2,pos.x/scale,pos.y/scale)
	WhatsNext_CastBlockEditDisplay:ClearAllPoints()
	WhatsNext_CastBlockEditDisplay:SetPoint(pos.p1,nil,pos.p2,pos.x/scale,pos.y/scale)	
	WhatsNext_LogicSetManagementDisplay:ClearAllPoints()
	WhatsNext_LogicSetManagementDisplay:SetPoint(pos.p1,nil,pos.p2,pos.x/scale,pos.y/scale)	
	
	color = self.db.profile.textColor
	frame.spellName:SetFont(SML:Fetch('font', self.db.profile.font), 16, self.db.profile.outline)
	frame.spellName:SetTextColor(color.r, color.g, color.b, color.a)
	
	frame:UpdateLayout()
end

function WhatsNext_Display_Update(frame)
	local time = GetTime()
	if not frame.lastUpdate  or frame.lastUpdate + WhatsNext.db.profile.timeBetweenUpdates <= time then
		WhatsNext.nextText, WhatsNext.nextIcon, WhatsNext.nextType, WhatsNext.timeTill = WhatsNext:GetNextCast()
		if WhatsNext.nextText ~= frame.lastNext or WhatsNext.nextIcon ~= frame.lastNextIcon then
			frame.spellName:SetText(WhatsNext.nextText)
			frame.icon:SetTexture(WhatsNext.nextIcon)
			frame:UpdateWidth()
			frame.lastNextText = WhatsNext.nextText
			frame.lastNextIcon = WhatsNext.nextIcon
			WhatsNext:FireNextSpellChanged()
		end
		frame.castTime = WhatsNext.timeTill and WhatsNext.timeTill + time or 1.5
		frame.lastUpdate = time
		
	end
	
	WhatsNext:FireNextSpellChanged()
	frame:UpdateBorderColor(frame.castTime and frame.castTime - time or 1.5)
end
		
local function WhatsNext_ExamineLogicBlock(logicBlock)
	local err = ""
	if not logicBlock.type then
		logicBlock.type = "spell"
	end
	
	local type = logicBlock.type
	local entry = logicBlock[type]
	if not entry or strtrim(entry) == "" then
		return false, "No " .. type .. " entry found."
	end
	
	for i = #logicBlock.checks,1,-1 do
		if not WhatsNext:ContainsConditionalStatement(logicBlock.checks[i].type) then
			tremove(logicBlock.checks,i)
			err = "Unknown conditional statement removed. " ..  logicBlock.checks[i].type .. ". " .. err
		end
	end
	
	
	return true, err
end
		
function WhatsNext_ExamineLogicSet(logicSet)
	local rtn,err
	for i = #logicSet,1,-1 do
		rtn,err = WhatsNext_ExamineLogicBlock(logicSet[i])
		if not rtn then
			tremove(logicSet,i)
			print("Logic Block " .. i .. " has been removed.",err)
		elseif err and err ~= "" then
			print("Logic Block " .. i .. " has been salvaged.",err)
		end
	end
end
			
local function GetMovementIcon(movement)
	return "Interface\\Icons\\INV_Misc_QuestionMark"
end
			
function WhatsNext:GetNextCast()
	self:ResetTestChecks()
	local tempTimeTillCast, timeTillCast, start, duration, enabled, name = nil, 1.61, ""
	local nextText, nextIcon, nextType = "What's Next?", "Interface\\Icons\\INV_Misc_QuestionMark", "text"
	local tempText, tempIcon
	if self.castSequence then
		for i,block in ipairs(self.castSequence) do
			if not block.type or block.type == "spell" then
				start, duration = GetSpellCooldown(block.spell)			
				tempText,_,tempIcon = GetSpellInfo(block.spell)
				if IsUsableSpell(tempText) and IsSpellInRange(tempText,"target") ~= 0 then
					tempTimeTillCast = not start and 99 or start == 0 and 0 or start + duration - GetTime()
				else 
					tempTimeTillCast = 999
				end
			elseif block.type == "item" then
				tempTimeTillCast = 999
				local id = GetItemID(block.item)
				if id then
					start, duration, enabled = GetItemCooldown(id)
					tempText, _, _, _, _, _, _, _, _, tempIcon = GetItemInfo(block.item) 
					if enabled ~= 0 and (not IsEquippedItem(tempText) and IsEquippableItem(tempText) or GetItemSpell(tempText)) and IsUsableItem(tempText) and IsItemInRange(tempText,"target") ~= 0 then
						tempTimeTillCast = not start and 99 or start == 0 and 0 or start + duration - GetTime()
					end
				end
			elseif block.type == "macro" then
				tempText, tempIcon = GetMacroInfo(block.macro);
				if tempText and tempIcon then
					tempTimeTillCast = 0
				else
					tempTimeTillCast = 999
				end
			elseif block.type == "movement" then
				tempText = block.movement
				tempIcon = GetMovementIcon(block.movement)
				tempTimeTillCast = 0
			elseif block.type == "note" then
				tempText = block.note
				tempIcon = "Interface\\Icons\\INV_MISC_NOTE_05"
				tempTimeTillCast = 0
			end
			
			if tempTimeTillCast + .1 < timeTillCast and self:TestChecks(block.checks) then
				timeTillCast, nextText, nextIcon, nextType = tempTimeTillCast, tempText, tempIcon, block.type or "spell"
				if timeTillCast <= .1 then
					break
				end
			end
		end	
	end
	return nextText, nextIcon, nextType, timeTillCast
end

-------------------
-- Set up events
-------------------

local events = {}
function events:PLAYER_REGEN_ENABLED() 
	inCombat = false
	if WhatsNext.db.profile.hideOutOfCombat then
		WhatsNext.frame:Hide()
	end
end

function events:PLAYER_REGEN_DISABLED() 
	inCombat = true
	if WhatsNext.db.profile.enabled then
		WhatsNext.frame:Show()
	end
end

function events:PLAYER_LOGIN()
	WhatsNext:ApplyDisplaySettings()
end

function events:PLAYER_SPECIALIZATION_CHANGED(a,b,c)
	WhatsNext:UpdateActiveLogicSet()
end

function events:PLAYER_ENTERING_WORLD()
	WhatsNext:UpdateActiveLogicSet()
end

local function EventFunc(self, event, ...)
	events[event](event, ...)
end
local eventHandler = CreateFrame("Frame", nil, WorldFrame)
eventHandler:SetScript("OnEvent", EventFunc)
for eventname in pairs(events) do eventHandler:RegisterEvent(eventname) end	


eventHandler:SetScript("OnUpdate", function()
	if WhatsNext.showFrame then
		WhatsNext.showFrame:Show()
		WhatsNext.showFrame = nil
	end
end)