---------------------------------------------------------------
-- Mouse.lua: Smart camera control and mouse function wrappers
---------------------------------------------------------------
-- Removes the need of holding right click to control camera.
-- Allows user to mouseover their character to control camera.
-- Toggles off on targeted spells, InteractUnit and pickups.

local _, db = ...
---------------------------------------------------------------
local TEXTURE, ICONS, Settings = db.TEXTURE, db.ICONS
---------------------------------------------------------------
local 	WorldFrame, UIParent, GameTooltip, Core = 
		WorldFrame, UIParent, GameTooltip, ConsolePort
---------------------------------------------------------------
-- Camera functions
local 	GetMouseFocus, HasCursorItem, SpellIsTargeting, IsMouseButtonDown, IsMouselooking = 
		GetMouseFocus, GetCursorInfo, SpellIsTargeting, IsMouseButtonDown, IsMouselooking
---------------------------------------------------------------
-- Highlight functions
local 	HighlightStart, HighlightStop = 
		TargetPriorityHighlightStart, TargetPriorityHighlightEnd
---------------------------------------------------------------
-- Mouse functions
local 	UnitGUID, UnitIsDead, UnitCanAttack, UnitExists, CanLootUnit, GetCursorPosition = 
		UnitGUID, UnitIsDead, UnitCanAttack, UnitExists, CanLootUnit, GetScaledCursorPosition
---------------------------------------------------------------
local 	Camera, numTap, modTap, timer, interactTimer, highlightTimer =
		CreateFrame("Frame", "ConsolePortCamera", UIParent), 0, 0, 0, 0, 0
Camera.Start = MouselookStart
Camera.Stop = MouselookStop
---------------------------------------------------------------
Camera.Locker = CreateFrame("Frame", "$parentLocker", Camera)
Camera.Locker:SetPoint("CENTER", UIParent, 0, 0)
Camera.Locker:SetSize(70, 180)
Camera.Locker:Hide()
---------------------------------------------------------------
Camera.Edge = CreateFrame("Frame", "$parentEdge", Camera)
Camera.Edge:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 50, -50)
Camera.Edge:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -50, 50)
Camera.Edge:Hide()
---------------------------------------------------------------
Camera.Deadzone = CreateFrame("Frame", "$parentDeadzone", Camera)
Camera.Deadzone:SetPoint("CENTER", UIParent, 0, 0)
Camera.Deadzone:SetSize(4, 4)
Camera.Deadzone:Hide()
---------------------------------------------------------------
Camera.BlockUI = CreateFrame("Frame", "ConsolePortUIBlocker", UIParent)
Camera.BlockUI:SetAllPoints()
Camera.BlockUI:SetFrameStrata("FULLSCREEN_DIALOG")
Camera.BlockUI:EnableMouse(true)
Camera.BlockUI:Hide()
---------------------------------------------------------------
local blockCursor, cameraMode, isMouseDown, isCentered, isOutside, isTargeting, hasItem, hasWorldFocus

function Camera:Toggle() if cameraMode then self:Stop() else self:Start() end end
function Camera:IsCentered() return self.Locker:IsMouseOver() and not self.Deadzone:IsMouseOver() end
function Camera:ShouldStart() return not isTargeting and not isMouseDown and not hasItem and self:IsCentered() and hasWorldFocus end
function Camera:OnJump() if Settings.mouseOnJump and not isTargeting and not hasItem then Camera:Start() end end

function Camera:OnUpdate(elapsed)
	cameraMode = IsMouselooking()
	isTargeting = SpellIsTargeting()
	isMouseDown = IsMouseButtonDown(1)
	hasItem = HasCursorItem()
	hasWorldFocus = GetMouseFocus() == WorldFrame
	---------------
	self.BlockUI:SetShown(cameraMode)
	---------------
	interactTimer = interactTimer > 0 and interactTimer - elapsed or 0
end

function Camera:CheckCursor()
	if cameraMode and hasItem then
		self:Stop()
	end
