----------------------------------
--			SinStats			--
----------------------------------
local AddName, Ns = ...
local L = Ns.L

local headerWidth, headerHeight, updateSpeed = 200, 15, 1

Ns.ConfigDefaultFontSize = 17 -- right side
Ns.TexturePath = "Interface\\AddOns\\"..AddName.."\\Textures\\"

Ns.IgnoreFormat = 0x1 -- just return the icon without any |T...:0|t. Can be used to display in Texture widgets
Ns.ForceIcon = 0x2 -- fors the use of the icon key is the source table has both a spell and and icon key
--Ns.IgnoreColor = ""
Ns.BottomLeft = 1 -- Option 1 use for anchor point of ident (indent???) stat. positioning
Ns.BottomRight = 2 -- Option 2 use for anchor point of ident (indent???) stat. positioning
Ns.LWin = LibStub("LibWindow-1.1")

function Ns.Band(var, flag)
	if var == nil then
		return false
	end
	if bit.band(var, flag) == flag then
		return true
	end
	return false
end

Ns.FunctionList = {}

------------------------------------------
--		  Bar Options Init			--
------------------------------------------
-- Processes baropt and WithBar tags set in Ns.Options and Ns.DefaultOrder.
-- Must run once before CheckForStats / CheckForNewOptions read the data.
function Ns.InitBarOptions()
	if Ns.BarKeys then return end
	local barSet = {}
	local dynamicLinked = {}
	local staticKeys = {}
	local dynamicBaseKeys = {}
	local modeKeys = {}
	local showBarEntry
	for _, entry in ipairs(Ns.Options) do
		if entry.baropt then
			barSet[entry.stat] = true
			if entry.stat == "ShowBar" then
				showBarEntry = entry
			end
			if entry.barmode then
				dynamicLinked[#dynamicLinked+1] = entry.stat
				dynamicLinked[#dynamicLinked+1] = false
				for mode in entry.barmode:gmatch("[^,]+") do
					if not modeKeys[mode] then modeKeys[mode] = {} end
					modeKeys[mode][#modeKeys[mode]+1] = entry.stat
				end
			elseif entry.staticopt then
				staticKeys[#staticKeys+1] = entry.stat
			else
				dynamicBaseKeys[#dynamicBaseKeys+1] = entry.stat
			end
		end
	end
	if showBarEntry then showBarEntry.linked = dynamicLinked end
	Ns.BarKeys = barSet

	for _, entry in ipairs(Ns.DefaultOrder) do
		if entry.options then
			for _, key in ipairs(staticKeys) do
				entry.options[key] = true
			end
			local mode = entry.options.WithBar
			if mode then
				entry.options.WithBar = nil
				if mode == true then mode = "Default" end
				for _, key in ipairs(dynamicBaseKeys) do
					entry.options[key] = true
				end
				if modeKeys[mode] then
					for _, key in ipairs(modeKeys[mode]) do
						entry.options[key] = true
					end
				end
			end
		end
	end
end

------------------------------------------
--			Initialize Icons			--
------------------------------------------
local function LibInit()
	local MinimapIcon = LibStub("LibDataBroker-1.1"):NewDataObject("SinStats",
		{
			type = "data source",
			text = "SinStats",
			icon = Ns.TexturePath.."MiniMap",

			OnTooltipShow = function(tooltip)
				tooltip:SetText("|T"..Ns.TexturePath.."SinStatsLogo:35|t")
				tooltip:AddLine("|cff71FFC9"..L["QuickAccess"].."|r")
				tooltip:AddLine("|T"..Ns.TexturePath.."LeftClick:19|t "..L["OpenSettings"], 1, 1, 1)
				tooltip:AddLine("|T"..Ns.TexturePath.."RightClick:19|t "..L["EnableDisable"], 1, 1, 1)
				tooltip:AddLine("|T"..Ns.TexturePath.."RightClick:19|t |cff71FFC9"..L["ShiftRightClick"].."|r - "..L["UnlockHUD"], 1, 1, 1)
			end,

			OnClick = function(self, button, down)
                if button == "LeftButton" then
                    Ns:ToggleConfig()
                elseif button == "RightButton" then
                    if IsShiftKeyDown() then
                        Ns.Profile.LockHUD = not Ns.Profile.LockHUD
                        Ns:InitialiseProfile(Ns.Profile)
                    else
                        Ns.Profile.HideHUD = not Ns.Profile.HideHUD
                        Ns:InitialiseProfile(Ns.Profile)
                    end
                end

            end,
		}
	)
	Ns.sshMiniButton = LibStub("LibDBIcon-1.0")
	Ns.Profile.Minimap.hide = not Ns.Profile.Minimap.Show
	Ns.sshMiniButton:Register("SinStats", MinimapIcon, Ns.Profile.Minimap)

end

local function InitDefaults()
	if SinStatsDB and SinStatsDB.profileKeys and SinStatsDB.profiles then
		-- Ensure a "Default" profile always exists (upgrade path for existing installs)
		if not SinStatsDB.profiles["Default"] then
			Ns:CreateProfile("Default", nil, "WARRIOR") -- just in case
		end
		-- Ensure specProfiles table exists
		if not SinStatsDB.specProfiles then
			SinStatsDB.specProfiles = {}
		end
		if not SinStatsDB.schemaMigrations then
			SinStatsDB.schemaMigrations = {}
		end
		-- One-time migration HideText was corrupted to true by the old
		-- reset once so existing profiles start clean
		if not SinStatsDB.schemaMigrations.hideTextReset then
			for _, settings in pairs(SinStatsDB.profiles) do
				settings.HideText = false
			end
			SinStatsDB.schemaMigrations.hideTextReset = true
		end
		return
	end
	SinStatsDB = { profileKeys={}, profiles={}, specProfiles={}, schemaMigrations={} }
	Ns:CreateProfile("Default", nil, "WARRIOR")
	if Ns.byPlayerClass then
		for className in pairs(Ns.byPlayerClass) do
			Ns:CreateProfile(className, nil, className)
		end
	end
end

local function CheckForNewOptions()
	for profile, settings in pairs(SinStatsDB.profiles) do
		for _, displayOrder in ipairs(Ns.ProfileRootOrders) do
			for _, value in pairs(displayOrder) do
				if type(value) == "table" and value.stat and not value.svnotintable then
					if settings[value.stat] == nil and value.default ~= nil then
						settings[value.stat] = value.default
					end
				end
			end
		end
		for stat, data in pairs(settings.Stats) do
			for _, statkey in pairs(Ns.DefaultOrder) do
				if statkey.stat == stat then
					if not statkey.options then
						break
					end
					for option, istrue in pairs(statkey.options) do
						for _, optionentry in pairs(Ns.Options) do
							if optionentry.stat == option then
								if istrue and (optionentry.default ~= nil) and data[option] == nil then
									data[option] = optionentry.default
								end
							end
						end
					end
				end
			end
		end
	end
end

local otherUpdateSpeeds = {
	[1] = 0.2,
}
local otherUpdateSpeedsElapsed, otherUpdateSpeedsStats = {}, {}

function Ns:RegisterForOtherUpdate(id, stat)
	if not otherUpdateSpeeds[id] then
		error("|cffff0000SinStats ERROR:|r Ns:RegisterForOtherUpdate - No Elapsed time available for id |cffffff00".. id .."|r")
	end
	if not otherUpdateSpeedsElapsed[id] then
		otherUpdateSpeedsElapsed[id] = otherUpdateSpeeds[id]
	end
	if not otherUpdateSpeedsStats[id] then
		otherUpdateSpeedsStats[id] = {}
	end
	tinsert(otherUpdateSpeedsStats[id], stat)
end

function Ns:ClearOtherUpdates()
	table.wipe(otherUpdateSpeedsElapsed)
	table.wipe(otherUpdateSpeedsStats)
end

local function MoveEvents(profile)
	if profile.EventEnable or profile.EventEnable == false then
		for k, v in pairs(Ns.EventsDisplayOrder) do
			if type(v) == "table" and v.stat then
				profile.Stats[v.stat] = profile[v.stat]
				profile[v.stat] = nil
			end
		end
	end
end

function Ns.NewSettings(profile, class)
--[[ DO NOT REMOVE! ]]--
	Ns.InitBarOptions() -- Process baropt / WithBar tags before reading the data
	Ns:CheckForStats(profile, class) -- Check if any new stats have been added to the Ns.DefaultOrder table
	CheckForNewOptions() -- Check if any new options have been added and add them to require SV stats in ALL profiles
	MoveEvents(profile)
	-- Remove old GroupOrder SV entry to be replaced by Stat. Display Order (including Custom order)
	if profile.GroupOrder ~= nil then
		for _, settings in pairs(SinStatsDB.profiles) do
			settings.GroupOrder = nil
		end
	end

	-- Fix for Font droplist with incorrect or blank setting
	if profile.StatFont == "Fonts/ARIALN.ttf" then
		for _, settings in pairs(SinStatsDB.profiles) do
			if settings.StatFont == "Fonts/ARIALN.ttf" then
				settings.StatFont = "Arial Narrow"
			end
		end
	end
end

--------------------------
--	Spec Profile Switch --
--------------------------
local specSwitching = false
function Ns:OnSpecChanged()
	if specSwitching then return end
	if not SinStatsDB.specProfiles then return end
	local specData = SinStatsDB.specProfiles[Ns.ProfileKey]
	if not specData or not specData.enabled then return end

	if not GetSpecialization then return end
	local specIndex = GetSpecialization()
	if not specIndex then return end

	local targetProfile = specData[specIndex]
	if not targetProfile or not SinStatsDB.profiles[targetProfile] then return end

	-- Already on this profile? Skip.
	if SinStatsDB.profileKeys[Ns.ProfileKey] == targetProfile then return end

	-- Switch profile
	specSwitching = true
	SinStatsDB.profileKeys[Ns.ProfileKey] = targetProfile
	Ns.Profile = SinStatsDB.profiles[targetProfile]
	Ns.NewSettings(Ns.Profile, Ns.Class)
	Ns:UpdateProfile()
	Ns:InitialiseProfile(Ns.Profile)
	specSwitching = false
end

--------------------------
--			HUD			--
--------------------------
local f = CreateFrame("Frame", "SinStatsFrame", UIParent)
f:SetSize(headerWidth, headerHeight)
f:EnableMouse(true)
f:SetFrameStrata("MEDIUM")
f.Background = f:CreateTexture()
f.Background:SetAllPoints()
f.Background:SetColorTexture(0.09020, 0.09020, 0.09020, 1)
f.Title = f:CreateFontString(nil, "OVERLAY");
f.Title:SetFontObject("GameFontHighlight");
f.Title:SetTextColor(1, 1, 1)
f.Title:SetText("SinStats")
f.Title:SetPoint("CENTER")
f.HUD = CreateFrame("Frame", nil, UIParent)
f.HUD:SetSize(headerWidth, 2)
f.HUD:SetPoint("TOPLEFT", f, "BOTTOMLEFT")
f.HUD:SetFrameStrata("MEDIUM")
f.HUD.Background = f.HUD:CreateTexture()
f.HUD.Background:SetTexture("Interface/BUTTONS/WHITE8X8")
f.HUD.Background:SetVertexColor(0.1, 0.1, 0.1, 0)
f.HUD.Background:SetPoint("TOPLEFT", f, "BOTTOMLEFT", 0, 1)
--f.HUD.Background:SetPoint("TOPRIGHT", f, "BOTTOMRIGHT")

f.HUD.BorderLines = {}
for i = 1, 4 do
	local line = f.HUD:CreateLine(nil, "OVERLAY", nil, 0)
	line:SetThickness(1)
	line:SetColorTexture(0.1, 0.1, 0.1, 1)
	line:Hide()
	f.HUD.BorderLines[i] = line
end
-- Top
f.HUD.BorderLines[1]:SetStartPoint("TOPLEFT", f.HUD.Background, 0, 0)
f.HUD.BorderLines[1]:SetEndPoint("TOPRIGHT", f.HUD.Background, 0, 0)
-- Right
f.HUD.BorderLines[2]:SetStartPoint("TOPRIGHT", f.HUD.Background, 0, 0)
f.HUD.BorderLines[2]:SetEndPoint("BOTTOMRIGHT", f.HUD.Background, 0, 0)
-- Bottom
f.HUD.BorderLines[3]:SetStartPoint("BOTTOMRIGHT", f.HUD.Background, 0, 0)
f.HUD.BorderLines[3]:SetEndPoint("BOTTOMLEFT", f.HUD.Background, 0, 0)
-- Left
f.HUD.BorderLines[4]:SetStartPoint("BOTTOMLEFT", f.HUD.Background, 0, 0)
f.HUD.BorderLines[4]:SetEndPoint("TOPLEFT", f.HUD.Background, 0, 0)

f.HUD.StatList = {}
f.HUD:SetScript("OnEvent", function (self, event, ...)
	if event == "PLAYER_LOGIN" then
		Ns:InitialiseVersionData()
--		print("|cff00f26dSinStats v" .. addVer .. "|r. Type |cff00f26d/sinstats|r or |cff00f26d/ss|r to open the settings.")
		Ns.Name = UnitName("player")
		Ns.ProfileKey = Ns.Name .. "-" .. GetRealmName()
		Ns.Class = select(2, UnitClass("player"))
		InitDefaults()
		if not SinStatsDB.profileKeys[Ns.ProfileKey] then
			SinStatsDB.profileKeys[Ns.ProfileKey] = "Default"
		end
		Ns.Profile = SinStatsDB.profiles[SinStatsDB.profileKeys[Ns.ProfileKey]]
		Ns.NewSettings(Ns.Profile, Ns.Class)
		LibInit()
		if not Ns.Profile.HUDPos then
			Ns.Profile.HUDPos = {}
			Ns:ResetHUD()
		end
		Ns.LWin.RegisterConfig(SinStatsFrame, Ns.Profile.HUDPos)
		Ns.LWin.MakeDraggable(SinStatsFrame)
		--Ns.LWin.RestorePosition(SinStatsFrame) 
		Ns.talentScan()
		self:SetScript("OnEvent", function(self, event, ...)
			-- Spec profile auto-switch (Retail and MoP Classic for now)
			if event == "PLAYER_SPECIALIZATION_CHANGED" then
				local unit = ...
				if unit ~= "player" then return end
				Ns:OnSpecChanged()
			elseif event == "PLAYER_TALENT_UPDATE" then
				-- MoP Classic: spec changes fire PLAYER_TALENT_UPDATE
				if WOW_PROJECT_ID == 19 then
					Ns:OnSpecChanged()
				end
			end
			if Ns.EventList[event] then
				local list = Ns.EventList[event]
				for k = 1, #list do
					local v = list[k]
					v.func(v.widget, v.data, v.options, ...)
				end
			end
			Ns.OnEventFunc(event, ...)
		end)

		if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then
			self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
		end
		-- MoP Classic already registers PLAYER_TALENT_UPDATE via Mists.lua events table

		C_Timer.After(1, function()
			self:GetScript("OnEvent")(SinStatsFrame, "UPDATE_INVENTORY_DURABILITY")
			self:GetScript("OnEvent")(SinStatsFrame, "PLAYER_EQUIPMENT_CHANGED")
		end)
		
		self.Elapsed = updateSpeed
		self:SetScript("OnUpdate", function(self, elapsed)
			for elapseId = 1, #otherUpdateSpeeds do
				local elapseTime = otherUpdateSpeedsElapsed[elapseId]
				if elapseTime then
					elapseTime = elapseTime - elapsed
					if elapseTime > 0 then
						otherUpdateSpeedsElapsed[elapseId] = elapseTime
					else
						otherUpdateSpeedsElapsed[elapseId] = otherUpdateSpeeds[elapseId]
						local stats = otherUpdateSpeedsStats[elapseId]
						if stats then
							for k = 1, #stats do
								local v = stats[k]
								v.func(v.widget, v.data, v.options, elapsed)
							end
						end
					end
				end
			end
			self.Elapsed = self.Elapsed - elapsed
			if self.Elapsed >= 0 then
				return
			end
			self.Elapsed = updateSpeed
			Ns.MeleeUpdate()
			Ns.DefenseUpdate()
			Ns.RangedUpdate()
			Ns.SpellUpdate()
			for k, v in ipairs(Ns.OnUpdateList) do
				v.func(v.widget, v.data, v.options, elapsed)
			end
		end)
		Ns:InitialiseProfile(Ns.Profile)

		-- Apply spec profile for the current spec at login
		if (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE) or (WOW_PROJECT_ID == 19) then
			C_Timer.After(0.5, function()
				Ns:OnSpecChanged()
			end)
		end
	end
end)
f.HUD:RegisterEvent("PLAYER_LOGIN")
f.HUD:RegisterEvent("UPDATE_INVENTORY_DURABILITY")

--------------------------------------
--			Slash commands			--
--------------------------------------
SLASH_SINSTATS1 = "/sinstats"
SLASH_SINSTATS2 = "/ss"
function SlashCmdList.SINSTATS()
	Ns:ToggleConfig()
end
