-- ==================================================================================================================== --
--															--
--	File			Skada.lua										--
--															--
--															--
--	Author			Zarnivoop						| Basis, thx	|		--
--															--
--										| Version	   :	 12.0.1 |	--
--										| Curse-Project-ID :	  17718 |	--
--										| Wago-ID	   :   Q6aEVa6W |	--
--										| License	   :	  MIT/X |	--
--															--
--															--
--				Baltha							| Revision	|		--
--										| Version	   :	 12.0.5 |	--
--										| Curse-Project-ID :	1188318 |	--
--										| License	   :	  MIT/X |	--
--															--
--															--
--										| Initial Version  : 2024-11-18 |	--
--										| Current Version  : 2026-05-17 |	--
--										|		   : Release	|	--
--															--
--															--
--	Path ( Revision )	"..\World of Warcraft\_retail_\Interface\AddOns\Skada\Misc\Changelog\ChangeLog.txt"	--
--															--
-- ==================================================================================================================== --


local _, addon	    = ...
local Skada	    = LibStub("AceAddon-3.0"):NewAddon(addon, "Skada", "AceTimer-3.0", "AceEvent-3.0")


_G.Skada	    = Skada


local L		    = LibStub("AceLocale-3.0"):GetLocale("Skada", true)
local acd	    = LibStub("AceConfigDialog-3.0")
local icon	    = LibStub("LibDBIcon-1.0", true)
local media	    = LibStub("LibSharedMedia-3.0")
local lds	    = LibStub("LibDualSpec-1.0", true)


local locale	    = GetLocale()
local emptyLines    = string.rep("\n", 2)
local blankChars1   = string.rep(" " , 6)
local blankChars2   = string.rep(" " , 14)


-- ======
-- | DB |
-- ======
SkadaMetaTitle	    = SkadaMetaTitle or ""


CurrentSkadaNames   = CurrentSkadaNames   or {}
SkadaLogData	    = SkadaLogData	  or {}
SkadaLogNameAndSort = SkadaLogNameAndSort or {}


-- =========================
-- | Display Skada Version |
-- =========================
local versionSkada = C_AddOns.GetAddOnMetadata("Skada", "Version")


if versionSkada ~= nil then print("|TInterface\\Icons\\Spell_Lightning_LightningBolt01:16|t" .. "\124cFF00FF00" .."Release " .. "\124cnORANGE_FONT_COLOR:" ..
				  "08-05-2026 " .. "\124cFF00FF00" .. "Skada Version " .. "\124cnORANGE_FONT_COLOR:" .. versionSkada .. "\124cFF00FF00 " ..
				  L["Localization"] .. " " .. "\124cnORANGE_FONT_COLOR:" .. locale) end


-- ================
-- | Minimap Icon |
-- ================
local dataobj	 = LibStub("LibDataBroker-1.1"):NewDataObject("Skada",

									{
										label = "Skada"						   	,
										type  = "data source"					   	,
										icon  = "Interface\\Icons\\Spell_Lightning_LightningBolt01"	,
										text  = "n/a"
									}

							     )


InterfaceOptions_AddCategory = InterfaceOptions_AddCategory


if not InterfaceOptions_AddCategory then

	InterfaceOptions_AddCategory = function(frame, addOn, position)

		frame.OnCommit  = frame.okay
		frame.OnDefault = frame.default
		frame.OnRefresh = frame.refresh


		if frame.parent then

			local category = Settings.GetCategory(frame.parent)
			local subcategory, layout = Settings.RegisterCanvasLayoutSubcategory(category, frame, frame.name, frame.name)


			subcategory.ID = frame.name


			return subcategory, category
		else
			local category, layout = Settings.RegisterCanvasLayoutCategory(frame, frame.name, frame.name)


			category.ID = frame.name
			Settings.RegisterAddOnCategory(category)


			return category
		end
	end
end




function Skada:GetSpellIcon(spellId)

	return self.SecretHelper:GetSpellIcon(spellId)
end




function Skada:GetGameVersion()

	local version = floor((floor(select(4, GetBuildInfo())) / 10000))


	return version
end


local popup
local next, ipairs, pairs, tinsert, tremove, tsort, type = next, ipairs, pairs, table.insert, table.remove, table.sort, type


local function IsInPVP()

	local _, instanceType = IsInInstance()
	return instanceType == "pvp" or instanceType == "arena"
end