end

function Camera:CheckCenter()
	if not isCentered and self:ShouldStart() then
		self:Start()
		isCentered = true
	elseif not self.Locker:IsMouseOver() and isCentered then
		isCentered = false
	end
end

function Camera:CheckDoubleTap(elapsed)
	timer = timer + elapsed
	if numTap > 1 then
		self:Toggle()
		modTap = 0
		numTap = 0
	end
	if timer > 0.25 then
		numTap = numTap > 0 and numTap - 1 or 0
		timer = timer - 0.25
	end
end

function Camera:CheckEdge()
	if not self.Edge:IsMouseOver() and not isOutside and hasWorldFocus then
		isOutside = true
		self:Start()
	elseif self.Edge:IsMouseOver() then
		isOutside = false
	end
end

function Camera:HighlightNoTarget(elapsed)
	highlightTimer = highlightTimer + elapsed
	if highlightTimer > 3 then
		if not UnitExists("target") then
			HighlightStart()
		end
		highlightTimer = highlightTimer - 3
	end
end

function Camera:HighlightAlways(elapsed)
	highlightTimer = highlightTimer + elapsed
	if highlightTimer > 3 then
		HighlightStart()
		highlightTimer = highlightTimer - 3
	end
end

function Camera:OnEvent(event, ...)
	local modifier, down = ...
	if down == 1 then
		if modifier then
			timer = 0
			numTap = modTap == modifier and numTap + 1 or 1
			modTap = modifier
		end
	end
end

function Camera:OnAction() interactTimer = 0.5 end

function Camera:OnInteract()
	local guid, hasLoot = UnitGUID("target")
	if guid then
		hasLoot = CanLootUnit(guid)
	end
	if hasLoot then
		blockCursor = true
		Camera:Start()
	else
		Camera:Stop()
	end
end

function Camera:OnStop()
	if blockCursor then
		blockCursor = nil
		Camera:Start()
	end
end

function Camera:OnRightClick()
	if interactTimer > 0 then
		Camera:Start()
	else
		blockCursor = nil
	end
end

-- Get rid of mouselook when trying to interact with mouse
hooksecurefunc("MouselookStop", Camera.OnStop)
-- InteractUnit removes mouse look, restart if target has loot
hooksecurefunc("InteractUnit", Camera.OnInteract)
-- Releasing 'right click' should remove the cursor block
hooksecurefunc("TurnOrActionStop", Camera.OnRightClick)
-- Hook jump to use it as a camera trigger
hooksecurefunc("JumpOrAscendStart", Camera.OnJump)
-- Get rid of mouselook when moving the pet
hooksecurefunc("PetMoveTo", Camera.Stop)
-- Hook action usage to manipulate mouselook
hooksecurefunc("UseAction", Camera.OnAction)

---------------------------------------------------------------
-- Mouse function wrappers in case of extended functionality
---------------------------------------------------------------

function Core:StopCamera() Camera:Stop() end
function Core:StartCamera() Camera:Start() end

---------------------------------------------------------------
-- Toggle smart mouse behaviour on/off
---------------------------------------------------------------
function Core:UpdateCameraDriver()
	Settings = Settings or db.Settings

	Camera:SetScript("OnEvent", nil)
	Camera:SetScript("OnUpdate", Camera.OnUpdate)
	Camera:UnregisterEvent("MODIFIER_STATE_CHANGED")

	numTap, modTap, timer, interactTimer, highlightTimer = 0, 0, 0, 0, 0

	if not Settings.disableSmartMouse then
		Camera:HookScript("OnUpdate", Camera.CheckCursor)

		if Settings.mouseOnCenter then
			Camera:HookScript("OnUpdate", Camera.CheckCenter)
		end
		if Settings.preventMouseDrift then
			Camera:HookScript("OnUpdate", Camera.CheckEdge)
		end
		if Settings.doubleModTap then
			Camera:SetScript("OnEvent", Camera.OnEvent)
			Camera:RegisterEvent("MODIFIER_STATE_CHANGED")
			Camera:HookScript("OnUpdate", Camera.CheckDoubleTap)
		end
	end

	if Settings.alwaysHighlight == 1 then
		Camera:HookScript("OnUpdate", Camera.HighlightNoTarget)
	elseif Settings.alwaysHighlight == 2 then
		Camera:HookScript("OnUpdate", Camera.HighlightAlways)
	end

	Camera.lockOnLoot = db.Mouse.Events.LOOT_OPENED
