--[[
	This Addon provides a big, dynamic library full of methods to instantly create unitframes for every need.

	"local *" and "E.*" explaination:
		"local" defines the private scope in LUA
		"E" is our 'class' name in this case and lets us access everything defined within.
	
	Since we want to access some variables across our AddOn, we have to throw them into this private(/public) scope (local(/E)).
	
	Important Lua-Garbage note:
		Setting the value of any table via {}, creates a NEW table and will contribute to generating garbage!!!
		To properly do this, empty a table with "wipe(t)" and settings values with: "table.val = newvalue"
		Not table = {val = newvalue}
]]-- 

-------------------------------------------------------------------------------------------------------------------------------
	-------------------------------------------------  Globals  -------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------

-- local E,CO,LOC,UF,AB,LT,AUR,TT = select(2, ...) -- Engine, Config, Locales, Unitframes, Actionbars, Layout, Auras, Tooltip
local E = select(2, ...) -- Engine
local CO,LOC,UF,AB,LT,AUR,TT,BC,B = E:LoadModules("Config", "Locale", "Unitframes", "Actionbars", "Layout", "Auras", "Tooltip", "Bar_Cast", "Blizzard")

E.E 									= 			CreateFrame("Frame") -- EngineEvent
E.InitComplete							=			false
E.Media									=			{}

E.Movers = {}
E.CVars = {}

E.UNIT_MAXLEVEL = 110

E.EventUnits = {
	["PLAYER_TARGET_CHANGED"]	=	"target",
	["PLAYER_FOCUS_CHANGED"]	=	"focus",
}

-------------------------------------------------------------------------------------------------------------------------------
	-------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------

local _
local C	= CreateFrame("Frame")

local UnitIsPlayer 	= UnitIsPlayer
local UnitClass 	= UnitClass
local UnitIsFriend 	= UnitIsFriend
local UnitIsEnemy 	= UnitIsEnemy