function Skada:GetGroupTypeAndCount()

	local count	= 0
	local units	= {}
	local groupSize = GetNumGroupMembers()


	if IsInRaid() then

		for i = 1, groupSize do

			units[#units + 1] = "raid" .. i


--			if UnitExists("raidpet" .. i) then
--
--				units[groupSize + 1] = "raidpet" .. i
--			end
		end


		groupType = "Raid"
		count	  = #units
	elseif IsInGroup() then

		for i = 1, groupSize - 1 do

			units[#units + 1] = "party" .. i


--			if UnitExists("party" .. i) then
--
--				units[groupSize + 1] = "partypet" .. i
--			end
		end

		units[groupSize] = "player"

		groupType = "Party"
		count	  = #units
	else

		units[#units + 1] = "player"


--		if UnitExists("pet") then
--
--			units[#units + 1] = "pet"
--		end


		groupType = "Single"
		count	  = #units
	end


	return groupType, count, units
end


do

	popup = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")


	popup:SetBackdrop(

				{

					bgFile   = "Interface\\Buttons\\WHITE8X8"		,
					edgeFile = "Interface\\Buttons\\WHITE8X8"		,

					tile     = {true, tileSize = 0, edgeSize = 1}		,
					insets   = {left = 0, right = 0, top = 0, bottom = 0}
				}

			 )


	popup:SetBackdropColor(0.05, 0.05, 0.05, 0.9)
	popup:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)
	popup:SetSize(300, 120)
	popup:SetPoint("CENTER", UIParent, "CENTER")
	popup:SetFrameStrata("TOOLTIP")

	popup:Hide()
	popup:EnableKeyboard(true)


	popup:SetScript("OnKeyDown",

				function(self, key)

					if GetBindingFromClick(key) == "TOGGLEGAMEMENU" then

						popup:SetPropagateKeyboardInput(false)
						popup:Hide()
					end
				end

		       )


	local text = popup:CreateFontString(nil, "ARTWORK", "GameFontNormal")


	text:SetPoint("TOP", popup, "TOP", 0, -25)
	text:SetText(L["Do you want to reset Skada?"])


	local accept = CreateFrame("Button", nil, popup, "BackdropTemplate")


	accept:SetSize(100, 30)
	accept:SetPoint("BOTTOMLEFT", popup, "BOTTOMLEFT", 40, 20)


	accept:SetBackdrop(

				{
					bgFile   = "Interface\\Buttons\\WHITE8X8",
					edgeFile = "Interface\\Buttons\\WHITE8X8",
					edgeSize = 1
				}

			  )


	accept:SetBackdropColor(0.1, 0.4, 0.1, 0.8)
	accept:SetBackdropBorderColor(0.2, 0.6, 0.2, 1)


	local acceptText = accept:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")


	acceptText:SetPoint("CENTER")
	acceptText:SetText(L["Yes"])


	accept:SetScript("OnEnter", function(self) self:SetBackdropColor(0.2, 0.5, 0.2, 1) end)
	accept:SetScript("OnLeave", function(self) self:SetBackdropColor(0.1, 0.4, 0.1, 0.8) end)


	accept:SetScript("OnClick",

				function(f)

					Skada:Reset()


					f:GetParent():Hide()
				end

			)


	local close = CreateFrame("Button", nil, popup, "BackdropTemplate")


	close:SetSize(100, 30)
	close:SetPoint("BOTTOMRIGHT", popup, "BOTTOMRIGHT", -40, 20)

	
	close:SetBackdrop(

				{

					bgFile   = "Interface\\Buttons\\WHITE8X8"	,
					edgeFile = "Interface\\Buttons\\WHITE8X8"	,
					edgeSize = 1
				}

			 )


	close:SetBackdropColor(0.4, 0.1, 0.1, 0.8)
	close:SetBackdropBorderColor(0.6, 0.2, 0.2, 1)


	local closeText = close:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")


	closeText:SetPoint("CENTER")
	closeText:SetText(L["No"])


	close:SetScript("OnEnter", function(self) self:SetBackdropColor(0.5, 0.2, 0.2, 1) end)
	close:SetScript("OnLeave", function(self) self:SetBackdropColor(0.4, 0.1, 0.1, 0.8) end)
	close:SetScript("OnClick", function(f) f:GetParent():Hide() end)


	function Skada:ShowPopup()

		popup:SetPropagateKeyboardInput(true)
		popup:Show()
	end
end


BINDING_HEADER_Skada	      = "Skada"
BINDING_NAME_SKADA_TOGGLE     = L["Toggle window"]
BINDING_NAME_SKADA_RESET      = L["Reset"]
BINDING_NAME_SKADA_NEWSEGMENT = L["Start new segment"]


--Skada.current	   = nil
--Skada.total	   = nil
--Skada.last	   = nil


local modes	   = {}
local changed	   = true
local wasinparty   = nil


Skada.classcolors  = RAID_CLASS_COLORS


local selectedfeed = nil
local feeds	   = {}
local windows	   = {}


Skada.displays	   = {}




function Skada:GetWindows()

	return windows
end




local function find_mode(name)

	for i, mode in ipairs(modes) do

		if mode:GetName() == name then return mode end
	end
end


local Window = {}
local mt     = { __index = Window }


function Window:new()

	return setmetatable(

				{

					selectedmode = nil	,
					selectedset  = nil	,
					restore_mode = nil	,
					restore_set  = nil	,
					usealt	     = true	,
					dataset	     = {}	,
					metadata     = {}	,
					display	     = nil	,
					history      = {}	,
					changed      = false

				}				,

				mt

			   )

end




function Window:AddOptions()

	local db = self.db


			  local options =

					{

						   type = "group"																		,
						   name = function()


								  local displayType, name = "", ""


								  if locale == "deDE" then

									  if db.display == "bar"    then displayType = "\124cnBLUE_FONT_COLOR:"		    end
									  if db.display == "broker" then displayType = "\124cnORANGE_FONT_COLOR:"	    end
								  end


								  if locale == "enUS" then

									  if db.display == "bar"    then displayType = "\124cnBLUE_FONT_COLOR:"		    end
									  if db.display == "broker" then displayType = "\124cnORANGE_FONT_COLOR:"	    end
								  end


								  name = displayType .. db.name


								  return name
							  end																			,

						   args =

							{

							  currentwindow =

									{

										   type = "description"														,
										   name = "\124cnNORMAL_FONT_COLOR:" .. L["Options: current window"] .. " " .. "|cffffffff" .. self.db.name .. emptyLines	,
										  order = 0

									}																	,

								 rename =

									{

										   type = "input"														,
										   name = "\124cFF00FF00" .. L["Rename window"]											,
										    get = function()

												  return db.name
											  end															,
										    set	= function(win, val)

												  -- =====================================================
												  -- | "REN" rebuilding SkadaLogNameAndSort/SkadaLogData |
												  -- =====================================================
												  local cwnHasName, cwnIsAllowedName = Skada:CheckWindowNames(val)


												  if cwnIsAllowedName then

													  local cwdHasName, cwdIsAllowedName = Skada:CheckWindowDuplicates(cwnHasName, "REN")


													  if cwdIsAllowedName == true then


														  local oldname = db.name


														  val		= cwdHasName
											  			  db.name	= val


														  for position, entry in pairs(SkadaLogNameAndSort) do

															  chunks = strsplittable(":", entry)


															  if chunks[1] == oldname then

																  newEntry = db.name .. ":" .. chunks[2]

																  table.insert(SkadaLogNameAndSort, position, newEntry)
																  table.remove(SkadaLogNameAndSort, position + 1)

																  table.insert(CurrentSkadaNames, position, db.name)
																  table.remove(CurrentSkadaNames, position + 1)


																  local sldValue =

																	date("%Y:%m:%d : %H:%M:%S") .. " : " .. "REN" .. " : " ..
																	db.display .. string.rep(" ",  7 - string.len(db.display)) .. " : " .. 
																	db.name    .. string.rep(" ", 22 - string.len(db.name))    .. " : " ..
																	chunks[1]  .. string.rep(" ", 22 - string.len(chunks[1]))  .. ":"


														  		  table.insert(SkadaLogData, sldValue)
															  end
														  end


														  Skada.options.args.windows.args[val]	   = Skada.options.args.windows.args[oldname]
														  Skada.options.args.windows.args[oldname] = nil
													  end
												  end
											  end															,
										order	= 1

									}	,

							         locked =

									{
										   type = "toggle"														,
										   name = L["Lock window"]													,
										   desc = L["Locks the bar window in place."]											,
										    get = function()

												  return db.barslocked
											  end															,
										    set = function()

												  db.barslocked = not db.barslocked


												  Skada:ApplySettings()
											  end															,
										  order = 2

									},

								 delete =

									{

										   type = "execute"														,
										   name = "\124cFF00FF00" .. L["Delete window"]											,
										   desc = L["Deletes the chosen window."]											,
										  width = "full"														,
										confirm = function()

												  return "Are you sure you want to delete this window?"
											  end															,
										   func = function()

												  -- =====================================================
												  -- | "DEL" rebuilding SkadaLogNameAndSort/SkadaLogData |
												  -- =====================================================
												  local newSkadaLogNameAndSort = {}


												  Skada:tcopy(newSkadaLogNameAndSort, SkadaLogNameAndSort)
												  Skada:DeleteWindow(db.name)


												  local sldValue = date("%Y:%m:%d : %H:%M:%S") .. " : " .. "DEL" .. " : " .. db.display .. 
														   string.rep(" ",  7 - string.len(db.display)) .. " : " .. 
														   string.rep(" ", 22) .. " : " .. db.name ..
														   string.rep(" ", 22 - string.len(db.name)) .. ":"


												  table.insert(SkadaLogData, sldValue)
												  Skada:CurrentSort(SkadaLogData, "LDR")


												  for position, entry in pairs(newSkadaLogNameAndSort) do

													  delEntry = strsplit(":", entry)


													  if delEntry == db.name then

														  table.remove(newSkadaLogNameAndSort, position)
														  table.remove(CurrentSkadaNames, position)


														  Skada:CurrentTooltip("DEL", "", db.name, "")


														  break
													  end
												  end

									
												  SkadaLogNameAndSort = {}
												  Skada:tcopy(SkadaLogNameAndSort, newSkadaLogNameAndSort)
											  end															,
										  order = 3

									 },

							}
					}

	     options.args.switchoptions =

					{
						   type = "group"																		,
						   name = L["Mode switching"]																	,
						  order = 4																			,

						   args =

							{

							  windowoptions =

									{

										   type = "description",
										   name = "\124cnNORMAL_FONT_COLOR:" .. L["Options: mode change"] .. " " .. "|cffffffff" .. self.db.name .. emptyLines		,
										  order = 0

									}	,

							   modeincombat =

									{

										   type = "select"														,
										   name = L["Combat mode"]													,
										   desc = L["Automatically switch to set 'Current' and this mode when entering combat."]					,
										 values = function()

												  local modes = {}


												  modes[""] = L["None"]


												  for i, mode in ipairs(Skada:GetModes()) do modes[mode:GetName()] = mode:GetName() end


												  return modes
											  end															,
										    get = function()

												  return db.modeincombat
											  end															,
										    set = function(win, mode)

												  db.modeincombat = mode
											  end,
										  order = 21
									},

							       wipemode =

									{

										   type = "select"														,
										   name = L["Wipe mode"]													,
										   desc = L["Automatically switch to set 'Current' and this mode after a wipe."]						,
										 values = function()

												  local modes = {}


												  modes[""] = L["None"]


												  for i, mode in ipairs(Skada:GetModes()) do modes[mode:GetName()] = mode:GetName() end


												  return modes
											  end															,
										    get = function()

												  return db.wipemode
											  end															,
										    set = function(win, mode)

												  db.wipemode = mode
											  end															,
										  order = 21

									},

						      returnaftercombat =

									{

										   type = "toggle"														,
										   name = L["Return after combat"]												,
										   desc = L["Return to the previous set and mode after combat ends."]								,
										    get = function()

												  return db.returnaftercombat
											  end															,
										    set = function()

												  db.returnaftercombat = not db.returnaftercombat
											  end															,
									       disabled = function()

												  return db.returnaftercombat == nil
											  end															,
										order = 23

									},
							}
					}


	self.display:AddDisplayOptions(self, options.args)


	Skada.options.args.windows.args[self.db.name] = options
end




function Window:SetChild(window)

	self.child = window
end




function Window:destroy()

	self.dataset = nil


	if self.display and self.display.Destroy then self.display:Destroy(self) end


	local name = self.db.name or Skada.windowdefaults.name


	Skada.options.args.windows.args[name] = nil
end




function Window:SetDisplay(name)


	if name ~= self.db.display or self.display == nil then

		if self.display then self.display:Destroy(self)	end


		self.db.display = name
		self.display	= Skada.displays[self.db.display]


		self:AddOptions()
	end
end




function Window:UpdateDisplay()

	if not self.metadata.maxvalue then

		self.metadata.maxvalue = 0


		for i, data in ipairs(self.dataset) do

			if data.id then

				local val = Skada:SafeNumber(data.value)


				if val > self.metadata.maxvalue then self.metadata.maxvalue = val end
			end
		end
	end


	if self.display and self.display.Update then self.display:Update(self) end


	self:set_mode_title()
end




function Window:UpdateInProgress()

	for i, data in ipairs(self.dataset) do

		if data.ignore then data.icon = nil end


		data.id	    = nil
		data.ignore = nil
	end
end




function Window:Show()

	self.display:Show(self)
end




function Window:Hide()

	self.display:Hide(self)
end




function Window:IsShown()

	return self.display:IsShown(self)
end




function Window:Reset()

	for i, data in ipairs(self.dataset) do wipe(data) end
end




function Window:Wipe()

	self:Reset()


	if self.display and self.display.Wipe then self.display:Wipe(self) end
	if self.child			      then self.child:Wipe()	   end
end




function Window:get_selected_set()

	return Skada:find_set(self.selectedset)
end




function Window:DisplayMode(mode)

	self:Wipe()

	self.selectedplayer = nil
	self.selectedmode   = mode
	self.metadata	    = wipe(self.metadata or {})


	if mode.metadata then

		for key, value in pairs(mode.metadata) do self.metadata[key] = value end
	end


	self.changed = true

	self:set_mode_title()


	if self.child then self.child:DisplayMode(mode)	end


	Skada:UpdateDisplay(false)
end


local numsetfmts = 8


local function SetLabelFormat(name, starttime, endtime, fmt)

	fmt		= fmt or Skada.db.profile.setformat


	local namelabel = name


	if fmt < 1 or fmt > numsetfmts then fmt = 3 end


	local timelabel = ""


	if starttime and endtime and fmt > 1 then

		local duration = SecondsToTime(endtime - starttime, false, false, 2)


		Skada.getsetlabel_fs = Skada.getsetlabel_fs or UIParent:CreateFontString(nil, "ARTWORK", "ChatFontNormal")
		Skada.getsetlabel_fs:SetText(duration)


		duration = "(" .. Skada.getsetlabel_fs:GetText() .. ")"


		if fmt == 2 then

			timelabel = duration
		elseif fmt == 3 then

			timelabel = date("%H:%M", starttime) .. " " .. duration
		elseif fmt == 4 then

			timelabel = date("%I:%M", starttime) .. " " .. duration
		elseif fmt == 5 then

			timelabel = date("%H:%M", starttime) .. " - " .. date("%H:%M", endtime)
		elseif fmt == 6 then

			timelabel = date("%I:%M", starttime) .. " - " .. date("%I:%M", endtime)
		elseif fmt == 7 then

			timelabel = date("%H:%M:%S", starttime) .. " - " .. date("%H:%M:%S", endtime)
		elseif fmt == 8 then

			timelabel = date("%H:%M", starttime) .. " - " .. date("%H:%M", endtime) .. " " .. duration
		end
	end


	local comb


	if #namelabel == 0 or #timelabel == 0 then

		comb = namelabel .. timelabel
	elseif timelabel:match("^%p") then

		comb = namelabel .. " " .. timelabel
	else

		comb = namelabel .. ": " .. timelabel
	end


	return comb, namelabel, timelabel
end




function Skada:SetLabelFormats()

	local ret = {}
	local start = 1000007900


	for i = 1, numsetfmts do ret[i] = SetLabelFormat("Hogger", start, start + 380, i) end


	return ret
end




function Skada:GetSetLabel(set)

	if not set then return "" end
	

	local startTime = set.startTime or set.starttime
	local endTime   = set.endTime   or set.endtime or time()

	
	return SetLabelFormat(set.name or "Unknown", startTime, endTime)
end




function Window:set_mode_title()

	if not self.selectedmode or not self.selectedset then return end


	local name = tostring(self.selectedmode.title or self.selectedmode:GetName())


	self.db.set = self.selectedset


	local savemode = name


	self.db.mode = savemode


	if self.db.titleset then

		local setname


		if self.selectedset == "current" then

			setname = L["Current"]
		elseif self.selectedset == "total" then

			setname = L["Total"]
		else

			local set = self:get_selected_set()


			if set then setname = Skada:GetSetLabel(set) end
		end


		if setname then	name = tostring(name) .. ": " .. tostring(setname) end
	end


	self.metadata.title = name


	if self.display and self.display.SetTitle then self.display:SetTitle(self, name) end
end




local function sort_modes()

	tsort(modes,

		function(a, b)

			if Skada.db.profile.sortmodesbyusage and Skada.db.profile.modeclicks then

				return (Skada.db.profile.modeclicks[a:GetName()] or 0) > (Skada.db.profile.modeclicks[b:GetName()] or 0)
			else

				return a:GetName() < b:GetName()
			end
		end

	     )

end




local function click_on_mode(win, id, label, button)

	if button == "LeftButton" then

		local mode = find_mode(id)


		if mode then

			if Skada.db.profile.sortmodesbyusage then

				if not Skada.db.profile.modeclicks then Skada.db.profile.modeclicks = {} end


				Skada.db.profile.modeclicks[id] = (Skada.db.profile.modeclicks[id] or 0) + 1
				sort_modes()
			end


			win:DisplayMode(mode)
		end
	elseif button == "RightButton" then

		win:RightClick()
	end
end




function Window:DisplayModes(settime)

	self:Wipe()

	self.selectedplayer = nil
	self.selectedmode   = nil
	self.metadata	    = wipe(self.metadata or {})
	self.metadata.title = L["Skada: Modes"]


	if settime == "current" or settime == "total" then

		self.selectedset = settime
	else

		local sessionId = tonumber(settime)


		if sessionId then

			self.selectedset = settime
		else

			self.selectedset = "current"
		end
	end


	self.metadata.click = click_on_mode
	self.metadata.maxvalue = 1
	self.metadata.sortfunc = function(a, b) return a.name < b.name end


	if self.display and self.display.SetTitle then self.display:SetTitle(self, self.metadata.title)	end


	self.changed = true


	if self.child then self.child:DisplayModes(settime) end


	Skada:UpdateDisplay(false)
end




local function click_on_set(win, id, label, button)

	if button == "LeftButton" then

		win:DisplayModes(id)
	elseif button == "RightButton" then

		win:RightClick()
	end
end




function Window:DisplaySets()

	self:Wipe()

	self.metadata	       = wipe(self.metadata or {})
	self.selectedplayer    = nil
	self.selectedmode      = nil
	self.selectedset       = nil
	self.metadata.title    = L["Skada: Fights"]


	if self.display and self.display.SetTitle then self.display:SetTitle(self, self.metadata.title)	end


	self.metadata.click    = click_on_set
	self.metadata.maxvalue = 1
	self.changed	       = true


	if self.child then self.child:DisplaySets() end


	Skada:UpdateDisplay(false)
end




function Window:RightClick(group, button)

	if self.selectedmode then

		self:DisplayModes(self.selectedset)

	elseif self.selectedset then

		self:DisplaySets()
	end
end




function Skada:tcopy(to, from, ...)

	for k, v in pairs(from) do

		local skip = false


		if ... then

			for i, j in ipairs(...) do

				if j == k then

					skip = true


					break
				end
			end
		end


		if not skip then

			if type(v) == "table" then

				to[k] = {}


				Skada:tcopy(to[k], v, ...)
			else

				to[k] = v
			end
		end
	end
end




function Skada:CreateWindow(name, db, display)

	local isnew = false


	if not db then

		isnew = true
		db    = {}


		self:tcopy(db, Skada.windowdefaults)
		tinsert(self.db.profile.windows, db)
	end


	if display then	db.display		= display								  end

	if not db.barbgcolor then db.barbgcolor = {r = 0.3, g = 0.3, b = 0.3, a = 0.6}					  end
	if not db.buttons then db.buttons	= {menu = true, reset = true, report = true, mode = true, segment = true} end
	if not db.scale then db.scale		= 1									  end
	if not db.version then db.version	= 1									  end


	-- =====================================================
	-- | "NEW" rebuilding SkadaLogNameAndSort/SkadaLogData |
	-- =====================================================
	local window = Window:new()


	window.db      = db
	window.db.name = string.sub(string.upper(name), 1 , 1) .. string.sub(string.lower(name), 2)


	if isnew then

		table.insert(CurrentSkadaNames  , window.db.name)
		table.insert(SkadaLogNameAndSort, window.db.name .. ":" .. "Alphabetical")


		table.insert(SkadaLogData,

				date("%Y:%m:%d : %H:%M:%S") .. " : " .. "NEW" .. " : " .. window.db.display .. string.rep(" ", 7- string.len(window.db.display)) .. " : " ..
				window.db.name .. string.rep(" ", 22- string.len(window.db.name)) .. " : " .. string.rep(" ", 22) .. ":"

			    )


		Skada:CurrentSort(SkadaLogData, "LDR")
	end


	if self.displays[window.db.display] then

		window:SetDisplay(window.db.display or "bar")
		window.display:Create(window, isnew)


		tinsert(windows, window)
		window:DisplaySets()


		if isnew and find_mode(L["Damage"]) then

			self:RestoreView(window, "current", L["Damage"])
		elseif window.db.set or window.db.mode then

			self:RestoreView(window, window.db.set, window.db.mode)
		end
	else

		self:Print("Window '" .. name .. "' was not loaded because its display module, '" .. window.db.display .. "' was not found.")
	end


	if #SkadaLogData == 0 then

		table.insert(SkadaLogNameAndSort, "Skada" .. ":" .. "Alphabetical")
		table.insert(SkadaLogData,

				date("%Y/%m/%d : %H:%M:%S") .. " : " .. "NEW" .. " : " .. window.db.display .. string.rep(" ", 7 - string.len(window.db.display)) .. " : " ..
				"Skada" .. string.rep(" ", 22- string.len("Skada")) .. " : " .. string.rep(" ", 22) .. ":"

			    )


		Skada:CurrentSort(SkadaLogData, "LDR")
	end


	self:ApplySettings()


	return window
end




function Skada:DeleteWindow(name)

	for i, win in ipairs(windows) do

		if win.db.name == name then

			win:destroy()
			wipe(tremove(windows, i))
		end
	end


	for i, win in ipairs(self.db.profile.windows) do

		if win.name == name then

			tremove(self.db.profile.windows, i)
		end
	end
end




function Skada:Print(msg)

	print("|cFF33FF99Skada|r: " .. msg)
end




function Skada:Debug(...)

	if not Skada.db.profile.debug then return end


	local msg = ""


	for i = 1, select("#", ...) do

		local val = select(i, ...)
		local v


		if issecretvalue and issecretvalue(val) then

			v = string.format("%s", val)
		else

			v = tostring(val)
		end

		
		if #msg > 0 then msg = msg .. ", " end


		msg = msg .. v
	end


	print("|cFF33FF99Skada Debug|r: " .. msg)
end




local function slashHandler(param)

	local reportusage = "/skada report [raid|party|instance|guild|officer|say] [current||total|set_num] [mode] [max_lines]"


	if param == "pets" then

		Skada:PetDebug()
	elseif param == "cpu" then

		local funcs = {}


		UpdateAddOnCPUUsage()


		for k, v in pairs(Skada) do

			if type(v) == "function" then

				local usage, calls = GetFunctionCPUUsage(v, true)


				tinsert(funcs, { ["name"] = k, ["usage"] = usage, ["calls"] = calls })
			end
		end


		tsort(funcs, function(a, b) return a.usage > b.usage end)


		for i, func in ipairs(funcs) do

			print(func.name .. '\t' .. func.usage .. ' (' .. func.calls .. ')')


			if i > 10 then break end
		end
	elseif param == "reset" then

		Skada:Reset()
	elseif param == "toggle" then

		Skada:ToggleWindow()
	elseif param == "debug" then

		Skada.db.profile.debug = not Skada.db.profile.debug
		Skada:Print("Debug mode " .. (Skada.db.profile.debug and ("|cFF00FF00" .. L["ENABLED"] .. "|r") or ("|cFFFF0000" .. L["DISABLED"] .. "|r")))
	elseif param == "config" then

		Skada:OpenOptions()
	elseif param:sub(1, 6) == "report" then

		local chan	       = (IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "instance") or (IsInRaid() and "raid") or (IsInGroup() and "party") or "say"
		local set	       = "current"
		local report_mode_name = L["Damage"]
		local w1, w2, w3, w4   = param:match("^%s*(%w*)%s*(%w*)%s*([^%d]-)%s*(%d*)%s*$", 7)


		if w1 and #w1 > 0 then chan = string.lower(w1) end


		if w2 and #w2 > 0 then

			w2 = tonumber(w2) or w2:lower()


			if Skada:find_set(w2) then set = w2 end
		end


		if w3 and #w3 > 0 then

			w3 = strtrim(w3)
			w3 = strtrim(w3, "'\"[]()")


			if find_mode(w3) then report_mode_name = w3 end
		end


		local max = tonumber(w4) or 10


		if chan == "instance" then chan = "instance_chat" end


		if chan == "say" or chan == "guild" or chan == "raid" or chan == "party" or chan == "officer" or chan == "instance_chat" then

			Skada:Report(chan, "preset", report_mode_name, set, max)
		else

			Skada:Print("Usage:")
			Skada:Print(("%-20s"):format(reportusage))
		end
	else

		Skada:Print("Usage:")
		Skada:Print(("%-20s"):format(reportusage))
		Skada:Print(("%-20s"):format("/skada reset"))
		Skada:Print(("%-20s"):format("/skada toggle"))
		Skada:Print(("%-20s"):format("/skada debug"))
		Skada:Print(("%-20s"):format("/skada config"))
	end
end




local function sendchat(msg, chan, chantype)

	if chantype == "self" then

		Skada:Print(msg)
	elseif chantype == "channel" then

		C_ChatInfo.SendChatMessage(msg, "CHANNEL", nil, chan)
	elseif chantype == "preset" then

		C_ChatInfo.SendChatMessage(msg, string.upper(chan))
	elseif chantype == "whisper" then

		C_ChatInfo.SendChatMessage(msg, "WHISPER", nil, chan)
	elseif chantype == "bnet" then

		C_BattleNet.SendWhisper(chan, msg)
	end
end




function Skada:Report(channel, chantype, report_mode_name, report_set_name, max, window)

	if chantype == "channel" then

		local list = { GetChannelList() }


		for i = 1, #list, 3 do

			if Skada.db.profile.report.channel == list[i + 1] then

				channel = list[i]


				break
			end
		end
	end


	local report_table
	local report_set
	local report_mode


	if not window then

		report_mode = find_mode(report_mode_name)
		report_set  = Skada:find_set(report_set_name)


		if #report_set == 0 and report_mode == nil then

			Skada:CurrentTooltip("MRM", "", "", "")


			return
		end


		report_table = Window:new()


		report_mode:Update(report_table, report_set)
	else

		report_table = window
		report_set   = window:get_selected_set()
		report_mode  = window.selectedmode
	end


	if not report_set then

		Skada:Print(L["There is nothing to report."])


		return
	end


	if not report_table.metadata.ordersort then tsort(report_table.dataset, Skada.valueid_sort) end


	sendchat(string.format(L["Skada: %s for %s:"], report_mode.title or report_mode:GetName(), Skada:GetSetLabel(report_set)), channel, chantype)


	local nr = 1


	for i, data in ipairs(report_table.dataset) do

		if data.id then

			local value = data.valuetext or data.valueText1
			local label = data.reportlabel or (data.spellid and GetSpellLink(data.spellid)) or data.label


			-- =========================
			-- | UTF8 space "\194\160" |
			-- =========================
			value = string.format("%s", string.rep("\194\160", 6 - string.len(value)) .. string.rep("\194\160", 4) .. value)
			label = string.format("%s", string.rep("\194\160", 6)  .. label)


			if report_mode.metadata and report_mode.metadata.showspots then

				sendchat(("%3u. %s %s"):format(nr, value, label), channel, chantype)

			else

				sendchat(("%s %s"):format(value, label), channel, chantype)
			end


			nr = nr + 1
		end


		if nr > max then break end
	end
end




function Skada:RefreshMMButton()

	if icon then

		icon:Refresh("Skada", self.db.profile.icon)


		if self.db.profile.icon.hide then

			icon:Hide("Skada")
		else

			icon:Show("Skada")
		end
	end
end





function Skada:ToggleWindow()

	local showing = false


	for i, win in ipairs(windows) do

		if win:IsShown() then

			showing = true


			break
		end
	end


	if showing then

		for i, win in ipairs(windows) do win:Hide() end
	else

		for i, win in ipairs(windows) do

			if not win.db.hidden then win:Show() end
		end
	end


	self:UpdateDisplay(true)
end




function Skada:CanReset()

	return true
end




function Skada:Reset()

	self:Wipe()


	if self.last ~= nil then

		wipe(self.last)


		self.last = nil
	end


	self.current = nil


	for i, win in ipairs(windows) do

		win:Reset()


		-- =======================================================
		-- | Prevents the segment from automatically switching	 |
		-- | from “All” to “Current” at the start of the battle. |
		-- |							 |
		-- | delete win.selectedset = "current"			 |
		-- =======================================================
		win.changed	= true
	end


	for i, mode in ipairs(modes) do

		if mode.OnReset then mode:OnReset() end
	end


	if not self._resetting and self.NativeAPI then

		self._resetting = true


		self.NativeAPI:ResetAllSessions()


		self._resetting = nil
	end


	self:UpdateDisplay(true)
end




function Skada:DeleteSet(set)

end




function Skada:ReloadSettings()

	for i, win in ipairs(windows) do win:destroy() end


	windows = {}


	for i, win in ipairs(self.db.profile.windows) do self:CreateWindow(win.name, win) end


	if icon and not icon:IsRegistered("Skada") then icon:Register("Skada", dataobj, self.db.profile.icon) end


	self:RefreshMMButton()
	self:ApplySettings()
end




function Skada:ApplySettings()

	for i, win in ipairs(windows) do

		if win.display and win.display.ApplySettings then win.display:ApplySettings(win) end
	end


	for i, win in ipairs(windows) do

		if (self.db.profile.hidesolo and not IsInGroup()) or (self.db.profile.hidepvp and IsInPVP()) then

			win:Hide()
		else

			if win.db.hidden then

				win:Hide()
			else

				win:Show()
			end
		end
	end


	self:UpdateDisplay(true)
end




function Skada:SetFeed(feed)

	selectedfeed = feed


	self:UpdateDisplay()
end




function Skada:Tick()

	if self.Simulation and self.Simulation.active then

		self.Simulation:Update()


		self:UpdateDisplay(true)


		return
	end


	local inCombat = InCombatLockdown()


	if inCombat and not self.current then

		self:Wipe()

		-- ===============================================================
		-- | Prevents the segment from automatically switching		 |
		-- | from “All” to “Current” at the start of the battle.	 |
		-- |								 |
		-- | delete self.current = { name = "Current", sessionType = 1 } |
		-- ===============================================================

		for i, win in ipairs(windows) do

			if win.db.returnaftercombat then

				win.restore_set  = win.selectedset
				win.restore_mode = win.selectedmode and win.selectedmode:GetName()
			end


			-- =======================================================
			-- | Prevents the segment from automatically switching	 |
			-- | from “All” to “Current” at the start of the battle. |
			-- |							 |
			-- | delete win.selectedset = "current"			 |
			-- =======================================================
			if win.db.modeincombat and win.db.modeincombat ~= "" then

				local mymode = find_mode(win.db.modeincombat)


				if mymode then win:DisplayMode(mymode) end
			elseif win.selectedmode then

				win.changed = true
			end


			if not win.db.hidden and self.db.profile.hidecombat then win:Hide() end
		end


		changed = true
	end


	if not inCombat and self.current then

		self.last    = self.current
		self.current = nil


		for i, win in ipairs(windows) do

			if win.db.returnaftercombat and win.restore_mode and win.restore_set then

				self:RestoreView(win, win.restore_set, win.restore_mode)


				win.restore_mode = nil
				win.restore_set = nil
			end


			if not win.db.hidden and self.db.profile.hidecombat and (not self.db.profile.hidesolo or IsInGroup()) then win:Show() end
		end


		changed = true
	end


	if inCombat or changed then self:UpdateDisplay(true) end


	if not inCombat then self:UpdateDisplay(true) end
end




function Skada:Wipe()

	for i, win in ipairs(windows) do win:Wipe() end
end




function Skada:RestoreView(win, theset, themode)

	if theset and type(theset) == "string" and (theset == "current" or theset == "total" or theset == "last") then

		win.selectedset = theset
	elseif theset and type(theset) == "number" then

		win.selectedset = tostring(theset)
	else

		win.selectedset = "current"
	end


	changed = true


	if themode then

		local mymode = find_mode(themode)


		if mymode then

			win:DisplayMode(mymode)
		else

			win:DisplayModes(win.selectedset)
		end
	else

		win:DisplayModes(win.selectedset)
	end
end




function Skada:find_set(s)

	if s == "current" then

		local set = self.NativeAPI:GetCurrentSession()


		if set then 

			set.sessionType = 1
			self:Debug("find_set('current') returned session with", set.combatSources and #set.combatSources or 0, "sources")
		else

			self:Debug("find_set('current') returned nil")
		end


		return set
	elseif s == "total" then

		local set = self.NativeAPI:GetTotalSession()


		if set then set.sessionType = 0 end


		return set
	elseif type(s) == "number" then

		return self.NativeAPI:GetSessionByID(s)
	elseif type(s) == "string" and tonumber(s) then

		return self.NativeAPI:GetSessionByID(tonumber(s))
	end


	return s
end



function Skada:find_player_in_session(session, playerGUID)

	if not session or not playerGUID then return nil end


	local sources	 = session.combatSources or session.participants or {}
	local idType	 = type(playerGUID)
	local idIsSecret = (issecretvalue and issecretvalue(playerGUID))


	if idType == "string" and not idIsSecret and playerGUID:sub(1, 7) == "combat_" then

		local index = tonumber(playerGUID:sub(8))

		if index then

			local nr = 1


			for k, p in pairs(sources) do

				if nr == index then return p end


				nr = nr + 1
			end
		end


		return nil
	end


	if idIsSecret then

		for k, p in pairs(sources) do

			local success, matches = pcall(function()

							       return (p.sourceGUID == playerGUID) or (k == playerGUID) or (p.guid == playerGUID) or (p.unitGUID == playerGUID) or (p.id == playerGUID)
							end

						      )


			if success and matches then return p end
		end
	else

		for k, p in pairs(sources) do

			if (p.sourceGUID == playerGUID) or (k == playerGUID) or (p.guid == playerGUID) or (p.unitGUID == playerGUID) or (p.id == playerGUID) then return p end
		end
	end


	return nil
end




function Skada:find_player(set, playerGUID)

	return self:find_player_in_session(set, playerGUID)
end




function Skada:DAMAGE_METER_COMBAT_SESSION_UPDATED()

	changed = true
end




function Skada:DAMAGE_METER_CURRENT_SESSION_UPDATED()

	changed = true
end




function Skada:DAMAGE_METER_RESET()

	if self._resetting then return end


	self:Reset()
end




local function check_reset(setting)

	if setting == 2 then

		Skada:Reset()
	elseif setting == 3 then

		Skada:ShowPopup()
	end
end




function Skada:GROUP_ROSTER_UPDATE()

	local wasInGroup = self._wasInGroup
	local inGroup	 = IsInGroup()


	self._wasInGroup = inGroup


	if wasInGroup == nil then return end


	if not wasInGroup and inGroup then

		check_reset(self.db.profile.reset.join)
	elseif wasInGroup and not inGroup then

		check_reset(self.db.profile.reset.leave)
	end


	self:ApplySettings()
end




function Skada:ZONE_CHANGED_NEW_AREA()

	local wasInInstance = self._wasInInstance
	local inInstance    = IsInInstance()


	self._wasInInstance = inInstance


	if wasInInstance == nil then return end


	if not wasInInstance and inInstance then check_reset(self.db.profile.reset.instance) end
end




-- ===============
-- | Data broker |
--================
function dataobj:OnEnter()

	GameTooltip:SetOwner(self, "ANCHOR_NONE")
	GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
	GameTooltip:ClearLines()


	GameTooltip:AddLine("\124cFF00FF00" .. L["Hint:"])
	GameTooltip:AddLine(" ")
	GameTooltip:AddLine("\124cnNORMAL_FONT_COLOR:" .. L["Left-Click to toggle Skada window."], 0, 1, 0)
	GameTooltip:AddLine("\124cnNORMAL_FONT_COLOR:" .. L["Shift + Left-Click to reset."], 0, 1, 0)
	GameTooltip:AddLine("\124cnNORMAL_FONT_COLOR:" .. L["Right-click to open menu"], 0, 1, 0)


	GameTooltip:Show()
end



function dataobj:OnLeave()

	GameTooltip:Hide()
end




function dataobj:OnClick(button)

	if button == "LeftButton" and IsShiftKeyDown() then

		Skada:Reset()
	elseif button == "LeftButton" then

		Skada:ToggleWindow()
	elseif button == "RightButton" then

		Skada:OpenMenu()
	end
end


local totalbarcolor = {r = 0.2, g = 0.2, b = 0.5, a = 1}
local bossicon	    = "Interface\\Icons\\Achievment_boss_ultraxion"
local nonbossicon   = "Interface\\Icons\\icon_petfamily_critter"


function Skada:UpdateDisplay(force)

	if force then changed = true end


	if selectedfeed ~= nil then

		local feedtext = selectedfeed()

		if feedtext then dataobj.text = feedtext end
	end


	for i, win in ipairs(windows) do

		if changed or win.changed or self.current then

			win.changed = false


			if win.selectedmode then

				win:set_mode_title()


				local set = win:get_selected_set()


				if set then

					win:UpdateInProgress()


					if win.selectedmode.Update then

						win.selectedmode:Update(win, set)
					else

						self:Print("Mode " .. win.selectedmode:GetName() .. " does not have an Update function!")
					end


					if self.db.profile.showtotals and win.selectedmode.FormatSetSummary then

						local total    = 0
						local existing = nil


						for i, data in ipairs(win.dataset) do

							if data.id then

								local val = Skada:SafeNumber(data.value)
								total = total + val
							end


							if not existing and not data.id then existing = data end
						end


						total = total + 1


						local d = existing or {}


						win.selectedmode:FormatSetSummary(d, set)


						d.value	 = total
						d.label	 = L["Total"]
						d.icon	 = dataobj.icon
						d.id	 = "total"
						d.ignore = true


						if not existing then tinsert(win.dataset, 1, d) end
					end
				end


				if win.display and win.display.Update then

					local success, err = pcall(win.display.Update, win.display, win)


					if not success and Skada.db.profile.debug then Skada:Debug("Display Update Error (Inside Mode):", err) end
				end
			elseif win.selectedset then

				local set = win:get_selected_set()


				if win.changed or not win.metadata.is_modelist then

					if Skada.db.profile.debug then Skada:Debug("View Change to Mode List. Wiping.") end


					win:Wipe()
				end


				if Skada.db.profile.debug then

					Skada:Debug("Updating Mode List. Modes count:", #modes, "Set exists:", set ~= nil)


					if set then

						Skada:Debug("  Set Name:", set.name or "Current/Unknown")
						Skada:Debug("  Sources count:", set.combatSources and #set.combatSources or 0)
					end
				end


				for i, mode in ipairs(modes) do

					local d = win.dataset[i] or {}


					win.dataset[i] = d


					d.id	= mode:GetName()
					d.label = mode:GetName()
					d.value = 1


					if set and mode.FormatSetSummary ~= nil then

						local success, err = pcall(mode.FormatSetSummary, mode, d, set)


						if not success and Skada.db.profile.debug then Skada:Debug("FormatSetSummary Error in", mode:GetName(), ":", err) end
					end


					if mode.metadata and mode.metadata.icon then d.icon = mode.metadata.icon end
				end


				if Skada.db.profile.debug then Skada:Debug("Mode List Dataset size:", #win.dataset) end


				win.metadata.ordersort = true
				win.metadata.maxvalue = 1


				if set then win.metadata.is_modelist = true end


				if win.display and win.display.Update then

					local success, err = pcall(win.display.Update, win.display, win)


					if not success and Skada.db.profile.debug then Skada:Debug("Display Update Error (Mode List):", err) end
				end
			else

				if win.changed or win.metadata.is_modelist or win.selectedmode then win:Wipe() end

				local nr = 1
				local d  = win.dataset[nr] or {}


				win.dataset[nr] = d


				d.id    = "total"
				d.label = L["Total"]
				d.value = 1


				if self.total and self.total.gotboss then

					d.icon = bossicon
				else

					d.icon = nonbossicon
				end


				nr = nr + 1


				d		= win.dataset[nr] or {}
				win.dataset[nr] = d


				d.id	= "current"
				d.label = L["Current"]
				d.value = 1


				if self.current and self.current.gotboss then

					d.icon = bossicon
				else

					d.icon = nonbossicon
				end


				for i, set in ipairs(self:GetSets()) do

					nr = nr + 1


					d		= win.dataset[nr] or {}
					win.dataset[nr] = d


					d.id	= set.sessionID or set.startTime or i
					d.label = self:GetSetLabel(set)
					d.value = 1


					if set.gotboss then

						d.icon = bossicon
					else

						d.icon = nonbossicon
					end
				end


				win.metadata.ordersort = true


				if win.display and win.display.Update then

					local success, err = pcall(win.display.Update, win.display, win)


					if not success and Skada.db.profile.debug then Skada:Debug("Display Update Error (Set List):", err) end
				end
			end
		end
	end


	changed = false
end




function Skada:GetSets()

	if self.NativeAPI then return self.NativeAPI:GetAvailableSessions() end


	return {}
end




function Skada:GetModes(sortfunc)

	return modes
end




function Skada:FormatNumber(number)

	if not number or number == 0 then return "" end
	if issecretvalue and issecretvalue(number) then	return "?" end


	if self.db.profile.numberformat == 1 then

		if number >= 1000000 then

			return ("%.1fM"):format(number / 1000000)
		elseif number >= 1000 then

			return ("%4.1fK"):format(number / 1000)
		elseif number > 0 then

			return ("%4.0fS"):format(number)
		end
	end


	return tostring(math.floor(number))
end




function Skada:FormatNumberSecret(number)

	if issecretvalue and issecretvalue(number) then

		local s = string.format("%.0f", number)


		return tostring(s)
	end


	return self:FormatNumber(number)
end




function Skada:SafeNumber(value)

	if not value then return 0 end
	if type(value) ~= "number" then return 0 end
	if issecretvalue and issecretvalue(value) then return 0 end


	return value
end


local numorder = 5


function Skada:AddDisplaySystem(key, mod)

	self.displays[key] = mod

	if mod.description then

		Skada.options.args.windows.args[key .. "desc"] =

								{
									type  = "description"		,
									name  = mod.description		,
									order = numorder
								}

		numorder = numorder + 1
	end
end




function Skada:AddMode(mode, category)

	mode.category = category or L['Other']


	tinsert(modes, mode)


	for i, win in ipairs(windows) do

		if mode:GetName() == win.db.mode then self:RestoreView(win, win.db.set, mode:GetName())	end
	end


	if selectedfeed == nil and self.db.profile.feed ~= "" then

		for name, feed in pairs(feeds) do

			if name == self.db.profile.feed then self:SetFeed(feed)	end
		end
	end


	sort_modes()


	for i, win in ipairs(windows) do win:Wipe() end


	changed = true
end




function Skada:RemoveMode(mode)

	tremove(modes, mode)
end




function Skada:GetFeeds()

	return feeds
end




function Skada:AddFeed(name, func)

	feeds[name] = func
end




function Skada:RemoveFeed(name, func)

	for i, feed in ipairs(feeds) do

		if feed.name == name then tremove(feeds, i) end
	end
end




function Skada:GetSetTime(set)

	if set.startTime then

		if set.endTime then

			return (set.endTime - set.startTime)
		else

			return (time() - set.startTime)
		end
	elseif set.time then

		return set.time
	elseif set.starttime then

		return (time() - set.starttime)
	else

		return 0
	end
end




function Skada:PlayerActiveTime(set, player)

	local hasActivity = false
	

	if Skada:SafeNumber(player.totalAmount or 0) > 0 then

		hasActivity = true
	elseif Skada:SafeNumber(player.healing or 0) > 0 then

		hasActivity = true
	elseif Skada:SafeNumber(player.damageTaken or 0) > 0 then

		hasActivity = true
	elseif Skada:SafeNumber(player.dispels or 0) > 0 then

		hasActivity = true
	elseif Skada:SafeNumber(player.interrupts or 0) > 0 then

		hasActivity = true
	end

	
	if not hasActivity then return 0 end
	

	local startTime = set.startTime or set.starttime or 0
	local endTime = set.endTime or set.endtime or time()

	
	if endTime <= startTime then return 0 end


	return endTime - startTime
end




function Skada:SetTooltipPosition(tooltip, frame)

	local p = self.db.profile.tooltippos


	if p == "default" then

		tooltip:SetOwner(UIParent, "ANCHOR_NONE")
		tooltip:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMRIGHT", -40, 40);
	elseif p == "topleft" then

		tooltip:SetOwner(frame, "ANCHOR_NONE")
		tooltip:SetPoint("TOPRIGHT", frame, "TOPLEFT")
	elseif p == "topright" then

		tooltip:SetOwner(frame, "ANCHOR_NONE")
		tooltip:SetPoint("TOPLEFT", frame, "TOPRIGHT")
	elseif p == "smart" and frame then

		if frame:GetLeft() < (GetScreenWidth() / 2) then

			tooltip:SetOwner(frame, "ANCHOR_NONE")
			tooltip:SetPoint("TOPLEFT", frame, "TOPRIGHT", 10, 0)
		else

			tooltip:SetOwner(frame, "ANCHOR_NONE")
			tooltip:SetPoint("TOPRIGHT", frame, "TOPLEFT", -10, 0)
		end
	end
end




function Skada:FormatValueText(datasetItem, ...)

	local value1, bool1, value2, bool2, value3, bool3 = ...

	if not datasetItem then return end


	if type(datasetItem) == "string" then return datasetItem end


	if bool1 then datasetItem.valueText1 = value1 end
	if bool2 then datasetItem.valueText2 = value2 end
	if bool3 then datasetItem.valueText3 = value3 end
end


local SafeNumber = Skada.SafeNumber


local function value_sort(a, b)

	if not a or not a.id then

		return false
	elseif not b or not b.id then

		return true
	elseif a.value == nil then

		return false
	elseif b.value == nil then

		return true
	else

		return SafeNumber(Skada, a.value) > SafeNumber(Skada, b.value)
	end
end


Skada.value_sort = value_sort


function Skada.valueid_sort(a, b)

	if not a or not a.id or a.value == nil then

		return false
	elseif not b or not b.id or b.value == nil then

		return true
	else

		return Skada:SafeNumber(a.value) > Skada:SafeNumber(b.value)
	end
end


local ttwin = Window:new()
local white = { r = 1, g = 1, b = 1 }


function Skada:AddSubviewToTooltip(tooltip, win, mode, id, label)

	wipe(ttwin.dataset)


	if mode.Enter then mode:Enter(win, id, label) end


	mode:Update(ttwin, win:get_selected_set())


	if not mode.metadata or not mode.metadata.ordersort then tsort(ttwin.dataset, value_sort) end


	if #ttwin.dataset > 0 then

		tooltip:AddLine(mode.title or mode:GetName(), NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)


		local nr = 0


		for i, data in ipairs(ttwin.dataset) do

			if data.id and nr < Skada.db.profile.tooltiprows then

				nr = nr + 1


				local color = white


				if data.color then

					color = data.color
				elseif data.class then

					local color = Skada.classcolors[data.class]
				end


				local label = data.label


				if mode.metadata and mode.metadata.showspots then label = nr .. ". " .. label end


				if data.valuetext then

					tooltip:AddDoubleLine(label, data.valuetext, color.r, color.g, color.b)
				else

					tooltip:AddDoubleLine(label, data.valueText1 or "", color.r, color.g, color.b)
				end
			end
		end


		if mode.Enter then tooltip:AddLine(" ")	end
	end
end



-- =========================================
-- | Generic tooltip function for displays |
-- =========================================
function Skada:ShowTooltip(win, id, label)

	local t = GameTooltip


	if Skada.db.profile.tooltips then

		if win.metadata.is_modelist and Skada.db.profile.informativetooltips then

			t:ClearLines()


			Skada:AddSubviewToTooltip(t, win, find_mode(id), id, label)


			t:Show()
		elseif win.metadata.click1 or win.metadata.click2 or win.metadata.click3 or win.metadata.tooltip then

			t:ClearLines()


			local hasClick = win.metadata.click1 or win.metadata.click2 or win.metadata.click3


			if win.metadata.tooltip then

				local numLines = t:NumLines()


				win.metadata.tooltip(win, id, label, t)


				if t:NumLines() ~= numLines and hasClick then t:AddLine(" ") end
			end


			if Skada.db.profile.informativetooltips then

				if win.metadata.click1 then Skada:AddSubviewToTooltip(t, win, win.metadata.click1, id, label) end
				if win.metadata.click2 then Skada:AddSubviewToTooltip(t, win, win.metadata.click2, id, label) end
				if win.metadata.click3 then Skada:AddSubviewToTooltip(t, win, win.metadata.click3, id, label) end
			end


			if win.metadata.post_tooltip then

				local numLines = t:NumLines()


				win.metadata.post_tooltip(win, id, label, t)


				if t:NumLines() ~= numLines and hasClick then t:AddLine(" ") end
			end


			if win.metadata.click1 then t:AddLine(L["Click for"]	     .. " " .. win.metadata.click1:GetName() .. ".", 0.2, 1, 0.2) end
			if win.metadata.click2 then t:AddLine(L["Shift-Click for"]   .. " " .. win.metadata.click2:GetName() .. ".", 0.2, 1, 0.2) end
			if win.metadata.click3 then t:AddLine(L["Control-Click for"] .. " " .. win.metadata.click3:GetName() .. ".", 0.2, 1, 0.2) end


			t:Show()
		end
	end
end


local borderbackdrop = {}


-- ==================
-- | Generic border |
-- ==================
function Skada:ApplyBorder(frame, texture, color, thickness, padtop, padbottom, padleft, padright)

	wipe(borderbackdrop)


	if not frame.borderFrame then

		frame.borderFrame = CreateFrame("Frame", nil, frame, "BackdropTemplate")
		frame.borderFrame:SetFrameLevel(0)
	end


	frame.borderFrame:SetPoint("TOPLEFT", frame, -thickness - (padleft or 0), thickness + (padtop or 0))
	frame.borderFrame:SetPoint("BOTTOMRIGHT", frame, thickness + (padright or 0), -thickness - (padbottom or 0))


	if texture and thickness > 0 then

		borderbackdrop.edgeFile = media:Fetch("border", texture)
	else

		borderbackdrop.edgeFile = nil
	end


	borderbackdrop.edgeSize = thickness


	frame.borderFrame:SetBackdrop(borderbackdrop)


	if color then frame.borderFrame:SetBackdropBorderColor(color.r, color.g, color.b, color.a) end
end



-- 2028
-- ==========================
-- | Generic frame settings |
-- ==========================
function Skada:FrameSettings(db, include_dimensions)


	      local obj =

			{
				   type = "group"																		,
				   name = L["Appearance"]																	,
				  order = function()

						  if GetLocale() == "deDE" then return 1 end
						  if GetLocale() == "enUS" then return 1 end
					  end																			,

				   args =

					{

						      apperance =

								{

									   type = "description"													,
									   name = "\124cnNORMAL_FONT_COLOR:" .. L["Options: apperance"] .. " " .. "|cffffffff" .. db.name .. emptyLines		,
									  order = 0

								},

						       bgheader =

								{
									   type = "header"													,
									   name = "\124cnNORMAL_FONT_COLOR:" .. L["Background"]									,
									  order = 1
								}	,

							texture =

								{

									   type = 'select'													,
								  dialogControl = 'LSM30_Background'												,
									   name = blankChars2 .. "\124cFF00FF00" .. L["Background texture"] 							,
									   desc = L["The texture used as the background."]									,
									 values = AceGUIWidgetLSMlists.background										,
									    get = function()

											  return db.background.texture
										  end														,
									    set = function(win, key)

											  db.background.texture = key


											  Skada:ApplySettings()
										  end														,
									  width = "double"													,
									  order = 2

								}	,

							   tile =

								{
									   type = 'toggle'													,
									   name = L["Tile"]													,
									   desc = L["Tile the background texture."]										,
									    get = function() return db.background.tile end									,
									    set = function(win, key)

										  db.background.tile = key


										  Skada:ApplySettings()
										  end														,
									order = 3

								}	,

						       tilesize =

								{

									   type = "range"													,
									   name = "\124cFF00FF00" .. L["Tile size"]										,
									   desc = L["The size of the texture pattern."]										,
									    min = 0														,
									    max = math.floor(GetScreenWidth())											,
									   step = 1.0														,
									    get = function()

											  return db.background.tilesize
										  end														,
									    set = function(win, val)
											  db.background.tilesize = val
	

											  Skada:ApplySettings()
										  end														,
									  order = 4

								}	,

							  color =

								{

									   type = "color"													,
									   name = L["Background color"]												,
									   desc = L["The color of the background."]										,
								       hasAlpha = true														,
									    get = function(i)

											  local c = db.background.color


											  return c.r, c.g, c.b, c.a
										  end														,
									    set = function(i, r, g, b, a)

											  db.background.color = { ["r"] = r, ["g"] = g, ["b"] = b, ["a"] = a }


											  Skada:ApplySettings()
										  end														,
									  order = 5
								}	,

							opacity =

								 {

										   type = "range"												,
										   name = "\124cFF00FF00" .. L["Opacity"]									,
										   desc = L["The opacity of the background."]									,
										    min = 0													,
										    max = 1													,
										   step = 0.01													,
									      isPercent = true													,
										    get = function()

												  return db.background.color.a
											   end													,
										    set = function(win, val)

												  db.background.color.a = val


												  Skada:ApplySettings()
											  end													,
									  order = 6

								}	,

						   borderheader =

								{

									   type = "header"													,
									   name = "\124cnNORMAL_FONT_COLOR:" .. "|cffffffff" .. L["Border"]							,
									  order = 7

								},

						  bordertexture =

								{
										   type = "select"												,
									  dialogControl = "LSM30_Border"											,
										   name = blankChars2 .. "\124cFF00FF00" .. L["Border texture"]							,
										   desc = L["The texture used for the borders."]								,
										 values = AceGUIWidgetLSMlists.border										,
										    get = function()

												  return db.background.bordertexture
											  end													,
										   set = function(win, key)

												 db.background.bordertexture = key


												 Skada:ApplySettings()
											 end													,
										  width = "double"												,
										  order = 8

								}	,

						    bordercolor =

								{

									   type = "color"													,
									   name = L["Border color"]												,
									   desc = L["The color used for the border."]										,
								       hasAlpha = true,
									    get = function(i)

											  local c = db.background.bordercolor or {r = 0, g = 0, b = 0, a = 1}


											  return c.r, c.g, c.b, c.a
										  end														,
									    set = function(i, r, g, b, a)

											  db.background.bordercolor = { ["r"] = r, ["g"] = g, ["b"] = b, ["a"] = a }


											  Skada:ApplySettings()
										  end														,
									order = 9

								}	,

						      thickness =

								{

										   type = "range"												,
										   name = "\124cFF00FF00" .. L["Border thickness"]								,
										   desc = L["The thickness of the borders."]									,
										    min = 0													,
										    max = 50													,
										   step = 0.5													,
										    get = function()

												  return db.background.borderthickness
											  end													,
										    set = function(win, val)

												  db.background.borderthickness = val


												  Skada:ApplySettings()
											  end													,
										  order = 10

								}	,

						   optionheader =

								{

										   type = "header"												,
										   name = "\124cnNORMAL_FONT_COLOR:" .. L["General"]								,
										  order = 11

								}	,

							  scale =

								{

										   type = "range"												,
										   name = "\124cFF00FF00" .. L["Scale"]										,
										   desc = L["Sets the scale of the window."]									,
										    min = 0.1													,
										    max = 3													,
										   step = 0.01													,
										    get = function()

												  return db.scale
											  end													,
										    set = function(win, val)

												  db.scale = val


												  Skada:ApplySettings()
											  end													,
										  order = 12

								}	,

							 strata =

								{

										   type = "select"												,
										   name = "\124cFF00FF00" .. L["Strata"]									,
										   desc = L["This determines what other frames will be in front of the frame."]					,

										 values =

											{

												["BACKGROUND"]	      = "BACKGROUND"								,
												["LOW"]		      = "LOW"									,
												["MEDIUM"]	      = "MEDIUM"								,
												["HIGH"]	      = "HIGH"									,
												["DIALOG"]	      = "DIALOG"								,
												["FULLSCREEN"]	      = "FULLSCREEN"								,
												["FULLSCREEN_DIALOG"] = "FULLSCREEN_DIALOG"

											}													,	

										    get = function()

												  return db.strata
											  end													,
										    set = function(win, val)

												  db.strata = val


												  Skada:ApplySettings()
											  end													,
										  order = 13
								}	,
					}
			}


	if include_dimensions then

		 obj.args.width =

				{

					   type = "range"																	,
					   name = L["Width"]																	,
					    min = 100																		,
					    max = 600																		,
					   step = 1.0																		,
					    get = function()

							  return db.width
						  end																		,
					    set = function(win, key)

							  db.width = key


							  Skada:ApplySettings()
						  end																		,
					  order = 4.3
				}

		obj.args.height =

				{

					   type = "range"																	,
					   name = L["Height"]																	,
					    min = 16																		,
					    max = 400																		,
					   step = 1.0																		,
					    get = function()

							  return db.height
						  end																		,
					    set = function(win, key)

							  db.height = key


							  Skada:ApplySettings()
						  end																		,
					  order = 4.4

				}
	end


	return obj
end




function Skada:OnInitialize()

	-- =====================
	-- | Font proportional |
	-- =====================
	media:Register("font"		, "ABF"				, [[Interface\Addons\Skada\Media\Fonts\ABF.ttf]])
	media:Register("font"		, "Accidental Presidency"	, [[Interface\Addons\Skada\Media\Fonts\AccidentalPresidency.ttf]])
	media:Register("font"		, "Adventure"			, [[Interface\Addons\Skada\Media\Fonts\Adventure.ttf]])
	media:Register("font"		, "Diablo"			, [[Interface\Addons\Skada\Media\Fonts\Avqest.ttf]])
	media:Register("font"		, "PT Sans Narrow"		, [[Interface\Addons\Skada\Media\Fonts\PTSansNarrow.ttf]])
	media:Register("font"		, "Vera Serif"			, [[Interface\Addons\Skada\Media\Fonts\VeraSe.ttf]])


	-- =====================================
	-- | Font non-proportional (monospace) |
	-- =====================================
	media:Register("font"		, "Cousine Bold"		, [[Interface\Addons\Skada\Media\Fonts\CousineBold.ttf]])
	media:Register("font"		, "Input Mono"			, [[Interface\Addons\Skada\Media\Fonts\InputMonoCondensed-Regular.ttf]])
	media:Register("font"		, "Roboto Mono"			, [[Interface\Addons\Skada\Media\Fonts\RobotoMono-Regular.ttf]])


	-- =============
	-- | Statusbar |
	-- =============
	media:Register("statusbar"	, "Aluminium"			, [[Interface\Addons\Skada\Media\Statusbar\Aluminium]])
	media:Register("statusbar"	, "Armory"			, [[Interface\Addons\Skada\Media\Statusbar\Armory]])
	media:Register("statusbar"	, "BantoBar"			, [[Interface\Addons\Skada\Media\Statusbar\BantoBar]])
	media:Register("statusbar"	, "Glaze2"			, [[Interface\Addons\Skada\Media\Statusbar\Glaze2]])
	media:Register("statusbar"	, "Gloss"			, [[Interface\Addons\Skada\Media\Statusbar\Gloss]])
	media:Register("statusbar"	, "Graphite"			, [[Interface\Addons\Skada\Media\Statusbar\Graphite]])
	media:Register("statusbar"	, "Grid"			, [[Interface\Addons\Skada\Media\Statusbar\Grid]])
	media:Register("statusbar"	, "Healbot"			, [[Interface\Addons\Skada\Media\Statusbar\Healbot]])
	media:Register("statusbar"	, "LiteStep"			, [[Interface\Addons\Skada\Media\Statusbar\LiteStep]])
	media:Register("statusbar"	, "Minimalist"			, [[Interface\Addons\Skada\Media\Statusbar\Minimalist]])
	media:Register("statusbar"	, "Otravi"			, [[Interface\Addons\Skada\Media\Statusbar\Otravi]])
	media:Register("statusbar"	, "Outline"			, [[Interface\Addons\Skada\Media\Statusbar\Outline]])
	media:Register("statusbar"	, "Perl"			, [[Interface\Addons\Skada\Media\Statusbar\Perl]])
	media:Register("statusbar"	, "Smooth"			, [[Interface\Addons\Skada\Media\Statusbar\Smooth]])
	media:Register("statusbar"	, "Round"			, [[Interface\Addons\Skada\Media\Statusbar\Round]])
	media:Register("statusbar"	, "TukTex"			, [[Interface\Addons\Skada\Media\Statusbar\normTex]])
	media:Register("statusbar"	, "Skada Modern"		, [[Interface\Addons\Skada\Media\Statusbar\SkadaModern]])
	media:Register("statusbar"	, "Skada Glass"			, [[Interface\Addons\Skada\Media\Statusbar\SkadaGlass]])
	media:Register("statusbar"	, "Skada Material"		, [[Interface\Addons\Skada\Media\Statusbar\SkadaMaterial]])


	-- ==========
	-- | Border |
	-- ==========
	media:Register("border"		, "Glow"			, [[Interface\Addons\Skada\Media\Border\glowTex]])
	media:Register("border"		, "Roth"			, [[Interface\Addons\Skada\Media\Border\roth]])


	-- ==============
	-- | Background |
	-- ==============
	media:Register("background"	, "Copper"			, [[Interface\Addons\Skada\media\background\copper]])


	-- =========
	-- | Sound |
	-- =========
	media:Register("sound"		, "Rubber Ducky"		, 566121)	--	[[Sound\Doodad\Goblin_Lottery_Open01.ogg]]
	media:Register("sound"		, "Cartoon FX"			, 566543)	--	[[Sound\Doodad\Goblin_Lottery_Open03.ogg]]
	media:Register("sound"		, "Explosion"			, 566982)	--	[[Sound\Doodad\Hellfire_Raid_FX_Explosion05.ogg]]
	media:Register("sound"		, "Shing!"			, 566240)	--	[[Sound\Doodad\PortcullisActive_Closed.ogg]]
	media:Register("sound"		, "Wham!"			, 566946)	--	[[Sound\Doodad\PVP_Lordaeron_Door_Open.ogg]]
	media:Register("sound"		, "Simon Chime"			, 566076)	--	[[Sound\Doodad\SimonGame_LargeBlueTree.ogg]]
	media:Register("sound"		, "War Drums"			, 567275)	--	[[Sound\Event Sounds\Event_wardrum_ogre.ogg]]
	media:Register("sound"		, "Cheer"			, 567283)	--	[[Sound\Event Sounds\OgreEventCheerUnique.ogg]]
	media:Register("sound"		, "Humm"			, 569518)	--	[[Sound\Spells\SimonGame_Visual_GameStart.ogg]]
	media:Register("sound"		, "Short Circuit"		, 568975)	--	[[Sound\Spells\SimonGame_Visual_BadPress.ogg]]
	media:Register("sound"		, "Fel Portal"			, 569215)	--	[[Sound\Spells\Sunwell_Fel_PortalStand.ogg]]
	media:Register("sound"		, "Fel Nova"			, 568582)	--	[[Sound\Spells\SeepingGaseous_Fel_Nova.ogg]]
	media:Register("sound"		, "You Will Die!"		, 546633)	--	[[Sound\Creature\CThun\CThunYouWillDie.ogg]]


	-- ======
	-- | DB |
	-- ======
	self.db = LibStub("AceDB-3.0"):New("SkadaDB", self.defaults, "Default")


	LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Skada", self.options, true)


	-- ==================================
	-- | Profiles (including dual spec) |
	-- ==================================
	LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Skada-Profiles",	LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db), true)


	local profiles = LibStub('AceDBOptions-3.0'):GetOptionsTable(self.db)


	profiles.order = 600
	profiles.disabled = false

	Skada.options.args.profiles = profiles


	if lds then

		lds:EnhanceDatabase(self.db, "SkadaDB")
		lds:EnhanceOptions(LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db), self.db)
	end




	-- ==========================
	-- | Blizzard options frame |
	-- ==========================
	local panel = CreateFrame("Frame", "SkadaBlizzOptions")

	panel.name = "Skada"
	InterfaceOptions_AddCategory(panel)


	local fs = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")


	fs:SetPoint("TOPLEFT", 10, -15)
	fs:SetPoint("BOTTOMRIGHT", panel, "TOPRIGHT", 10, -45)
	fs:SetJustifyH("LEFT")
	fs:SetJustifyV("TOP")
	fs:SetText("Skada")


	local button = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")


	button:SetText(L["Configure"])
	button:SetWidth(128)
	button:SetPoint("TOPLEFT", 10, -48)


	button:SetScript('OnClick',

				function()

					while CloseWindows() do end


					return Skada:OpenOptions()
				end

			)


	-- ========================
	-- | Skada slash handling |
	-- ========================
	SLASH_SKADA1	   = "/skada"
	SlashCmdList.SKADA = slashHandler


	self.db.RegisterCallback(self, "OnProfileChanged", "ReloadSettings")
	self.db.RegisterCallback(self, "OnProfileCopied" , "ReloadSettings")
	self.db.RegisterCallback(self, "OnProfileReset"  , "ReloadSettings")

end




function Skada:OpenOptions(window)

	acd:SetDefaultSize("Skada", 800, 600)


	if window then

		acd:Open("Skada")
		acd:SelectGroup("Skada", "windows", window.db.name)
	elseif not acd:Close("Skada") then

		acd:Open("Skada")
	end
end




function Skada:OnEnable()

	self:ReloadSettings()


	if not self.NativeAPI then

		self:Print("|cFFFF0000Error:|r NativeAPI not loaded! Skada requires WoW 12.0.0+")


		return
	end


	if self.moduleList then

		for i = 1, #self.moduleList do self.moduleList[i](self, L) end


		self.moduleList = nil
	end


	-- =======================================
	-- | Register native damage meter events |
	-- =======================================
	popup:RegisterEvent("DAMAGE_METER_COMBAT_SESSION_UPDATED")
	popup:RegisterEvent("DAMAGE_METER_CURRENT_SESSION_UPDATED")


	self.NativeAPI:TestSessionTypes()
	self:ScheduleRepeatingTimer("Tick", self.db.profile.updatefrequency or 0.25)


	popup:RegisterEvent("GROUP_ROSTER_UPDATE")
	popup:RegisterEvent("ZONE_CHANGED_NEW_AREA")


	self._wasInGroup    = IsInGroup()
	self._wasInInstance = IsInInstance()


	media.RegisterCallback(self, "LibSharedMedia_Registered", "OnMediaRegistered")
end




function Skada:OnMediaRegistered(event, mediaType, key)

	self:ApplySettings()
	
end




function Skada:AddLoadableModule(name, description, func)

	if not self.moduleList then self.moduleList = {} end


	self.moduleList[#self.moduleList + 1] = func
	self:AddLoadableModuleCheckbox(name, L[name], description and L[description])
end




-- ==============================
-- | necessary routines (start) |
-- ==============================


	-- =================================
	-- | validation: skada window name |
	-- =================================
	function Skada:CheckWindowNames(inputName)

		local outputIsAllowedName = true


		if string.len(inputName) == 0 or string.len(inputName) > 15 then

			outputHasName	    = ""
			outputIsAllowedName = false
		else

			for position = 1, string.len(inputName) do

				local character = string.sub(inputName, position, position)


				if not string.match(character, "[a-zA-Z]+") then

					outputHasName	    = ""
					outputIsAllowedName = false


					break
				end
			end


			if outputIsAllowedName == true then outputHasName = string.sub(string.upper(inputName), 1 ,1) .. string.sub(string.lower(inputName), 2) end
		end


		return outputHasName, outputIsAllowedName
	end




	-- ==================================
	-- | check: skada window duplicates |
	-- ==================================
	function Skada:CheckWindowDuplicates(inputName, eventName)

		local hash	      = {}

		local res	      = {}
		local rCardSkadaNames = 0

		local uSkadaNames     = {}
		local uCardSkadaNames = 0


		for position, currentName in pairs(CurrentSkadaNames) do table.insert(uSkadaNames, currentName)	end


		table.insert(uSkadaNames, inputName)
		uCardSkadaNames = #uSkadaNames


		for position, newCurrentName in pairs(uSkadaNames) do

			if (not hash[newCurrentName]) then

				res[#res+1]	     = newCurrentName
				hash[newCurrentName] = true
			end
		end


		rCardSkadaNames = #res


		if uCardSkadaNames == rCardSkadaNames then

			outputHasName	    = inputName
			outputIsAllowedName = true
		else

			outputHasName	    = ""
			outputIsAllowedName = false

			if eventName == "INS" then

				table.insert(SkadaLogData,

						date("%Y:%m:%d : %H:%M:%S") .. " : " .. "DUP" .. " : " .. string.rep(" ", 7) .. " : " .. inputName ..
						string.rep(" ", 22 - string.len(inputName)) .. " : " ..	"INS/DEL" .. string.rep(" ", 22- string.len("INS/DEL")) .. ":"

					    )


				Skada:CurrentSort(SkadaLogData, "LDR")
			end


			if eventName == "REN" then

				table.insert(SkadaLogData,

						date("%Y:%m:%d : %H:%M:%S") .. " : " .. "DUP" .. " : " .. string.rep(" ", 7) .. " : " .. inputName .. string.rep(" ", 22 - string.len(inputName)) .. " : " ..
						"REN/DEL" .. string.rep(" ", 22- string.len("INS/DEL")) .. ":"

					    )


				Skada:CurrentSort(SkadaLogData, "LDR")
			end
		end


		return outputHasName, outputIsAllowedName
	end




	-- ===================================================
	-- | sort: "ALP"/"CAT"/"CSA"/"CSC"/"CSK"/"LDR"/"NBR" |
	-- ===================================================


	-- ==========================================================================================================
	-- | hint: lexicographical order									    |
	-- |													    |
	-- | condition 1:											    |
	-- | the lexicographical arrangement follows the alphabet.						    |
	-- |													    |
	-- | condition 2:											    |
	-- | uppercase letters before lowercase letters. in most standard systems (e.g., ASCII/Unicode), the	    |
	-- | uppercase letter is placed before the corresponding lowercase letter when sorting alphabetically.	    |
	-- |													    |
	-- | condition 3 (german vowel mutation)								    |
	-- |		 A < Ä < B and a < ä < b								    |	
	-- |		 O < Ö < P and o < ö < p								    |
	-- |		 U < Ü < V and u < ü < v								    |
	-- |													    |
	-- | e.g.											 	    |
	-- |		[condition 1]				Healing < HPS					    |
	-- |		[condition 1 then condition 2]		DPS < Dps (but e.g. Dot < DPS)			    |
	-- |		[condition 1 then condition 3]		Unterbrechung < Überheilung (german vowel mutation) |
	-- ==========================================================================================================
	function Skada:CurrentSort(inputTable, inputSort)

		-- =========================================
		-- | "ALP": modules lexicographical sorted |
		-- =========================================
		if inputSort == "ALP" then

			table.sort(inputTable,

					function(a, b)

						a_Name = string.sub(string.upper(a:GetName()), 1 , 1) .. string.sub(string.lower(a:GetName()), 2)
						b_Name = string.sub(string.upper(b:GetName()), 1 , 1) .. string.sub(string.lower(b:GetName()), 2)

						return a_Name < b_Name
					end

			  	  )


		-- ===============================================================================
		-- | "CAT": categories and modules in each category are sorted lexicographically |
		-- ===============================================================================
		elseif inputSort == "CAT" then

			local keyTable = {}


			for i, module in ipairs(inputTable) do table.insert(keyTable, string.sub(string.upper(module:GetName()), 1 , 1) .. string.sub(string.lower(module:GetName()), 2)) end


			table.sort(keyTable)


			table.sort(inputTable,

					function(a, b)

						local a_score, b_score, a_sort_score, b_sort_score = 0, 0, 0, 0


						a_Name = string.sub(string.upper(a:GetName()), 1 , 1) .. string.sub(string.lower(a:GetName()), 2)
						b_Name = string.sub(string.upper(b:GetName()), 1 , 1) .. string.sub(string.lower(b:GetName()), 2)


						if locale == "deDE" then

							if a.category == L['Healing']	   then a_cat_score = 100 end
							if a.category == L['Power gained'] then a_cat_score = 200 end
							if a.category == L['Damage']	   then a_cat_score = 300 end
							if a.category == L['Other']	   then a_cat_score = 400 end

							if b.category == L['Healing']	   then b_cat_score = 100 end
							if b.category == L['Power gained'] then b_cat_score = 200 end
							if b.category == L['Damage']	   then b_cat_score = 300 end
							if b.category == L['Other']	   then b_cat_score = 400 end
						end


						if locale == "enUS" then

							if a.category == L['Damage']	   then a_cat_score = 100 end
							if a.category == L['Healing']	   then a_cat_score = 200 end
							if a.category == L['Power gained'] then a_cat_score = 300 end
							if a.category == L['Other']	   then a_cat_score = 400 end

							if b.category == L['Damage']	   then b_cat_score = 100 end
							if b.category == L['Healing']	   then b_cat_score = 200 end
							if b.category == L['Power gained'] then b_cat_score = 300 end
							if b.category == L['Other']	   then b_cat_score = 400 end
						end


						for i, keyName in pairs(keyTable) do

							if a_Name == keyName then

								a_sort_score = i


								break
							end
						end


						a_score = a_score + a_cat_score + a_sort_score


						for i, keyName in pairs(keyTable) do

							if b_Name == keyName then

								b_sort_score = i


								break
							end
						end


						b_score = b_score + b_cat_score + b_sort_score


						return a_score < b_score
					end

				  )


		-- ==========================================
		-- | "CSA": channels lexicographical sorted |
		-- ==========================================
		elseif inputSort == "CSA" then

			table.sort(inputTable,

					function(a, b)

						local a_score, b_score = 0, 0


						if locale == "deDE" then

							if a == "whisper"		then a_score = a_score +  1 end		-- Flüstern
							if a == "target"		then a_score = a_score +  2 end		-- Flüsterziel
							if a == "guild"			then a_score = a_score +  3 end		-- Gilde
							if a == "party"			then a_score = a_score +  4 end		-- Gruppe
							if a == "instance_chat"		then a_score = a_score +  5 end		-- Instanz
							if a == "self"	 		then a_score = a_score +  6 end		-- Lokal
							if a == "officer"		then a_score = a_score +  7 end		-- Offizier
							if a == "say"			then a_score = a_score +  8 end		-- Sagen
							if a == "raid"			then a_score = a_score +  9 end		-- Schlachtzug


							for i, channel in pairs(csaKindTable) do				-- Kanal: 1 ...	Kanal: n ...

								if string.match(channel, a) then

									local offset = string.sub(channel, 8 , 8)
									a_score	     = a_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end


							if b == "whisper"		then b_score = b_score +  1 end
							if b == "target"		then b_score = b_score +  2 end
							if b == "guild"			then b_score = b_score +  3 end
							if b == "party"			then b_score = b_score +  4 end
							if b == "instance_chat"		then b_score = b_score +  5 end
							if b == "self"	 		then b_score = b_score +  6 end
							if b == "officer"		then b_score = b_score +  7 end
							if b == "say"			then b_score = b_score +  8 end
							if b == "raid"			then b_score = b_score +  9 end


							for i, channel in pairs(csaKindTable) do

								if string.match(channel, b) then

									local offset = string.sub(channel, 8 , 8)
									b_score      = b_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end
						end


						if locale == "enUS" then

							if a == "guild"			then a_score = a_score +   1 end
							if a == "instance_chat"		then a_score = a_score +   2 end
							if a == "officer"		then a_score = a_score +   3 end
							if a == "party"			then a_score = a_score +   4 end
							if a == "raid"			then a_score = a_score +   5 end
							if a == "say"	 		then a_score = a_score +   6 end
							if a == "self"			then a_score = a_score +   7 end
							if a == "whisper"		then a_score = a_score +   8 end
							if a == "target"		then a_score = a_score +   9 end


							for i, channel in pairs(csaKindTable) do

								if string.match(channel, a) then

									local offset = string.sub(channel, 10 , 10)
									a_score      = a_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end


							if b == "guild"			then b_score = b_score +   1 end
							if b == "instance_chat"		then b_score = b_score +   2 end
							if b == "officer"		then b_score = b_score +   3 end
							if b == "party"			then b_score = b_score +   4 end
							if b == "raid"			then b_score = b_score +   5 end
							if b == "say"	 		then b_score = b_score +   6 end
							if b == "self"			then b_score = b_score +   7 end
							if b == "whisper"		then b_score = b_score +   8 end
							if b == "target"		then b_score = b_score +   9 end


							for i, channel in pairs(csaKindTable) do

								if string.match(channel, b) then

									local offset = string.sub(channel, 10 , 10)
									b_score      = b_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end
						end


						return a_score < b_score
					end

			  	  )


		-- ==================================================================
		-- | "CSC": channels in each category are sorted lexicographically. |
		-- ==================================================================
		elseif inputSort == "CSC" then

			table.sort(inputTable,

					function(a, b)

						local a_score, b_score = 0, 0


						if locale == "deDE" then

							if a == "guild"			then a_score = a_score +  1 end		-- Gilde
							if a == "officer"		then a_score = a_score +  2 end		-- Offizier
							if a == "party"			then a_score = a_score +  3 end		-- Gruppe
							if a == "instance_chat"		then a_score = a_score +  4 end		-- Instanz
							if a == "raid"			then a_score = a_score +  5 end		-- Schlachtzug
							if a == "self"	 		then a_score = a_score +  6 end		-- Lokal
							if a == "say"			then a_score = a_score +  7 end		-- Sagen
							if a == "whisper"		then a_score = a_score +  8 end		-- Flüstern
							if a == "target"		then a_score = a_score +  9 end		-- Flüsterziel


							for i, channel in pairs(csaKindTable) do				-- Kanal: 1 ...	Kanal: n ...

								if string.match(channel, a) then

									local offset = string.sub(channel, 8 , 8)
									a_score	     = a_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end


							if b == "guild"			then b_score = b_score +  1 end
							if b == "officer"		then b_score = b_score +  2 end
							if b == "party"			then b_score = b_score +  3 end
							if b == "instance_chat"		then b_score = b_score +  4 end
							if b == "raid"			then b_score = b_score +  5 end
							if b == "self"	 		then b_score = b_score +  6 end
							if b == "say"			then b_score = b_score +  7 end
							if b == "whisper"		then b_score = b_score +  8 end
							if b == "target"		then b_score = b_score +  9 end


							for i, channel in pairs(csaKindTable) do

								if string.match(channel, b) then

									local offset = string.sub(channel, 8 , 8)
									b_score	     = b_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end
						end


						if locale == "enUS" then

							if a == "guild"			then a_score = a_score +  1 end
							if a == "officer"		then a_score = a_score +  2 end
							if a == "party"			then a_score = a_score +  3 end
							if a == "instance_chat"		then a_score = a_score +  4 end
							if a == "raid"			then a_score = a_score +  5 end
							if a == "self"	 		then a_score = a_score +  6 end
							if a == "say"			then a_score = a_score +  7 end
							if a == "whisper"		then a_score = a_score +  8 end
							if a == "target"		then a_score = a_score +  9 end


							for i, channel in pairs(csaKindTable) do

								if string.match(channel, a) then

									local offset = string.sub(channel, 10 , 10)
									a_score	     = a_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end


							if b == "guild"			then b_score = b_score +  1 end
							if b == "officer"		then b_score = b_score +  2 end
							if b == "party"			then b_score = b_score +  3 end
							if b == "instance_chat"		then b_score = b_score +  4 end
							if b == "raid"			then b_score = b_score +  5 end
							if b == "self"	 		then b_score = b_score +  6 end
							if b == "say"			then b_score = b_score +  7 end
							if b == "whisper"		then b_score = b_score +  8 end
							if b == "target"		then b_score = b_score +  9 end


							for i, channel in pairs(csaKindTable) do

								if string.match(channel, b) then

									local offset = string.sub(channel, 10 , 10)
									b_score	     = b_score + 10 + 0.1 * tonumber(offset)


									break
								end
							end
						end


						return a_score < b_score
					end

			  	  )


		-- =========================================
		-- | "CSK": channel sort kind alphabetical |
		-- =========================================
		elseif inputSort == "CSK" then

			table.sort(inputTable,

					function(a, b)

						return a < b
					end

			  	  )


		-- ===========================
		-- | "LDR": log data reverse |
		-- ===========================
		elseif inputSort == "LDR" then

			table.sort(inputTable,

					function(a, b)

						if string.sub(a, 1, 21) > string.sub(b, 1, 21) then return a > b end
					end

			  	  )


		-- =========================
		-- | "NBR": number reverse |
		-- =========================
		elseif inputSort == "NBR" then

			table.sort(inputTable,

					function(a, b)

						return a > b
					end

			  	  )
		end
	end




	-- ========================================================
	-- | gametooltip: internal events "DEL"/"INS"/"MRM"/"REN" |
	-- ========================================================
	function Skada:CurrentTooltip(eventName_1, eventName_2, eventWindowName_1, eventWindowName_2)

		GameTooltip:ClearLines()
		GameTooltip:SetOwner(UIParent, "ANCHOR_NONE")
		GameTooltip:SetPoint("CENTER", UIParent, "TOP",0, -100)
		GameTooltip:AddLine(L["Hint:"])


		-- ========================
		-- | "DEL": delete Window |
		-- ========================
		if eventName_1 == "DEL" then

			GameTooltip:AddLine(L["Skada: Window"]  .. "\124cFF00FF00" .. " > " .. eventWindowName_1 ..  " < " .. "\124r" .. L["Deleted"], 1, 1, 1)
		end


		-- ========================
		-- | "INS": insert Window |
		-- ========================
		if eventName_1 == "INS" then

			if eventName_2 == "DUP" then

				GameTooltip:AddLine(L["Skada: Window"]    .. "\124cFF00FF00" .. " > " .. eventWindowName_1 .. " < " .. "\124r" .. L["Duplicates insert"], 1, 1, 1)
			end


			if eventName_2 == "NEW" then

				GameTooltip:AddLine(L["New Skada-Window"] .. "\124cFF00FF00" .. " > " .. eventWindowName_1 .. " < " .. "\124r" .. L["Has been created."], 1, 1, 1)
			end
		end


		-- =================================================
		-- | "RCP": reporting during combat is prohibited. |
		-- =================================================
		if eventName_1 == "RCP" then

			GameTooltip:AddLine(L["Reporting during combat is prohibited!"] , 1, 1, 1)
		end


		-- ==============================
		-- | "MRM": missing report mode |
		-- ==============================
		if eventName_1 == "MRM" then

			GameTooltip:AddLine(L["Skada: Report Window requires a Report Mode !"] , 1, 1, 1)
		end


		GameTooltip:Show()


		return
	end




--  ========================================================
--  | menu: button (OpenReportWindow, ShowPopup, OpenMenu) |
--  ========================================================
function Skada:MenuButton(win, mod, nameButton, nameMenu, xRightPoint , yRightPoint, normalTexture, highlightTexture)

	local nameButton = CreateFrame("Button", "InlineFrameMenuButton", win.frame, "BackdropTemplate")

	nameButton:ClearAllPoints()
	nameButton:SetSize(13, 13)
	nameButton:SetNormalTexture(normalTexture)
	nameButton:SetHighlightTexture(highlightTexture, "ADD")
	nameButton:SetAlpha(0.5)


	nameButton:RegisterForClicks("LeftButtonUp", "RightButtonUp")


	nameButton:SetFrameLevel(9)
	nameButton:SetPoint("RIGHT", xRightPoint, yRightPoint)
	nameButton:SetBackdrop(skadamenubuttonbackdrop)


	nameButton:SetScript("OnClick", nameMenu)


	win.frame.skadamenubutton = title
	win.frame.nameButton	  = nameButton
	win.frame.win		  = win


	win.frame:EnableMouse(true)


	mod:ApplySettings(win)
end
-- ============================
-- | necessary routines (end) |
-- ============================