end

---------------------------------------------------------------
-- This handle is used to determine whether a dedicated button
-- should be used to cast spells or to interact with mouseover.
-- The behaviour alters itself depending on whether the button
-- is bound to a healing spell or harmful spell.
---------------------------------------------------------------
local Mouse = CreateFrame("Frame", "ConsolePortMouseHandle", UIParent, "SecureHandlerStateTemplate, SecureHandlerAttributeTemplate")
Mouse:Execute([[
	---------------------------------------------------------------
	id = 0
	---------------------------------------------------------------
	isEnabled = true
	target = false
	---------------------------------------------------------------

	UpdateTarget = [=[
		target = ...

		if not isEnabled then
			return
		end

		local interact, loot = false, false

		if target ~= "hover" and ( target == "enemy" or target == "friend" ) then
			local helpful = self:RunAttribute("IsHelpfulAction", id)
			local harmful = self:RunAttribute("IsHarmfulAction", id)

			if 	( not helpful and not harmful ) or
				( target == "friend" and helpful ) or
				( target == "enemy" and harmful ) then
				self:ClearBindings()
			end
		elseif target == "loot" then
			loot = true
		else
			interact = true
		end

		if ( interact or loot ) and USE then
			local key = GetBindingKey(USE)
			if key then
				if loot then
					self:SetBinding(true, key, "INTERACTTARGET")
				else
					self:SetBinding(true, key, "TURNORACTION")
				end
			end
		end
		self:CallMethod("TrackUnit", target)
	]=]

	UpdateAttribute = [=[
		local attribute, value = ...
		if attribute == "state-targetstate" then
			self:Run(UpdateTarget, value)
		elseif attribute == "override" then
			isEnabled = value
			if not isEnabled then
				self:ClearBindings()
			else
				self:Run(UpdateTarget, Target)
			end
		end
	]=]
]])
---------------------------------------------------------------
local Focus = CreateFrame("Button", "$parentFocus", Mouse, "SecureActionButtonTemplate")
Focus:SetAttribute("type", "focus")
Focus:SetAttribute("unit", "mouseover")
Mouse:SetFrameRef("Focus", Focus)
Mouse:Execute([[ Focus = self:GetFrameRef("Focus") ]])
---------------------------------------------------------------
Mouse:SetAttribute("_onattributechanged", "self:Run(UpdateAttribute, name, value)")

Mouse:SetPoint("CENTER", 0, -300)
Mouse:SetSize(300, 64)
Mouse:SetAlpha(0)

Mouse.Line = Mouse:CreateTexture("$parentLine", "ARTWORK")
Mouse.Line:SetAtlas("legioninvasion-title-bg")
Mouse.Line:SetPoint("BOTTOM")
Mouse.Line:SetSize(300, 100)
Mouse.Line:SetVertexColor(1, 0.75, 0.75)

Mouse.Button = Mouse:CreateTexture("$parentButton", "ARTWORK")
Mouse.Button:SetPoint("LEFT", 50, 16)
Mouse.Button:SetSize(32, 32)

Mouse.Text = Mouse:CreateFontString("$parentText", "OVERLAY", "MovieSubtitleFont")
Mouse.Text:SetPoint("CENTER", 0, 16)

Mouse.FadeIn = db.UIFrameFadeIn
Mouse.FadeOut = db.UIFrameFadeOut