-----------------------------------------------------------------------------------------
-- Fix for BfA changes. We have to access some more functions via the namespace now!
-----------------------------------------------------------------------------------------
if E:IsLegionClient() then
	E.GetPlayerMapPosition		= GetPlayerMapPosition										----- Returns the units (arg2) X and Y position for mapID (arg1)
	E.GetCurrentMapID			= GetCurrentMapID								----- Returns the players map ID (needed for above) [Bugged and returning nothing in Beta-Build#26567]
else
	E.GetPlayerMapPosition		= C_Map.GetPlayerMapPosition								----- Returns the units (arg2) X and Y position for mapID (arg1)
	E.GetCurrentMapID			= C_Map.GetBestMapForUnit									----- Returns the player map ID when "player" is passed
end
-----------------------------------------------------------------------------------------

local EventUnit
local HighPowers = {0} -- Powers that should not be displayed separately

-- Prints AddOn messages to console/chat
function E:print(msg)
	print(E.PrintPrefix .. E.MessageColor .. " " .. msg .. E.ColorReset)
end

-- Prints AddOn debug-messages to console/chat. But without any concatentation to prevent errors caused by nil
-- Messages can be turned off by simply setting E.Debug to false
function E:debugprint(...)
	if E.Debug == true then
		print(...)
	end
end

-- Check if power shouldn't be displayed separated
function E:IsHighPower(powerId)
	for k,v in pairs(HighPowers) do
		if v == powerId then return true end
	end
	
	return false
end

-- Retrieve class color of unit (@param1)
function E:GetUnitClassColor(unit)
	return E.ClassColors[select(3,UnitClass(unit))]
end

function E:GetUnitPowerColor(unit)
	return E.PowerColors[UnitPowerType(unit)]
end

local UnitReactionDefault, UnitReactionColor, UnitReactionClassID, UnitReactionProfileTarget, UnitReactionReaction, ReturnTable
ReturnTable = {}
UnitReactionDefault = {1, 1, 1}

function E:GetUnitReactionColor(Unit)
	UnitReactionColor = UnitReactionDefault
	_, _, UnitReactionClassID = UnitClass(Unit)
	UnitReactionProfileTarget = CO.db.profile.unitframe.units.all.reactionColors
		
	UnitReactionReaction 		= UnitReaction(Unit, "player") -- Get reaction to player
	
	if UnitReactionReaction then
		if not UnitIsPlayer(Unit) then
			
			if not UnitIsFriend(Unit, "player") and UnitIsEnemy(Unit, "player") then
				UnitReactionReaction = 1
			end
			
			if UnitReactionReaction >= 5 then
				UnitReactionColor = UnitReactionProfileTarget["friendly"]
			elseif UnitReactionReaction == 4 then
				UnitReactionColor = UnitReactionProfileTarget["neutral"]
			elseif UnitReactionReaction == 3 then
				UnitReactionColor = UnitReactionProfileTarget["unfriendly"]
			else
				UnitReactionColor = UnitReactionProfileTarget["hostile"]
			end
		else
			if UnitReactionReaction >= 5 then  
				UnitReactionColor = E.ClassColors[UnitReactionClassID]
			else
				UnitReactionColor = UnitReactionProfileTarget["hostile"]
				-- UnitReactionColor = E.ReactionColors["hostile"]
			end
		end
	elseif UnitReactionClassID then
		UnitReactionColor = E.ClassColors[UnitReactionClassID]
	end
	
	ReturnTable.r = UnitReactionColor[1]
	ReturnTable.g = UnitReactionColor[2]
	ReturnTable.b = UnitReactionColor[3]
	
	
	return ReturnTable
end

function E:UnitChanged(Unit, PlaySound)
	if true then return end
	
	local UnitFirstUppercase = E:firstToUpper(Unit)
	local reactionColor = {0,0,0}

	local unitString, unitNumber = E:ExtractDigits(Unit)
	
	local UnitUpperRaw	= E:firstToUpper(unitString)
	
	if Unit == "targettarget" then UnitUpperRaw = "Tot" end
	
	local Frame			= E.Frames[UnitUpperRaw .. "Health" .. unitNumber .."Overlay"]
	
	--E:CacheAllFrameColors()
	if UnitExists(Unit) then
		E:UpdateUnitAllInfo(Unit)
		
		reactionColor = E:GetUnitReactionColor(Unit)
		UF:UpdateBarColor(Frame, {reactionColor.r, reactionColor.g, reactionColor.b})
		--E:ToggleFrame(E.Frames[UnitUpperRaw .. "BarCluster"], true, true)
		UF:UpdatePortrait(Unit)
		if Unit == "target" then UF:UpdatePortrait("targettarget") UF:UpdateTotColor() E:UpdateUnitAllInfo("targettarget") end
		if Unit == "focus" or Unit == "target" then UF:UpdatePortrait("focustarget") UF:UpdateFocusTargetColor() E:UpdateUnitAllInfo("focustarget") end
		if PlaySound then
			PlaySoundFile("Interface/AddOns/CUI/Sounds/target.ogg")
		end
	else
		--E:ToggleFrame(E.Frames[UnitUpperRaw .. "BarCluster"], false, true)
		if PlaySound then
			PlaySoundFile("Interface/AddOns/CUI/Sounds/targetlost.ogg")
		end
	end
end

function E:SkinButtonIcon(B)
	B:SetTexCoord(0.06,0.94,0.06,0.94)
end

function E:ToggleParty(State)
	E:ToggleFrame(E.Frames["PartyMainFrame"], State, false)
end

function E:SetVisibilityHandler(object)
	object:SetAttribute("_onstate-visible", [[
	if newstate == 1 then
		self:Show();
	else
		self:Hide();
	end
]]);
end

-- Toggle mover overlays and drag functionality
-- We can NOT simply show the whole thing, since we still have to hide them afterwards
-- this results in EVERY frame disappearing
function E:ToggleMover(state)	
	-- k: Mover Name, v: Mover Object
	for k,v in pairs(E.Movers) do	
		if state == true then
			v.Handle:EnableMouse(true)
			v.Handle:Show()
		else
			v.Handle:EnableMouse(false)
			v.Handle:Hide()
		end
		
	end
	
	-- Use Frame:IsEventRegistered("event") to check OnEvent visiblity for special frames
	
	-- Here we toggle special blizzard frames
	if state == true then
		-- Extra Button
		ExtraActionBarFrame:Show()
		ExtraActionBarFrame:SetAlpha(1) -- W.h.y
		ExtraActionButton1:Show()
		-- 
		BNToastFrame:Show()
		VehicleSeatIndicator:Show()
	else
		-- Extra Button
		ExtraActionBarFrame:Hide()
		ExtraActionBarFrame:SetAlpha(0) -- W.h.y
		ExtraActionButton1:Hide()
		BNToastFrame:Hide()
		VehicleSeatIndicator:Hide()
	end
	B:ToggleZoneAbility(state)
	B:ToggleAltPowerBar(state)
	UF:OverrideHolderVisibility(state)
	
	-- Castbar movers are a bit special
	BC:ToggleMovers(state)
end

-- Returns a registered mover from child object or name-string
function E:GetMover(C)
	if type(C) == "string" then
		return E.Movers[C .. "Mover"]
	else
		return E.Movers[E:GetFullFrameName(C) .. "Mover"]
	end
end

function E:RegisterMover(M, MName)
	E.Movers[MName] = M
end

function E:LoadMoverPositions(limit)
	local Conf = CO.db.profile.movers
	local ConfData = {}
	
	if limit then
		local mover = E:GetMover(limit)
		
		if mover then
			ConfData = Conf[mover:GetName()]
			E:RepositionMover(mover, ConfData["point"], ConfData["xOffset"], ConfData["yOffset"])
			
			return
		end
	end
	
	-- k: Mover Name - v: Mover Object
	for k,v in pairs(E.Movers) do
		ConfData = Conf[k]
		
		if ConfData then
			if v then
				E:RepositionMover(v, ConfData["point"], ConfData["xOffset"], ConfData["yOffset"])
			else
				E:print("Corrupt mover data found!")
			end
		end
	end
end

-- @PARAM1: Child target
-- @PARAM2: Localized mover name for user display
function E:CreateMover(C, LT, A, X, Y)
	local MNameRaw = E:GetFullFrameName(C)
	local MName = MNameRaw .. "Mover"
	
	if not A then A = "CENTER" end
	if not (X and Y) then X, Y = C:GetWidth(), C:GetHeight() if X <= 0 and Y <= 0 then X, Y = 50, 50 end end
	local M = E:NewFrame("Frame", MName, "LOW", X,Y, {"CENTER", E.Parent, "CENTER", 0, 0}, E.Parent)
	
	
	
	-- Position update function
	-- This is probably the only existing way to deal with constantly moving blizzard frames and also work for everything else ofc
	local function Mover_SetPosition(_, _, parent)
		-- if InCombatLockdown() then return end
		if parent ~= M then
			C:ClearAllPoints()
			C:SetParent(M)
			C:SetPoint(A, M, A, 0, 0)
		end
	end
	hooksecurefunc(C, "SetPoint", Mover_SetPosition)
	
	C:SetPoint("CENTER") -- Execute hook initially to make it work from the beginning [was eating some time to figure that out]
	
	-- Create Mover handle to interact with
	M.Handle = E:CreateMoverHandle(C, LT, A, X, Y)
	
	-- E:GetMover(FrameObjectOrNameAsString).Handle:Show() -- We can use this to access the handle at any time!
	
	M.Handle:SetScript("OnDragStart", function(self)
		local parent = M
		parent:SetMovable(true)
		self:SetClampedToScreen(false)
		parent:SetClampedToScreen(false)
		parent:StartMoving()
	end)
	M.Handle:SetScript("OnDragStop", function(self)
		local point, relativePoint, xOfs, yOfs
		local parent = M
		local title = MName
		
		parent:SetMovable(false)
		parent:StopMovingOrSizing()
		
		if not CO.db.profile.movers[title] then
			CO.db.profile.movers[title] = {}
		end
		local conf = CO.db.profile.movers[title]
		
		
		point, _, relativePoint, xOfs, yOfs = parent:GetPoint(parent:GetNumPoints())
		conf["point"] 			= point
		conf["relativePoint"] 	= relativePoint
		conf["xOffset"] 		= xOfs
		conf["yOffset"] 		= yOfs
	end)
	
	-- Internal register
	-- MoverObject, MoverName
	E:RegisterMover(M, MName)
	
	return M
end

function E:RepositionMover(M, Point, OffsetX, OffsetY)
	E:RepositionFrame(M, Point, OffsetX, OffsetY, E.Parent)
end

-- Since we move frames via movers, which are basically frames, the frames we want to move with are parented to the mover
-- By later "copying" the translations made to the overlay to the base parent, we can move the whole thing
function E:CreateMoverHandle(C, LT, A, X, Y)
	local MH = E:NewFrame("Frame", "Handle", "HIGH", X,Y, {A, C, A, 0, 0}, C)
	local RGB = E:GetUnitClassColor("player")
	MH:EnableMouse(true)
	MH:SetMovable(true)
	MH:RegisterForDrag("LeftButton")
	
	MH:SetBackdrop({bgFile = "Interface/Tooltips/UI-Tooltip-Background", 
		edgeFile = [[Interface\Buttons\WHITE8X8]], 
		edgeSize = 1, 
		tile = true, tileSize = 16});
	MH:SetBackdropColor(RGB[1], RGB[2], RGB[3], 1)
	MH:SetBackdropBorderColor(0.15, 0.15, 0.15, 1)
	MH:SetFrameLevel(100)
	
	-- Init font overlay
	MH.Name = MH:CreateFontString(nil, "ARTWORK")
	E:InitializeFontFrame(MH.Name, "ARTWORK", "FRIZQT__.TTF", 14, {1,1,1}, 1, {0,0}, "", 250, Y, MH, "CENTER", {1,1})
	MH.Name:ClearAllPoints()
	MH.Name:SetPoint("CENTER", MH, 'CENTER', 5, -5)
	
	MH.Name:SetText(LT) -- Set provided name
	
	MH:Hide()
	
	return MH
end

-- Update Mover dimensions based on parent
function E:UpdateMoverDimensions(C)
	local M = E:GetMover(C)
	
	if not M then return end
	
	M:SetSize(C:GetWidth(), C:GetHeight())
	M.Handle:SetSize(C:GetWidth(), C:GetHeight())
end

E.StatusBars = {}
function E:RegisterStatusBar(Bar)
	-- Expects Bar.Unit and Bar.Type to already be present
	table.insert(E.StatusBars, Bar)	
end

-- Method to initially set all required CVars on an empty/default profile
function E:InitCVars()
	for k,v in pairs(CO.db.profile.CVars) do
		E:RegisterCVar(k,v)
	end
	
	E:LoadRegisteredCVars()
end

function E:RegisterCVar(CVar, value)
	E.CVars[CVar] = value
end

function E:LoadRegisteredCVars()
	for k,v in pairs(E.CVars) do
		SetCVar(k, v)
	end
end

function E:UpdateGroup(type)
	if not type then return end
	
	local num = 5
	local classID
	
	
	if type == "raid" then num = 40 end
	for i=1, num do
		if UnitExists(type .. i) then
			if select(3, UnitClass(type .. i)) ~= nil then
				outOfRangeAlpha, inRangeAlpha = UF:GetRangeAlpha()
				
				UF:UpdateUnit(type .. i, nil, nil, outOfRangeAlpha, inRangeAlpha)
				-- UF:UpdateBarColor(E.Frames[E:firstToUpper(type) .. "Health" .. i .. "Overlay"], E.ClassColors[classID])
				-- UF:UpdateBarColor(E.Frames[E:firstToUpper(type) .. "Power" .. i .. "Overlay"], E.PowerColors[UnitPowerType(type .. i)])
				
				UF:UpdateRoleIcon(type .. i)
			end			
		end
	end
	
	HidePartyFrame() -- Remove Blizz frames
end

------------------------
-- This method handles EVERY Unitframe update we possibly could need.
----- @PARAM
--------	info(str):			Type of the update to perform (power, health, name, level, ...)
--------	unit(str):			The unit to update
--------	flags(str):			Additional info, such as a change of the power type
----- @RETURN
--------	NONE
------------------------
function E:UpdateUnitInfo(info, unit, flags)
	
	-- Prevent execution for unsupported frames
	if E:DoesStringPartExist(unit, "raidpet") then return end
	
	local unitString, unitNumber = E:ExtractDigits(unit)
	
	local UnitUpperRaw	= E:firstToUpper(unitString)
	local UnitUpper		= E:firstToUpper(unit)
	local InfoUpper		= E:firstToUpper(info)
	
	if unit == "targettarget" then UnitUpperRaw = "Tot" end
	
	local Frame			= E.Frames[UnitUpperRaw .. InfoUpper .. unitNumber .."Overlay"]
	local TextFrame		= E.Frames[UnitUpperRaw .. InfoUpper .. unitNumber .."Font"]
	local InfoMax, InfoCurrent, InfoType, InfoReadable, PowerType, unitClass, InfoRoundedPct
	
	if not Frame and not TextFrame then return end
	if not Frame and not TextFrame then E:debugprint(UnitUpperRaw .. InfoUpper .. unitNumber .. " is not supported!") return end
	
	unitClass 		= select(3,UnitClass(unit))
	PowerType 		= UnitPowerType(unit)
	
	if info == "health" then
		InfoMax 		= UnitHealthMax(unit)
		InfoCurrent 	= UnitHealth(unit)
		E:SetFontInfo(TextFrame,nil,nil,nil, UF.FontColors[info])
		end
	if info == "power" or info == "powertype" then
		InfoMax 		= UnitPowerMax(unit)
		InfoCurrent 	= UnitPower(unit)
		E:SetFontInfo(TextFrame,nil,nil,nil, E.PowerColors[PowerType])
	end
	if info == "level" then
		if not UnitIsBattlePet(unit) then
			InfoCurrent = UnitLevel(unit)
			
			if InfoCurrent == -1 then
				InfoCurrent = "Boss"
			end E:SetFontInfo(TextFrame,nil,nil,nil, UF.FontColors[info])
		else
			InfoCurrent = UnitBattlePetLevel(unit)
		end
	end
	if info == "name" then
		InfoCurrent 	= UnitName(unit)
		E:SetFontInfo(TextFrame,nil,nil,nil, E.ClassColors[unitClass])
	end
	
	if flags == "powertype" then
		if PowerType then -- Fix for black bars
			UF:UpdateBarColor(Frame, E.PowerColors[PowerType])
		end
	end
	if info ~= "level" and info ~= "name" then
		Frame:SetMinMaxValues(0, InfoMax)
		Frame:SetValue(InfoCurrent)
	end
	InfoReadable = InfoCurrent
	if type(InfoCurrent) == "number" and info ~= "level" then
		if InfoMax <= 0 then
			InfoRoundedPct = ""
		else
			InfoRoundedPct = E:Round(InfoCurrent / InfoMax * 100, 2) .."%"
			if info == "power" then
				if not E:IsHighPower(PowerType) then
					InfoRoundedPct = ""
				end
			end
		end
		InfoReadable = E:readableNumber(InfoCurrent, 2) .. "\n" .. InfoRoundedPct
		--InfoReadable = E:readableNumber(InfoCurrent, 2)
	end
	
	if TextFrame then
		E:UpdateFont(TextFrame)
		TextFrame:SetText(InfoReadable)
	end
end

function E:UpdateUnitAllInfo(unit)
	
	local NumToCreate, AddStr

	for k,v in pairs(UF.RequiredFonts) do
		NumToCreate 	= UF.ToCreate[unit] or 1
		AddStr			= ""
			
		for i=1,NumToCreate do
			if NumToCreate > 1 then AddStr = i else AddStr = "" end
			E:UpdateUnitInfo(v, unit .. AddStr)
			if v == "power" then
				E:UpdateUnitInfo(v, unit .. AddStr, "powertype")
			end
		end
	end
end

function C:Init()
	
	CO, TT, UF, AUR, BC, B = E:LoadModules("Config", "Tooltip", "Unitframes", "Auras", "Bar_Cast", "Blizzard")
	E:InitCVars()
	
	local arg1
	
	E.E:SetScript("OnEvent",function(self,event, ...)

		-- BfA Version
		--if event == "UNIT_POWER_UPDATE" then E:UpdateUnitInfo("power", arg1); UF:UpdateAlternatePower() end
		-- Legion Version
		if select(3, UnitClass("player")) ~= 6 then
			if E:IsLegionClient() then
				if (event == "UNIT_POWER" or event == "RUNE_POWER_UPDATE") and ... and ... == "player" then UF:UpdateAlternatePower() end
			else
				if event == "UNIT_POWER_UPDATE" or event == "RUNE_POWER_UPDATE" then UF:UpdateAlternatePower() end
			end
		end

		if event == "UNIT_DISPLAYPOWER" or event == "PLAYER_SPECIALIZATION_CHANGED" or event == "SPELLS_CHANGED" then UF:SetupAlternatePower(); end
	end)
end

E:AddModule("Core", C)