function Mouse:CheckLoot(elapsed)
	local alpha = self:GetAlpha()
	local guid, hasLoot, canLoot = UnitGUID("target")
	if guid then
		hasLoot, canLoot = CanLootUnit(guid)
	end
	if hasLoot and canLoot then
		self.Text:SetText(LOOT)
		if self.fade ~= "in" then
			self:FadeIn(0.1, alpha, 1)
			self.fade = "in"
		end
	else
		if self.fade ~= "out" then
			self:FadeOut(0.2, alpha, 0)
			self.fade = "out"
		end
	end
end

function Mouse:CheckHover(elapsed)
	local guid, hasLoot, canLoot = UnitGUID("mouseover")
	local exists = UnitExists("mouseover")
	local isDead, isEnemy = UnitIsDead("mouseover"), UnitCanAttack("player", "mouseover")
	if guid then
		hasLoot, canLoot = CanLootUnit(guid)
	end
	if hasLoot and canLoot then
		self.Text:SetText(LOOT)
	elseif isEnemy and not isDead then
		self.Text:SetText(ATTACK)
	elseif exists and ( isDead or not isEnemy ) then
		self.Text:SetText(UNIT_FRAME_DROPDOWN_SUBSECTION_TITLE_INTERACT)
	end
	if self.fade ~= "in" then
		self:FadeIn(0.1, self:GetAlpha(), 1)
		self.fade = "in"
	end
end

function Mouse:TrackUnit(unitType)
	local hasScript = self:GetScript("OnUpdate")
	if not hasScript and unitType == "loot" then
		self:SetScript("OnUpdate", self.CheckLoot)
	elseif not hasScript and unitType == "hover" then
		self:SetScript("OnUpdate", self.CheckHover)
	else
		self:SetScript("OnUpdate", nil)
		if self.fade ~= "out" then
			self:FadeOut(0.5, self:GetAlpha(), 0)
			self.fade = "out"
		end
	end
end

---------------------------------------------------------------
-- Cursor trail for interact button
---------------------------------------------------------------
local Trail = CreateFrame("Frame", "ConsolePortCursorTrail", UIParent)
Trail:SetFrameStrata("TOOLTIP")
Trail:SetSize(32,32)
Trail.Texture = Trail:CreateTexture(nil, "OVERLAY", nil, 7)
Trail.Texture:SetAllPoints(Trail)
Trail.Icon = Trail:CreateTexture(nil, "OVERLAY")
Trail.Icon:SetPoint("LEFT", Trail, "RIGHT", -6, 0)
Trail.Icon:SetSize(24, 24)
Trail.Texture2 = Trail:CreateTexture(nil, "OVERLAY", nil, 7)
Trail.Texture2:SetSize(32, 32)
Trail.Texture2:SetPoint("TOP", Trail, "BOTTOM", 0, 10)
Trail.Icon2 = Trail:CreateTexture(nil, "OVERLAY")
Trail.Icon2:SetPoint("LEFT", Trail.Texture2, "RIGHT", -6, 0)
Trail.Icon2:SetSize(24, 24)

function Trail:ResetStates()
	self.Icon:SetTexture()
	self.Icon2:SetTexture()
	self.Texture:SetTexture()
	self.Texture2:SetTexture()
	self.isMouseOver = nil
	self.isTargeting = nil
	self.hasItem = nil
	self.default = true
end

function Trail.OnAction(...)
	local action = ...
	local self = Trail
	if SpellIsTargeting() then
		self:ResetStates()
		self.isTargeting = true
		self.default = nil
		self.Icon:SetTexture(GetActionTexture(action) or "Interface\\RAIDFRAME\\ReadyCheck-Ready")
		self.Texture:SetTexture(db.ICONS.CP_T_L3)
		if GetBindingAction("BUTTON2", true) == "TURNORACTION" then
			self.Texture2:SetTexture(db.ICONS.CP_T_R3)
			self.Icon2:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-NotReady")
		else
			self.Texture2:SetTexture()
			self.Icon2:SetTexture()
		end
	else
		self.isTargeting = nil
	end
end

function Trail.OnItemAdd(...)
	local self = Trail
	self:ResetStates()
	self.hasItem = true
	if GetBindingAction("BUTTON1", true) == "CAMERAORSELECTORMOVE" then
		self.Texture:SetTexture(db.ICONS.CP_T_L3)
		self.Icon:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-NotReady")
	end
end

function Trail.OnTooltipAdd(_, owner)
	local self = Trail
	if not hasItem and not isTargeting then
		if owner == UIParent then
			self:ResetStates()
			self.isMouseOver = true
			self.Texture:SetTexture(self.Default)
		else
			self.isMouseOver = nil
		end
	end
end

function Trail.OnTooltipClear()
	Trail.isMouseOver = nil
end

function Trail:OnUpdate()
	local posX, posY = GetCursorPosition()
	self:SetPoint("BOTTOMLEFT", posX+24, posY-46)
	if ( self.isTargeting and isTargeting ) then
		self:SetAlpha(1)
	elseif ( self.hasItem and hasItem ) then
		if hasWorldFocus and not self.hasWorldFocus then
			self.hasWorldFocus = true
			self.Icon:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-NotReady")
		elseif not hasWorldFocus and self.hasWorldFocus then
			self.hasWorldFocus = nil
			self.Icon:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-Ready")
		end
		self:SetAlpha(1)
	elseif self.isMouseOver and not cameraMode then
		self:SetAlpha(GameTooltip:GetAlpha())
	else
		self:SetAlpha(0)
	end
end

hooksecurefunc("UseAction", Trail.OnAction)
hooksecurefunc("PickupContainerItem", Trail.OnItemAdd)
hooksecurefunc("PickupSpell", Trail.OnItemAdd)
hooksecurefunc(GameTooltip, "SetOwner", Trail.OnTooltipAdd)
GameTooltip:HookScript("OnTooltipCleared", Trail.OnTooltipClear)

---------------------------------------------------------------
-- Toggle interactive mouse driver on/off
---------------------------------------------------------------
function Core:UpdateMouseDriver()
	if not InCombatLockdown() then
		if db.Settings.interactWith then
			local button = db.Settings.interactWith
			local original = db.Bindings and db.Bindings[button] and db.Bindings[button][""]
			local id = original and self:GetActionID(original)

			local targetstate = "[@playertarget,exists,harm,dead] loot; [@playertarget,exists,harm,nodead] enemy; [@playertarget,exists,noharm,nodead] friend; nil"

			if db.Settings.mouseOverMode then
				targetstate = "[@mouseover,exists] hover; "..targetstate
			end

			local currentTarget = SecureCmdOptionParse(targetstate)

			Trail.Default = ICONS[button]
			Trail.Targeting = ICONS.CP_T_L3
			Trail.Texture:SetTexture(Trail.Default)
			Trail:SetScript("OnUpdate", Trail.OnUpdate)
			Trail:Show()

			Mouse.Button:SetTexture(ICONS[db.Settings.interactWith])

			RegisterStateDriver(Mouse, "targetstate", targetstate)
			Mouse:SetAttribute("target", currentTarget)

			self:RegisterSpellHeader(Mouse)

			Mouse:Execute(format([[
				USE = "%s"
				id = %d
				self:Run(UpdateTarget, self:GetAttribute("target"))
				self:SetAttribute("target", nil)
			]], button, id or -1))
		else
			Trail.Default = ICONS.CP_T_R3
			Trail.Targeting = ICONS.CP_T_L3

			Mouse:Execute([[
				self:ClearBindings()
			]])

			UnregisterStateDriver(Mouse, "targetstate")
			self:UnregisterSpellHeader(Mouse)

		end
		Trail:SetScript("OnUpdate", Trail.OnUpdate)
		Trail:Show()
		self:RemoveUpdateSnippet(self.UpdateMouseDriver)
	end
end