
local detailsFramework = _G ["DetailsFramework"]
if (not detailsFramework or not DetailsFrameworkCanLoad) then
	return
end

local _
--lua locals
local rawset = rawset --lua local
local rawget = rawget --lua local
local setmetatable = setmetatable --lua local
local unpack = table.unpack or unpack --lua local
local type = type --lua local
local floor = math.floor --lua local
local loadstring = loadstring --lua local

local IS_WOW_PROJECT_MAINLINE = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE
local IS_WOW_PROJECT_NOT_MAINLINE = WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE
local IS_WOW_PROJECT_CLASSIC_ERA = WOW_PROJECT_ID == WOW_PROJECT_CLASSIC

local UnitCastingInfo = UnitCastingInfo
local UnitChannelInfo = UnitChannelInfo

if IS_WOW_PROJECT_CLASSIC_ERA then
    UnitCastingInfo = CastingInfo
    UnitChannelInfo = ChannelInfo
end

local PixelUtil = PixelUtil or DFPixelUtil

local UnitGroupRolesAssigned = detailsFramework.UnitGroupRolesAssigned

local cleanfunction = function() end
local APIFrameFunctions

do
	local metaPrototype = {
		WidgetType = "panel",
		dversion = detailsFramework.dversion,
	}

	--check if there's a metaPrototype already existing
	if (_G[detailsFramework.GlobalWidgetControlNames["panel"]]) then
		--get the already existing metaPrototype
		local oldMetaPrototype = _G[detailsFramework.GlobalWidgetControlNames ["panel"]]
		--check if is older
		if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < detailsFramework.dversion) ) then
			--the version is older them the currently loading one
			--copy the new values into the old metatable
			for funcName, _ in pairs(metaPrototype) do
				oldMetaPrototype[funcName] = metaPrototype[funcName]
			end
		end
	else
		--first time loading the framework
		_G[detailsFramework.GlobalWidgetControlNames["panel"]] = metaPrototype
	end
end

local PanelMetaFunctions = _G[detailsFramework.GlobalWidgetControlNames["panel"]]
detailsFramework:Mixin(PanelMetaFunctions, detailsFramework.ScriptHookMixin)

--default options for the frame layout
local default_framelayout_options = {
	amount_per_line = 4,
	start_x = 2,
	start_y = -2,
	is_vertical = false,
	grow_right = true, --on vertical (if not grow next line left)
	grow_down = true, --on horizontal (if not grow next line up)
	anchor_to_child = false, --if true set the point to the previous frame instead of coordinate
	anchor_point = "topleft",
	anchor_relative = "topleft",
	offset_x = 100,
	offset_y = 20,
	width = 0, --if bigger than 0, it will set the value
	height = 0,
	break_if_hidden = true, --stop if encounters a hidden frame
}

--mixin for frame layout
detailsFramework.LayoutFrame = {
	AnchorTo = function(self, anchor, point, x, y)
		if (point == "top") then
			self:ClearAllPoints()
			self:SetPoint("bottom", anchor, "top", x or 0, y or 0)

		elseif (point == "bottom") then
			self:ClearAllPoints()
			self:SetPoint("top", anchor, "bottom", x or 0, y or 0)

		elseif (point == "left") then
			self:ClearAllPoints()
			self:SetPoint("right", anchor, "left", x or 0, y or 0)

		elseif (point == "right") then
			self:ClearAllPoints()
			self:SetPoint("left", anchor, "right", x or 0, y or 0)
		end
	end,

	ArrangeFrames = function(self, frameList, options)
		if (not frameList) then
			frameList = {self:GetChildren()}
		end

		options = options or {}
		detailsFramework.table.deploy(options, default_framelayout_options)

		local breakLine = options.amount_per_line + 1
		local currentX, currentY = options.start_x, options.start_y
		local offsetX, offsetY = options.offset_x, options.offset_y
		local anchorPoint = options.anchor_point
		local anchorAt = options.anchor_relative
		local latestFrame = self
		local firstRowFrame = frameList[1]

		if (options.is_vertical) then
			for i = 1, #frameList do
				local thisFrame =  frameList[i]
				if (options.break_if_hidden and not thisFrame:IsShown()) then
					break
				end
				thisFrame:ClearAllPoints()

				if (options.anchor_to_child) then
					if (i == breakLine) then
						if (options.grow_right) then
							thisFrame:SetPoint("topleft", firstRowFrame, "topright", offsetX, 0)
						else
							thisFrame:SetPoint("topright", firstRowFrame, "topleft", -offsetX, 0)
						end
						firstRowFrame = thisFrame
						latestFrame = thisFrame
						breakLine = breakLine + options.amount_per_line
					else
						thisFrame:SetPoint(anchorPoint, latestFrame, i == 1 and "topleft" or anchorAt, offsetX, i == 1 and 0 or offsetY)
						latestFrame = thisFrame
					end
				else
					if (i == breakLine) then
						if (options.grow_right) then
							currentX = currentX + offsetX
						else
							currentX = currentX - offsetX
						end
						currentY = options.start_y

						firstRowFrame = thisFrame
						breakLine = breakLine + options.amount_per_line
					end

					thisFrame:SetPoint(anchorPoint, self, anchorAt, currentX, currentY)
					currentY = currentY - offsetY
				end
			end

		else
			for i = 1, #frameList do
				local thisFrame =  frameList [i]
				if (options.break_if_hidden and not thisFrame:IsShown()) then
					break
				end
				thisFrame:ClearAllPoints()

				if (options.anchor_to_child) then
					if (i == breakLine) then
						if (options.grow_down) then
							thisFrame:SetPoint("topleft", firstRowFrame, "bottomleft", 0, -offsetY)
						else
							thisFrame:SetPoint("bottomleft", firstRowFrame, "topleft", 0, offsetY)
						end
						firstRowFrame = thisFrame
						latestFrame = thisFrame
						breakLine = breakLine + options.amount_per_line
					else
						thisFrame:SetPoint(anchorPoint, latestFrame, i == 1 and "topleft" or anchorAt, i == 1 and 0 or offsetX, offsetY)
						latestFrame = thisFrame
					end
				else
					if (i == breakLine) then
						if (options.grow_down) then
							currentY = currentY - offsetY
						else
							currentY = currentY + offsetY
						end
						currentX = options.start_x

						firstRowFrame = thisFrame
						breakLine = breakLine + options.amount_per_line
					end

					thisFrame:SetPoint(anchorPoint, self, anchorAt, currentX, currentY)
					currentX = currentX + offsetX
				end
			end
		end
	end
}


------------------------------------------------------------------------------------------------------------
--metatables
	PanelMetaFunctions.__call = function(_table, value)
		--nothing to do
		return true
	end

------------------------------------------------------------------------------------------------------------
--members
	--tooltip
	local gmember_tooltip = function(_object)
		return _object:GetTooltip()
	end
	--shown
	local gmember_shown = function(_object)
		return _object:IsShown()
	end
	--backdrop color
	local gmember_color = function(_object)
		return _object.frame:GetBackdropColor()
	end
	--backdrop table
	local gmember_backdrop = function(_object)
		return _object.frame:GetBackdrop()
	end
	--frame width
	local gmember_width = function(_object)
		return _object.frame:GetWidth()
	end
	--frame height
	local gmember_height = function(_object)
		return _object.frame:GetHeight()
	end
	--locked
	local gmember_locked = function(_object)
		return rawget(_object, "is_locked")
	end

	PanelMetaFunctions.GetMembers = PanelMetaFunctions.GetMembers or {}
	PanelMetaFunctions.GetMembers ["tooltip"] = gmember_tooltip
	PanelMetaFunctions.GetMembers ["shown"] = gmember_shown
	PanelMetaFunctions.GetMembers ["color"] = gmember_color
	PanelMetaFunctions.GetMembers ["backdrop"] = gmember_backdrop
	PanelMetaFunctions.GetMembers ["width"] = gmember_width
	PanelMetaFunctions.GetMembers ["height"] = gmember_height
	PanelMetaFunctions.GetMembers ["locked"] = gmember_locked

	PanelMetaFunctions.__index = function(object, key)
		local func = PanelMetaFunctions.GetMembers[key]
		if (func) then
			return func(object, key)
		end

		local fromMe = rawget(object, key)
		if (fromMe) then
			return fromMe
		end

		return PanelMetaFunctions[key]
	end

	--tooltip
	local smember_tooltip = function(_object, _value)
		return _object:SetTooltip (_value)
	end
	--show
	local smember_show = function(_object, _value)
		if (_value) then
			return _object:Show()
		else
			return _object:Hide()
		end
	end
	--hide
	local smember_hide = function(_object, _value)
		if (not _value) then
			return _object:Show()
		else
			return _object:Hide()
		end
	end
	--backdrop color
	local smember_color = function(_object, _value)
		local _value1, _value2, _value3, _value4 = detailsFramework:ParseColors(_value)
		return _object:SetBackdropColor(_value1, _value2, _value3, _value4)
	end
	--frame width
	local smember_width = function(_object, _value)
		return _object.frame:SetWidth(_value)
	end
	--frame height
	local smember_height = function(_object, _value)
		return _object.frame:SetHeight(_value)
	end

	--locked
	local smember_locked = function(_object, _value)
		if (_value) then
			_object.frame:SetMovable(false)
			return rawset(_object, "is_locked", true)
		else
			_object.frame:SetMovable(true)
			rawset(_object, "is_locked", false)
			return
		end
	end

	--backdrop
	local smember_backdrop = function(_object, _value)
		return _object.frame:SetBackdrop(_value)
	end

	--close with right button
	local smember_right_close = function(_object, _value)
		return rawset(_object, "rightButtonClose", _value)
	end

	PanelMetaFunctions.SetMembers = PanelMetaFunctions.SetMembers or {}
	PanelMetaFunctions.SetMembers["tooltip"] = smember_tooltip
	PanelMetaFunctions.SetMembers["show"] = smember_show
	PanelMetaFunctions.SetMembers["hide"] = smember_hide
	PanelMetaFunctions.SetMembers["color"] = smember_color
	PanelMetaFunctions.SetMembers["backdrop"] = smember_backdrop
	PanelMetaFunctions.SetMembers["width"] = smember_width
	PanelMetaFunctions.SetMembers["height"] = smember_height
	PanelMetaFunctions.SetMembers["locked"] = smember_locked
	PanelMetaFunctions.SetMembers["close_with_right"] = smember_right_close

	PanelMetaFunctions.__newindex = function(_table, _key, _value)
		local func = PanelMetaFunctions.SetMembers [_key]
		if (func) then
			return func (_table, _value)
		else
			return rawset(_table, _key, _value)
		end
	end

------------------------------------------------------------------------------------------------------------
--methods

--right click to close
	function PanelMetaFunctions:CreateRightClickLabel(textType, width, height, showCloseText)
		local text
		width = width or 20
		height = height or 20

		if (showCloseText) then
			text = showCloseText
		else
			if (textType) then
				textType = string.lower(textType)
				if (textType == "short") then
					text = "close window"

				elseif (textType == "medium") then
					text = "close window"

				elseif (textType == "large") then
					text = "close window"
				end
			else
				text = "close window"
			end
		end

		return detailsFramework:NewLabel(self, _, "$parentRightMouseToClose", nil, "|TInterface\\TUTORIALFRAME\\UI-TUTORIAL-FRAME:" .. width .. ":" .. height .. ":0:1:512:512:8:70:328:409|t " .. text)
	end

--show & hide
	function PanelMetaFunctions:Show()
		self.frame:Show()
	end

	function PanelMetaFunctions:Hide()
		self.frame:Hide()
	end

-- setpoint
	function PanelMetaFunctions:SetPoint(v1, v2, v3, v4, v5)
		v1, v2, v3, v4, v5 = detailsFramework:CheckPoints (v1, v2, v3, v4, v5, self)
		if (not v1) then
			print("Invalid parameter for SetPoint")
			return
		end
		return self.widget:SetPoint(v1, v2, v3, v4, v5)
	end

-- sizes
	function PanelMetaFunctions:SetSize(w, h)
		if (w) then
			self.frame:SetWidth(w)
		end
		if (h) then
			self.frame:SetHeight(h)
		end
	end

-- clear
	function PanelMetaFunctions:HideWidgets()
		for widgetName, widgetSelf in pairs(self) do
			if (type(widgetSelf) == "table" and widgetSelf.dframework) then
				widgetSelf:Hide()
			end
		end
	end

-- backdrop
	function PanelMetaFunctions:SetBackdrop(background, edge, tilesize, edgesize, tile, left, right, top, bottom)

		if (type(background) == "boolean" and not background) then
			return self.frame:SetBackdrop(nil)

		elseif (type(background) == "table") then
			self.frame:SetBackdrop(background)

		else
			local currentBackdrop = self.frame:GetBackdrop() or {edgeFile="Interface\\DialogFrame\\UI-DialogBox-Border", bgFile="Interface\\DialogFrame\\UI-DialogBox-Background", tile=true, tileSize=16, edgeSize=16, insets={left=1, right=0, top=0, bottom=0}}
			currentBackdrop.bgFile = background or currentBackdrop.bgFile
			currentBackdrop.edgeFile = edgeFile or currentBackdrop.edgeFile
			currentBackdrop.tileSize = tilesize or currentBackdrop.tileSize
			currentBackdrop.edgeSize = edgesize or currentBackdrop.edgeSize
			currentBackdrop.tile = tile or currentBackdrop.tile
			currentBackdrop.insets.left = left or currentBackdrop.insets.left
			currentBackdrop.insets.right = left or currentBackdrop.insets.right
			currentBackdrop.insets.top = left or currentBackdrop.insets.top
			currentBackdrop.insets.bottom = left or currentBackdrop.insets.bottom
			self.frame:SetBackdrop(currentBackdrop)
		end
	end

-- backdropcolor
	function PanelMetaFunctions:SetBackdropColor(color, arg2, arg3, arg4)
		if (arg2) then
			self.frame:SetBackdropColor(color, arg2, arg3, arg4 or 1)
		else
			local _value1, _value2, _value3, _value4 = detailsFramework:ParseColors(color)
			self.frame:SetBackdropColor(_value1, _value2, _value3, _value4)
		end
	end

-- border color
	function PanelMetaFunctions:SetBackdropBorderColor(color, arg2, arg3, arg4)
		if (arg2) then
			return self.frame:SetBackdropBorderColor(color, arg2, arg3, arg4)
		end
		local _value1, _value2, _value3, _value4 = detailsFramework:ParseColors(color)
		self.frame:SetBackdropBorderColor(_value1, _value2, _value3, _value4)
	end

-- tooltip
	function PanelMetaFunctions:SetTooltip (tooltip)
		if (tooltip) then
			return rawset(self, "have_tooltip", tooltip)
		else
			return rawset(self, "have_tooltip", nil)
		end
	end
	function PanelMetaFunctions:GetTooltip()
		return rawget(self, "have_tooltip")
	end

-- frame levels
	function PanelMetaFunctions:GetFrameLevel()
		return self.widget:GetFrameLevel()
	end
	function PanelMetaFunctions:SetFrameLevel(level, frame)
		if (not frame) then
			return self.widget:SetFrameLevel(level)
		else
			local framelevel = frame:GetFrameLevel (frame) + level
			return self.widget:SetFrameLevel(framelevel)
		end
	end

-- frame stratas
	function PanelMetaFunctions:SetFrameStrata()
		return self.widget:GetFrameStrata()
	end
	function PanelMetaFunctions:SetFrameStrata(strata)
		if (type(strata) == "table") then
			self.widget:SetFrameStrata(strata:GetFrameStrata())
		else
			self.widget:SetFrameStrata(strata)
		end
	end

------------------------------------------------------------------------------------------------------------
--scripts

	local OnEnter = function(frame)
		local capsule = frame.MyObject
		local kill = capsule:RunHooksForWidget("OnEnter", frame, capsule)
		if (kill) then
			return
		end

		if (frame.MyObject.have_tooltip) then
			GameCooltip2:Reset()
			GameCooltip2:SetType ("tooltip")
			GameCooltip2:SetColor ("main", "transparent")
			GameCooltip2:AddLine(frame.MyObject.have_tooltip)
			GameCooltip2:SetOwner(frame)
			GameCooltip2:ShowCooltip()
		end
	end

	local OnLeave = function(frame)
		local capsule = frame.MyObject
		local kill = capsule:RunHooksForWidget("OnLeave", frame, capsule)
		if (kill) then
			return
		end

		if (frame.MyObject.have_tooltip) then
			GameCooltip2:ShowMe(false)
		end

	end

	local OnHide = function(frame)
		local capsule = frame.MyObject
		local kill = capsule:RunHooksForWidget("OnHide", frame, capsule)
		if (kill) then
			return
		end
	end

	local OnShow = function(frame)
		local capsule = frame.MyObject
		local kill = capsule:RunHooksForWidget("OnShow", frame, capsule)
		if (kill) then
			return
		end
	end

	local OnMouseDown = function(frame, button)
		local capsule = frame.MyObject
		local kill = capsule:RunHooksForWidget("OnMouseDown", frame, button, capsule)
		if (kill) then
			return
		end

		if (frame.MyObject.container == UIParent) then
			if (not frame.isLocked and frame:IsMovable()) then
				frame.isMoving = true
				frame:StartMoving()
			end

		elseif (not frame.MyObject.container.isLocked and frame.MyObject.container:IsMovable()) then
			if (not frame.isLocked and frame:IsMovable()) then
				frame.MyObject.container.isMoving = true
				frame.MyObject.container:StartMoving()
			end
		end


	end

	local OnMouseUp = function(frame, button)
		local capsule = frame.MyObject
		local kill = capsule:RunHooksForWidget("OnMouseUp", frame, button, capsule)
		if (kill) then
			return
		end

		if (button == "RightButton" and frame.MyObject.rightButtonClose) then
			frame.MyObject:Hide()
		end

		if (frame.MyObject.container == UIParent) then
			if (frame.isMoving) then
				frame:StopMovingOrSizing()
				frame.isMoving = false
			end
		else
			if (frame.MyObject.container.isMoving) then
				frame.MyObject.container:StopMovingOrSizing()
				frame.MyObject.container.isMoving = false
			end
		end
	end

------------------------------------------------------------------------------------------------------------
--object constructor
function detailsFramework:CreatePanel (parent, w, h, backdrop, backdropcolor, bordercolor, member, name)
	return detailsFramework:NewPanel(parent, parent, name, member, w, h, backdrop, backdropcolor, bordercolor)
end

function detailsFramework:NewPanel(parent, container, name, member, w, h, backdrop, backdropcolor, bordercolor)

	if (not name) then
		name = "DetailsFrameworkPanelNumber" .. detailsFramework.PanelCounter
		detailsFramework.PanelCounter = detailsFramework.PanelCounter + 1

	elseif (not parent) then
		parent = UIParent
	end
	if (not container) then
		container = parent
	end

	if (name:find("$parent")) then
		name = name:gsub("$parent", parent:GetName())
	end

	local PanelObject = {type = "panel", dframework = true}

	if (member) then
		parent [member] = PanelObject
	end

	if (parent.dframework) then
		parent = parent.widget
	end
	if (container.dframework) then
		container = container.widget
	end

	--default members:
		--misc
		PanelObject.is_locked = true
		PanelObject.container = container
		PanelObject.rightButtonClose = false

	PanelObject.frame = CreateFrame("frame", name, parent,"BackdropTemplate")
	PanelObject.frame:SetSize(100, 100)
	PanelObject.frame.Gradient = {
					["OnEnter"] = {0.3, 0.3, 0.3, 0.5},
					["OnLeave"] = {0.9, 0.7, 0.7, 1}
	}
	PanelObject.frame:SetBackdrop({bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]], edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", edgeSize = 10, tileSize = 64, tile = true})

	PanelObject.widget = PanelObject.frame

	if (not APIFrameFunctions) then
		APIFrameFunctions = {}
		local idx = getmetatable(PanelObject.frame).__index
		for funcName, funcAddress in pairs(idx) do
			if (not PanelMetaFunctions [funcName]) then
				PanelMetaFunctions [funcName] = function(object, ...)
					local x = loadstring ( "return _G['"..object.frame:GetName().."']:"..funcName.."(...)")
					return x (...)
				end
			end
		end
	end

	PanelObject.frame:SetWidth(w or 100)
	PanelObject.frame:SetHeight(h or 100)

	PanelObject.frame.MyObject = PanelObject

	PanelObject.HookList = {
		OnEnter = {},
		OnLeave = {},
		OnHide = {},
		OnShow = {},
		OnMouseDown = {},
		OnMouseUp = {},
	}

	--hooks
		PanelObject.frame:SetScript("OnEnter", OnEnter)
		PanelObject.frame:SetScript("OnLeave", OnLeave)
		PanelObject.frame:SetScript("OnHide", OnHide)
		PanelObject.frame:SetScript("OnShow", OnShow)
		PanelObject.frame:SetScript("OnMouseDown", OnMouseDown)
		PanelObject.frame:SetScript("OnMouseUp", OnMouseUp)

	setmetatable(PanelObject, PanelMetaFunctions)

	if (backdrop) then
		PanelObject:SetBackdrop(backdrop)
	elseif (type(backdrop) == "boolean") then
		PanelObject.frame:SetBackdrop(nil)
	end

	if (backdropcolor) then
		PanelObject:SetBackdropColor(backdropcolor)
	end

	if (bordercolor) then
		PanelObject:SetBackdropBorderColor(bordercolor)
	end

	return PanelObject
end

------------fill panel

local button_on_enter = function(self)
	self.MyObject._icon:SetBlendMode("ADD")
	if (self.MyObject.onenter_func) then
		pcall(self.MyObject.onenter_func, self.MyObject)
	end
end
local button_on_leave = function(self)
	self.MyObject._icon:SetBlendMode("BLEND")
	if (self.MyObject.onleave_func) then
		pcall(self.MyObject.onleave_func, self.MyObject)
	end
end

local add_row = function(self, t, need_update)
	local index = #self.rows+1

	local thisrow = detailsFramework:NewPanel(self, self, "$parentHeader_" .. self._name .. index, nil, 1, 20)
	thisrow.backdrop = {bgFile = [[Interface\Tooltips\UI-Tooltip-Background]]}
	thisrow.color = {.3, .3, .3, .9}
	thisrow.type = t.type
	thisrow.func = t.func
	thisrow.name = t.name
	thisrow.notext = t.notext
	thisrow.icon = t.icon
	thisrow.iconalign = t.iconalign

	thisrow.hidden = t.hidden or false

	thisrow.onenter = t.onenter
	thisrow.onleave = t.onleave

	local text = detailsFramework:NewLabel(thisrow, nil, self._name .. "$parentLabel" .. index, "text")
	text:SetPoint("left", thisrow, "left", 2, 0)
	text:SetText(t.name)

	tinsert(self._raw_rows, t)
	tinsert(self.rows, thisrow)

	if (need_update) then
		self:AlignRows()
	end
end

local align_rows = function(self)

	local rows_shown = 0
	for index, row in ipairs(self.rows) do
		if (not row.hidden) then
			rows_shown = rows_shown + 1
		end
	end

	local cur_width = 1
	local row_width = self._width / max(rows_shown, 0.0001)


	local sindex = 1

	wipe (self._anchors)

	for index, row in ipairs(self.rows) do
		if (not row.hidden) then
			if (self._autowidth) then
				if (self._raw_rows [index].width) then
					row.width = self._raw_rows [index].width
				else
					row.width = row_width
				end
				row:SetPoint("topleft", self, "topleft", cur_width, -1)
				tinsert(self._anchors, cur_width)
				cur_width = cur_width + row_width + 1
			else
				row:SetPoint("topleft", self, "topleft", cur_width, -1)
				row.width = self._raw_rows [index].width
				tinsert(self._anchors, cur_width)
				cur_width = cur_width + self._raw_rows [index].width + 1
			end

			row:Show()

			local rowType = row.type

			if (rowType == "text") then
				for i = 1, #self.scrollframe.lines do
					local line = self.scrollframe.lines [i]
					local text = tremove(line.text_available)
					if (not text) then
						self:CreateRowText (line)
						text = tremove(line.text_available)
					end
					tinsert(line.text_inuse, text)
					text:SetPoint("left", line, "left", self._anchors [#self._anchors], 0)
					text:SetWidth(row.width)

					detailsFramework:SetFontSize(text, row.textsize or 10)
					text:SetJustifyH(row.textalign or "left")
				end
			elseif (rowType == "entry") then
				for i = 1, #self.scrollframe.lines do
					local line = self.scrollframe.lines [i]
					local entry = tremove(line.entry_available)
					if (not entry) then
						self:CreateRowEntry (line)
						entry = tremove(line.entry_available)
					end
					tinsert(line.entry_inuse, entry)
					entry:SetPoint("left", line, "left", self._anchors [#self._anchors], 0)
					if (sindex == rows_shown) then
						entry:SetWidth(row.width - 25)
					else
						entry:SetWidth(row.width)
					end
					entry.func = row.func

					entry.onenter_func = nil
					entry.onleave_func = nil

					if (row.onenter) then
						entry.onenter_func = row.onenter
					end
					if (row.onleave) then
						entry.onleave_func = row.onleave
					end
				end

			elseif (rowType == "checkbox") then
				for i = 1, #self.scrollframe.lines do
					local line = self.scrollframe.lines [i]
					local checkbox = tremove(line.checkbox_available)
					if (not checkbox) then
						self:CreateCheckbox (line)
						checkbox = tremove(line.checkbox_available)
					end

					tinsert(line.checkbox_inuse, checkbox)

					checkbox:SetPoint("left", line, "left", self._anchors [#self._anchors] + ((row.width - 20) / 2), 0)
					if (sindex == rows_shown) then
						checkbox:SetWidth(20)
						--checkbox:SetWidth(row.width - 25)
					else
						checkbox:SetWidth(20)
					end

					checkbox.onenter_func = nil
					checkbox.onleave_func = nil
				end

			elseif (rowType == "button") then
				for i = 1, #self.scrollframe.lines do
					local line = self.scrollframe.lines [i]
					local button = tremove(line.button_available)
					if (not button) then
						self:CreateRowButton (line)
						button = tremove(line.button_available)
					end
					tinsert(line.button_inuse, button)
					button:SetPoint("left", line, "left", self._anchors [#self._anchors], 0)
					if (sindex == rows_shown) then
						button:SetWidth(row.width - 25)
					else
						button:SetWidth(row.width)
					end

					if (row.icon) then
						button._icon.texture = row.icon
						button._icon:ClearAllPoints()
						if (row.iconalign) then
							if (row.iconalign == "center") then
								button._icon:SetPoint("center", button, "center")
							elseif (row.iconalign == "right") then
								button._icon:SetPoint("right", button, "right")
							end
						else
							button._icon:SetPoint("left", button, "left")
						end
					end

					if (row.name and not row.notext) then
						button._text:SetPoint("left", button._icon, "right", 2, 0)
						button._text.text = row.name
					end

					button.onenter_func = nil
					button.onleave_func = nil

					if (row.onenter) then
						button.onenter_func = row.onenter
					end
					if (row.onleave) then
						button.onleave_func = row.onleave
					end

				end
			elseif (rowType == "icon") then
				for i = 1, #self.scrollframe.lines do
					local line = self.scrollframe.lines [i]
					local icon = tremove(line.icon_available)
					if (not icon) then
						self:CreateRowIcon (line)
						icon = tremove(line.icon_available)
					end
					tinsert(line.icon_inuse, icon)
					icon:SetPoint("left", line, "left", self._anchors [#self._anchors] + ( ((row.width or 22) - 22) / 2), 0)
					icon.func = row.func
				end

			elseif (rowType == "texture") then
				for i = 1, #self.scrollframe.lines do
					local line = self.scrollframe.lines [i]
					local texture = tremove(line.texture_available)
					if (not texture) then
						self:CreateRowTexture (line)
						texture = tremove(line.texture_available)
					end
					tinsert(line.texture_inuse, texture)
					texture:SetPoint("left", line, "left", self._anchors [#self._anchors] + ( ((row.width or 22) - 22) / 2), 0)
				end

			end

			sindex = sindex + 1
		else
			row:Hide()
		end
	end

	if (#self.rows > 0) then
		if (self._autowidth) then
			self.rows [#self.rows]:SetWidth(row_width - rows_shown + 1)
		else
			self.rows [#self.rows]:SetWidth(self._raw_rows [rows_shown].width - rows_shown + 1)
		end
	end

	self.showing_amt = rows_shown
end

local update_rows = function(self, updated_rows)

	for i = 1, #updated_rows do
		local t = updated_rows [i]
		local raw = self._raw_rows [i]

		if (not raw) then
			self:AddRow (t)
		else
			raw.name = t.name
			raw.hidden = t.hidden or false
			raw.textsize = t.textsize
			raw.textalign = t.textalign

			local widget = self.rows [i]
			widget.name = t.name
			widget.textsize = t.textsize
			widget.textalign = t.textalign
			widget.hidden = t.hidden or false

			--
			widget.onenter = t.onenter
			widget.onleave = t.onleave
			--

			widget.text:SetText(t.name)
			detailsFramework:SetFontSize(widget.text, raw.textsize or 10)
			widget.text:SetJustifyH(raw.textalign or "left")
		end
	end

	for i = #updated_rows+1, #self._raw_rows do
		local raw = self._raw_rows [i]
		local widget = self.rows [i]
		raw.hidden = true
		widget.hidden = true
	end

	for index, row in ipairs(self.scrollframe.lines) do
		for i = #row.text_inuse, 1, -1 do
			tinsert(row.text_available, tremove(row.text_inuse, i))
		end
		for i = 1, #row.text_available do
			row.text_available[i]:Hide()
		end

		for i = #row.entry_inuse, 1, -1 do
			tinsert(row.entry_available, tremove(row.entry_inuse, i))
		end
		for i = 1, #row.entry_available do
			row.entry_available[i]:Hide()
		end

		for i = #row.button_inuse, 1, -1 do
			tinsert(row.button_available, tremove(row.button_inuse, i))
		end
		for i = 1, #row.button_available do
			row.button_available[i]:Hide()
		end

		for i = #row.checkbox_inuse, 1, -1 do
			tinsert(row.checkbox_available, tremove(row.checkbox_inuse, i))
		end
		for i = 1, #row.checkbox_available do
			row.checkbox_available[i]:Hide()
		end

		for i = #row.icon_inuse, 1, -1 do
			tinsert(row.icon_available, tremove(row.icon_inuse, i))
		end
		for i = 1, #row.icon_available do
			row.icon_available[i]:Hide()
		end

		for i = #row.texture_inuse, 1, -1 do
			tinsert(row.texture_available, tremove(row.texture_inuse, i))
		end
		for i = 1, #row.texture_available do
			row.texture_available[i]:Hide()
		end
	end

	self.current_header = updated_rows

	self:AlignRows()

end

local create_panel_text = function(self, row)
	row.text_total = row.text_total + 1
	local text = detailsFramework:NewLabel(row, nil, self._name .. "$parentLabel" .. row.text_total, "text" .. row.text_total)
	tinsert(row.text_available, text)
end

local create_panel_entry = function(self, row)
	row.entry_total = row.entry_total + 1
	local editbox = detailsFramework:NewTextEntry(row, nil, "$parentEntry" .. row.entry_total, "entry", 120, 20)
	editbox.align = "left"

	editbox:SetHook("OnEnterPressed", function()
		editbox.widget.focuslost = true
		editbox:ClearFocus()
		editbox.func (editbox.index, editbox.text)
		return true
	end)

	editbox:SetHook("OnEnter", function()
		if (editbox.onenter_func) then
			pcall(editbox.onenter_func, editbox)
		end
	end)
	editbox:SetHook("OnLeave", function()
		if (editbox.onleave_func) then
			pcall(editbox.onleave_func, editbox)
		end
	end)

	editbox.editbox.current_bordercolor = {1, 1, 1, 0.1}

	editbox:SetTemplate(detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE"))
	editbox:SetBackdropColor(.2, .2, .2, 0.7)

	tinsert(row.entry_available, editbox)
end

local create_panel_checkbox = function(self, row)
	--row.checkbox_available
	row.checkbox_total = row.checkbox_total + 1

	local switch = detailsFramework:NewSwitch (row, nil, "$parentCheckBox" .. row.checkbox_total, nil, 20, 20, nil, nil, false)
	switch:SetAsCheckBox()
	switch:SetTemplate(detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE"))

	tinsert(row.checkbox_available, switch)
end

local create_panel_button = function(self, row)
	row.button_total = row.button_total + 1
	local button = detailsFramework:NewButton(row, nil, "$parentButton" .. row.button_total, "button" .. row.button_total, 120, 20)

	--create icon and the text
	local icon = detailsFramework:NewImage(button, nil, 20, 20)
	local text = detailsFramework:NewLabel(button)

	button._icon = icon
	button._text = text

	button:SetHook("OnEnter", button_on_enter)
	button:SetHook("OnLeave", button_on_leave)

	tinsert(row.button_available, button)
end

local icon_onclick = function(texture, iconbutton)
	iconbutton._icon.texture = texture
	iconbutton.func (iconbutton.index, texture)
end

local create_panel_icon = function(self, row)
	row.icon_total = row.icon_total + 1
	local iconbutton = detailsFramework:NewButton(row, nil, "$parentIconButton" .. row.icon_total, "iconbutton", 22, 20)

	iconbutton:SetHook("OnEnter", button_on_enter)
	iconbutton:SetHook("OnLeave", button_on_leave)

	iconbutton:SetHook("OnMouseUp", function()
		detailsFramework:IconPick (icon_onclick, true, iconbutton)
		return true
	end)

	local icon = detailsFramework:NewImage(iconbutton, nil, 20, 20, "artwork", nil, "_icon", "$parentIcon" .. row.icon_total)
	iconbutton._icon = icon

	icon:SetPoint("center", iconbutton, "center", 0, 0)

	tinsert(row.icon_available, iconbutton)
end

local create_panel_texture = function(self, row)
	row.texture_total = row.texture_total + 1
	local texture = detailsFramework:NewImage(row, nil, 20, 20, "artwork", nil, "_icon" .. row.texture_total, "$parentIcon" .. row.texture_total)
	tinsert(row.texture_available, texture)
end

local set_fill_function = function(self, func)
	self._fillfunc = func
end
local set_total_function = function(self, func)
	self._totalfunc = func
end
local drop_header_function = function(self)
	wipe (self.rows)
end

local fillpanel_update_size = function(self, elapsed)
	local panel = self.MyObject

	panel._width = panel:GetWidth()
	panel._height = panel:GetHeight()

	panel:UpdateRowAmount()
	if (panel.current_header) then
		update_rows (panel, panel.current_header)
	end
	panel:Refresh()

	self:SetScript("OnUpdate", nil)
end

 -- ~fillpanel
  --alias
function detailsFramework:CreateFillPanel(parent, rows, w, h, total_lines, fill_row, autowidth, options, member, name)
	return detailsFramework:NewFillPanel(parent, rows, name, member, w, h, total_lines, fill_row, autowidth, options)
end

function detailsFramework:NewFillPanel(parent, rows, name, member, w, h, total_lines, fill_row, autowidth, options)
	local panel = detailsFramework:NewPanel(parent, parent, name, member, w, h)
	panel.backdrop = nil

	options = options or {rowheight = 20}
	panel.rows = {}

	panel.AddRow = add_row
	panel.AlignRows = align_rows
	panel.UpdateRows = update_rows
	panel.CreateRowText = create_panel_text
	panel.CreateRowEntry = create_panel_entry
	panel.CreateRowButton = create_panel_button
	panel.CreateCheckbox = create_panel_checkbox
	panel.CreateRowIcon = create_panel_icon
	panel.CreateRowTexture = create_panel_texture
	panel.SetFillFunction = set_fill_function
	panel.SetTotalFunction = set_total_function
	panel.DropHeader = drop_header_function

	panel._name = name
	panel._width = w
	panel._height = h
	panel._raw_rows = {}
	panel._anchors = {}
	panel._fillfunc = fill_row
	panel._totalfunc = total_lines
	panel._autowidth = autowidth

	panel:SetScript("OnSizeChanged", function()
		panel:SetScript("OnUpdate", fillpanel_update_size)
	end)

	for index, t in ipairs(rows) do
		panel.AddRow(panel, t)
	end

	local refresh_fillbox = function(self)
		local offset = FauxScrollFrame_GetOffset(self)
		local filled_lines = panel._totalfunc(panel)

		for index = 1, #self.lines do
			local row = self.lines [index]
			if (index <= filled_lines) then

				local real_index = index + offset
				local results = panel._fillfunc (real_index, panel)

				if (results and results [1]) then
					row:Show()

					local text, entry, button, icon, texture, checkbox = 1, 1, 1, 1, 1, 1

					for index, t in ipairs(panel.rows) do
						if (not t.hidden) then
							if (t.type == "text") then
								local fontstring = row.text_inuse [text]
								text = text + 1
								fontstring:SetText(results [index])
								fontstring.index = real_index
								fontstring:Show()

							elseif (t.type == "entry") then
								local entrywidget = row.entry_inuse [entry]
								entry = entry + 1
								entrywidget.index = real_index

								if (type(results [index]) == "table") then
									entrywidget:SetText(results [index].text)
									entrywidget.id = results [index].id
									entrywidget.data1 = results [index].data1
									entrywidget.data2 = results [index].data2
								else
									entrywidget:SetText(results [index])
								end

								entrywidget:SetCursorPosition(0)

								entrywidget:Show()

							elseif (t.type == "checkbox") then
								local checkboxwidget = row.checkbox_inuse [button]
								checkbox = checkbox + 1
								checkboxwidget.index = real_index
								checkboxwidget:SetValue(results [index])

								local func = function()
									t.func (real_index, index)
									panel:Refresh()
								end
								checkboxwidget.OnSwitch = func

							elseif (t.type == "button") then
								local buttonwidget = row.button_inuse [button]
								button = button + 1
								buttonwidget.index = real_index

								if (type(results [index]) == "table") then
									if (results [index].text) then
										buttonwidget:SetText(results [index].text)
									end

									if (results [index].icon) then
										buttonwidget._icon:SetTexture(results [index].icon)
									end

									if (results [index].func) then
										local func = function()
											t.func (real_index, results [index].value)
											panel:Refresh()
										end
										buttonwidget:SetClickFunction(func)
									else
										local func = function()
											t.func (real_index, index)
											panel:Refresh()
										end
										buttonwidget:SetClickFunction(func)
									end

									buttonwidget.id = results [index].id
									buttonwidget.data1 = results [index].data1
									buttonwidget.data2 = results [index].data2

								else
									local func = function()
										t.func (real_index, index)
										panel:Refresh()
									end
									buttonwidget:SetClickFunction(func)
									buttonwidget:SetText(results [index])
								end

								buttonwidget:Show()

							elseif (t.type == "icon") then
								local iconwidget = row.icon_inuse [icon]
								icon = icon + 1

								iconwidget.line = index
								iconwidget.index = real_index

								if (type(results [index]) == "string") then
									local result = results [index]:gsub(".-%\\", "")
									iconwidget._icon.texture = results [index]
									iconwidget._icon:SetTexCoord(0.1, .9, 0.1, .9)

								elseif (type(results [index]) == "table") then
									iconwidget._icon:SetTexture(results [index].texture)

									local textCoord = results [index].texcoord
									if (textCoord) then
										iconwidget._icon:SetTexCoord(unpack(textCoord))
									else
										iconwidget._icon:SetTexCoord(0.1, .9, 0.1, .9)
									end

									local color = results [index].color
									if (color) then
										local r, g, b, a = detailsFramework:ParseColors(color)
										iconwidget._icon:SetVertexColor(r, g, b, a)
									else
										iconwidget._icon:SetVertexColor(1, 1, 1, 1)
									end
								else
									iconwidget._icon:SetTexture(results [index])
									iconwidget._icon:SetTexCoord(0.1, .9, 0.1, .9)
								end

								iconwidget:Show()

							elseif (t.type == "texture") then
								local texturewidget = row.texture_inuse [texture]
								texture = texture + 1

								texturewidget.line = index
								texturewidget.index = real_index

								if (type(results [index]) == "string") then
									local result = results [index]:gsub(".-%\\", "")
									texturewidget.texture = results [index]

								elseif (type(results [index]) == "table") then
									texturewidget:SetTexture(results [index].texture)

									local textCoord = results [index].texcoord
									if (textCoord) then
										texturewidget:SetTexCoord(unpack(textCoord))
									else
										texturewidget:SetTexCoord(0, 1, 0, 1)
									end

									local color = results [index].color
									if (color) then
										local r, g, b, a = detailsFramework:ParseColors(color)
										texturewidget:SetVertexColor(r, g, b, a)
									else
										texturewidget:SetVertexColor(1, 1, 1, 1)
									end

								else
									texturewidget:SetTexture(results [index])
								end

								texturewidget:Show()
							end
						end
					end

				else
					row:Hide()
				end
			else
				row:Hide()
			end
		end
	end

	function panel:Refresh()
		if (type(panel._totalfunc) == "boolean") then
			--not yet initialized
			return
		end
		local filled_lines = panel._totalfunc (panel)
		local scroll_total_lines = #panel.scrollframe.lines
		local line_height = options.rowheight
		refresh_fillbox (panel.scrollframe)
		FauxScrollFrame_Update (panel.scrollframe, filled_lines, scroll_total_lines, line_height)
		panel.scrollframe:Show()
	end

	local scrollframe = CreateFrame("scrollframe", name .. "Scroll", panel.widget, "FauxScrollFrameTemplate", "BackdropTemplate")
	scrollframe:SetScript("OnVerticalScroll", function(self, offset) FauxScrollFrame_OnVerticalScroll (self, offset, 20, panel.Refresh) end)
	scrollframe:SetPoint("topleft", panel.widget, "topleft", 0, -21)
	scrollframe:SetPoint("topright", panel.widget, "topright", -23, -21)
	scrollframe:SetPoint("bottomleft", panel.widget, "bottomleft")
	scrollframe:SetPoint("bottomright", panel.widget, "bottomright", -23, 0)
	scrollframe:SetSize(w, h)
	panel.scrollframe = scrollframe
	scrollframe.lines = {}

	detailsFramework:ReskinSlider(scrollframe)

	--create lines
	function panel:UpdateRowAmount()
		local size = options.rowheight
		local amount = math.floor(((panel._height-21) / size))

		for i = #scrollframe.lines+1, amount do
			local row = CreateFrame("frame", panel:GetName() .. "Row_" .. i, panel.widget,"BackdropTemplate")
			row:SetSize(1, size)
			row.color = {1, 1, 1, .2}

			row:SetBackdrop({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]]})

			if (i%2 == 0) then
				row:SetBackdropColor(.5, .5, .5, 0.2)
			else
				row:SetBackdropColor(1, 1, 1, 0.00)
			end

			row:SetPoint("topleft", scrollframe, "topleft", 0, (i-1) * size * -1)
			row:SetPoint("topright", scrollframe, "topright", 0, (i-1) * size * -1)
			tinsert(scrollframe.lines, row)

			row.text_available = {}
			row.text_inuse = {}
			row.text_total = 0

			row.entry_available = {}
			row.entry_inuse = {}
			row.entry_total = 0

			row.button_available = {}
			row.button_inuse = {}
			row.button_total = 0

			row.checkbox_available = {}
			row.checkbox_inuse = {}
			row.checkbox_total = 0

			row.icon_available = {}
			row.icon_inuse = {}
			row.icon_total = 0

			row.texture_available = {}
			row.texture_inuse = {}
			row.texture_total = 0
		end
	end
	panel:UpdateRowAmount()

	panel.AlignRows (panel)

	return panel
end


------------color pick
local color_pick_func = function()
	local r, g, b = ColorPickerFrame:GetColorRGB()
	local a = OpacitySliderFrame:GetValue()
	ColorPickerFrame:dcallback (r, g, b, a, ColorPickerFrame.dframe)
end
local color_pick_func_cancel = function()
	ColorPickerFrame:SetColorRGB (unpack(ColorPickerFrame.previousValues))
	local r, g, b = ColorPickerFrame:GetColorRGB()
	local a = OpacitySliderFrame:GetValue()
	ColorPickerFrame:dcallback (r, g, b, a, ColorPickerFrame.dframe)
end

function detailsFramework:ColorPick (frame, r, g, b, alpha, callback)

	ColorPickerFrame:ClearAllPoints()
	ColorPickerFrame:SetPoint("bottomleft", frame, "topright", 0, 0)

	ColorPickerFrame.dcallback = callback
	ColorPickerFrame.dframe = frame

	ColorPickerFrame.func = color_pick_func
	ColorPickerFrame.opacityFunc = color_pick_func
	ColorPickerFrame.cancelFunc = color_pick_func_cancel

	ColorPickerFrame.opacity = alpha
	ColorPickerFrame.hasOpacity = alpha and true

	ColorPickerFrame.previousValues = {r, g, b}
	ColorPickerFrame:SetParent(UIParent)
	ColorPickerFrame:SetFrameStrata("tooltip")
	ColorPickerFrame:SetColorRGB (r, g, b)
	ColorPickerFrame:Show()

end

------------icon pick
function detailsFramework:IconPick (callback, close_when_select, param1, param2)

	if (not detailsFramework.IconPickFrame) then

		local string_lower = string.lower

		detailsFramework.IconPickFrame = CreateFrame("frame", "DetailsFrameworkIconPickFrame", UIParent, "BackdropTemplate")
		tinsert(UISpecialFrames, "DetailsFrameworkIconPickFrame")
		detailsFramework.IconPickFrame:SetFrameStrata("FULLSCREEN")

		detailsFramework.IconPickFrame:SetPoint("center", UIParent, "center")
		detailsFramework.IconPickFrame:SetWidth(416)
		detailsFramework.IconPickFrame:SetHeight(350)
		detailsFramework.IconPickFrame:EnableMouse(true)
		detailsFramework.IconPickFrame:SetMovable(true)

		detailsFramework:CreateTitleBar (detailsFramework.IconPickFrame, "Details! Framework Icon Picker")

		detailsFramework.IconPickFrame:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true})
		detailsFramework.IconPickFrame:SetBackdropBorderColor(0, 0, 0)
		detailsFramework.IconPickFrame:SetBackdropColor(24/255, 24/255, 24/255, .8)
		detailsFramework.IconPickFrame:SetFrameLevel(5000)

		detailsFramework.IconPickFrame:SetScript("OnMouseDown", function(self)
			if (not self.isMoving) then
				detailsFramework.IconPickFrame:StartMoving()
				self.isMoving = true
			end
		end)

		detailsFramework.IconPickFrame:SetScript("OnMouseUp", function(self)
			if (self.isMoving) then
				detailsFramework.IconPickFrame:StopMovingOrSizing()
				self.isMoving = nil
			end
		end)

		detailsFramework.IconPickFrame.emptyFunction = function() end
		detailsFramework.IconPickFrame.callback = detailsFramework.IconPickFrame.emptyFunction

		detailsFramework.IconPickFrame.preview =  CreateFrame("frame", nil, UIParent, "BackdropTemplate")
		detailsFramework.IconPickFrame.preview:SetFrameStrata("tooltip")
		detailsFramework.IconPickFrame.preview:SetFrameLevel(6001)
		detailsFramework.IconPickFrame.preview:SetSize(76, 76)

		local preview_image_bg = detailsFramework:NewImage(detailsFramework.IconPickFrame.preview, nil, 76, 76)
		preview_image_bg:SetDrawLayer("background", 0)
		preview_image_bg:SetAllPoints(detailsFramework.IconPickFrame.preview)
		preview_image_bg:SetColorTexture(0, 0, 0)

		local preview_image = detailsFramework:NewImage(detailsFramework.IconPickFrame.preview, nil, 76, 76)
		preview_image:SetAllPoints(detailsFramework.IconPickFrame.preview)

		detailsFramework.IconPickFrame.preview.icon = preview_image
		detailsFramework.IconPickFrame.preview:Hide()

		--serach
		detailsFramework.IconPickFrame.searchLabel =  detailsFramework:NewLabel(detailsFramework.IconPickFrame, nil, "$parentSearchBoxLabel", nil, "Search:")
		detailsFramework.IconPickFrame.searchLabel:SetPoint("topleft", detailsFramework.IconPickFrame, "topleft", 12, -36)
		detailsFramework.IconPickFrame.searchLabel:SetTemplate(detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
		detailsFramework.IconPickFrame.searchLabel.fontsize = 12

		detailsFramework.IconPickFrame.search = detailsFramework:NewTextEntry(detailsFramework.IconPickFrame, nil, "$parentSearchBox", nil, 140, 20)
		detailsFramework.IconPickFrame.search:SetPoint("left", detailsFramework.IconPickFrame.searchLabel, "right", 2, 0)
		detailsFramework.IconPickFrame.search:SetTemplate(detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE"))

		detailsFramework.IconPickFrame.search:SetHook("OnTextChanged", function()
			detailsFramework.IconPickFrame.searching = detailsFramework.IconPickFrame.search:GetText()
			if (detailsFramework.IconPickFrame.searching == "") then
				detailsFramework.IconPickFrameScroll:Show()
				detailsFramework.IconPickFrame.searching = nil
				detailsFramework.IconPickFrameScroll.RefreshIcons()
			else
				detailsFramework.IconPickFrameScroll:Hide()
				FauxScrollFrame_SetOffset (detailsFramework.IconPickFrame, 1)
				detailsFramework.IconPickFrame.last_filter_index = 1
				detailsFramework.IconPickFrameScroll.RefreshIcons()
			end
		end)

		--manually enter the icon path
		detailsFramework.IconPickFrame.customIcon = detailsFramework:CreateLabel(detailsFramework.IconPickFrame, "Icon Path:", detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
		detailsFramework.IconPickFrame.customIcon:SetPoint("bottomleft", detailsFramework.IconPickFrame, "bottomleft", 12, 16)
		detailsFramework.IconPickFrame.customIcon.fontsize = 12

		detailsFramework.IconPickFrame.customIconEntry = detailsFramework:CreateTextEntry(detailsFramework.IconPickFrame, function()end, 200, 20, "CustomIconEntry", _, _, detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE"))
		detailsFramework.IconPickFrame.customIconEntry:SetPoint("left", detailsFramework.IconPickFrame.customIcon, "right", 2, 0)

		detailsFramework.IconPickFrame.customIconEntry:SetHook("OnTextChanged", function()
			detailsFramework.IconPickFrame.preview:SetPoint("bottom", detailsFramework.IconPickFrame.customIconEntry.widget, "top", 0, 2)
			detailsFramework.IconPickFrame.preview.icon:SetTexture(detailsFramework.IconPickFrame.customIconEntry:GetText())
			detailsFramework.IconPickFrame.preview:Show()
		end)

		detailsFramework.IconPickFrame.customIconEntry:SetHook("OnEnter", function()
			detailsFramework.IconPickFrame.preview:SetPoint("bottom", detailsFramework.IconPickFrame.customIconEntry.widget, "top", 0, 2)
			detailsFramework.IconPickFrame.preview.icon:SetTexture(detailsFramework.IconPickFrame.customIconEntry:GetText())
			detailsFramework.IconPickFrame.preview:Show()
		end)

		--close button
		local close_button = CreateFrame("button", nil, detailsFramework.IconPickFrame, "UIPanelCloseButton", "BackdropTemplate")
		close_button:SetWidth(32)
		close_button:SetHeight(32)
		close_button:SetPoint("TOPRIGHT", detailsFramework.IconPickFrame, "TOPRIGHT", -8, -7)
		close_button:SetFrameLevel(close_button:GetFrameLevel()+2)
		close_button:SetAlpha(0) --just hide, it is used below

		--accept custom icon button
		local accept_custom_icon = function()
			local path = detailsFramework.IconPickFrame.customIconEntry:GetText()

			detailsFramework:QuickDispatch(detailsFramework.IconPickFrame.callback, path, detailsFramework.IconPickFrame.param1, detailsFramework.IconPickFrame.param2)

			if (detailsFramework.IconPickFrame.click_close) then
				close_button:Click()
			end
		end

		detailsFramework.IconPickFrame.customIconAccept = detailsFramework:CreateButton(detailsFramework.IconPickFrame, accept_custom_icon, 82, 20, "Accept", nil, nil, nil, nil, nil, nil, detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
		detailsFramework.IconPickFrame.customIconAccept:SetPoint("left", detailsFramework.IconPickFrame.customIconEntry, "right", 2, 0)

		--fill with icons
		local MACRO_ICON_FILENAMES = {}
		local SPELLNAMES_CACHE = {}

		detailsFramework.IconPickFrame:SetScript("OnShow", function()
			MACRO_ICON_FILENAMES[1] = "INV_MISC_QUESTIONMARK"
			local index = 2

			for i = 1, GetNumSpellTabs() do
				local tab, tabTex, offset, numSpells, _ = GetSpellTabInfo(i)
				offset = offset + 1
				local tabEnd = offset + numSpells

				for j = offset, tabEnd - 1 do
					--to get spell info by slot, you have to pass in a pet argument
					local spellType, ID = GetSpellBookItemInfo(j, "player")
					if (spellType ~= "FLYOUT") then
						MACRO_ICON_FILENAMES [index] = GetSpellBookItemTexture(j, "player") or 0
						SPELLNAMES_CACHE [index] = GetSpellInfo(ID)
						index = index + 1

					elseif (spellType == "FLYOUT") then
						local _, _, numSlots, isKnown = GetFlyoutInfo(ID)
						if (isKnown and numSlots > 0) then
							for k = 1, numSlots do
								local spellID, overrideSpellID, isKnown = GetFlyoutSlotInfo(ID, k)
								if (isKnown) then
									MACRO_ICON_FILENAMES [index] = GetSpellTexture(spellID) or 0
									SPELLNAMES_CACHE [index] = GetSpellInfo(spellID)
									index = index + 1
								end
							end
						end
					end
				end
			end

			GetLooseMacroItemIcons(MACRO_ICON_FILENAMES)
			GetLooseMacroIcons(MACRO_ICON_FILENAMES)
			GetMacroIcons(MACRO_ICON_FILENAMES)
			GetMacroItemIcons(MACRO_ICON_FILENAMES)

			--reset the custom icon text entry
			detailsFramework.IconPickFrame.customIconEntry:SetText("")
			--reset the search text entry
			detailsFramework.IconPickFrame.search:SetText("")
		end)

		detailsFramework.IconPickFrame:SetScript("OnHide", function()
			wipe(MACRO_ICON_FILENAMES)
			wipe(SPELLNAMES_CACHE)
			detailsFramework.IconPickFrame.preview:Hide()
			collectgarbage()
		end)

		detailsFramework.IconPickFrame.buttons = {}

		local onClickFunction = function(self)

			detailsFramework:QuickDispatch(detailsFramework.IconPickFrame.callback, self.icon:GetTexture(), detailsFramework.IconPickFrame.param1, detailsFramework.IconPickFrame.param2)

			if (detailsFramework.IconPickFrame.click_close) then
				close_button:Click()
			end
		end

		local onEnter = function(self)
			detailsFramework.IconPickFrame.preview:SetPoint("bottom", self, "top", 0, 2)
			detailsFramework.IconPickFrame.preview.icon:SetTexture(self.icon:GetTexture())
			detailsFramework.IconPickFrame.preview:Show()
			self.icon:SetBlendMode("ADD")
		end
		local onLeave = function(self)
			detailsFramework.IconPickFrame.preview:Hide()
			self.icon:SetBlendMode("BLEND")
		end

		local backdrop = {bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tile = true, tileSize = 16,
		insets = {left = 0, right = 0, top = 0, bottom = 0}, edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1}

		for _, button in ipairs(detailsFramework.IconPickFrame.buttons) do
			button:SetBackdropBorderColor(0, 0, 0, 1)
		end

		local width = 412
		local height = 248
		local linesAmount = 6
		local lineHeight = 40

		local updateIconScroll = function(self, data, offset, totalLines)
            for i = 1, totalLines do
                local index = i + offset
                local iconsInThisLine = data[index]
				if (iconsInThisLine) then
					local line = self:GetLine(i)
                    for o = 1, #iconsInThisLine do
						local _, _, texture = GetSpellInfo(iconsInThisLine[o])
						if (texture) then
							line.buttons[o].icon:SetTexture(texture)
							line.buttons[o].texture = texture
						else
							line.buttons[o].icon:SetTexture(iconsInThisLine[o])
							line.buttons[o].texture = iconsInThisLine[o]
						end
					end
				end
			end
		end

		local lower = string.lower

		local scroll = detailsFramework:CreateScrollBox(detailsFramework.IconPickFrame, "DetailsFrameworkIconPickFrameScroll", updateIconScroll, {}, width, height, linesAmount, lineHeight)
		detailsFramework:ReskinSlider(scroll)
		scroll:SetPoint("topleft", detailsFramework.IconPickFrame, "topleft", 2, -58)

		function scroll.RefreshIcons()
			--build icon list
			local iconList = {}
			local numMacroIcons = #MACRO_ICON_FILENAMES

			local filter
			if (detailsFramework.IconPickFrame.searching) then
				filter = lower(detailsFramework.IconPickFrame.searching)
			end

			if (filter and filter ~= "") then
				local index
				local currentTable
				for i = 1, #SPELLNAMES_CACHE do
					if (SPELLNAMES_CACHE[i] and SPELLNAMES_CACHE[i]:lower():find(filter)) then
						if (not index) then
							index = 1
							local t = {}
							iconList[#iconList+1] = t
							currentTable = t
						end

						currentTable[index] = SPELLNAMES_CACHE[i]

						index = index + 1
						if (index == 11) then
							index = nil
						end
					end

				end
			else
				for i = 1, #SPELLNAMES_CACHE, 10 do
					local t = {}
					iconList[#iconList+1] = t
					for o = i, i+9 do
						if (SPELLNAMES_CACHE[o]) then
							t[#t+1] = SPELLNAMES_CACHE[o]
						end
					end
				end

				for i = 1, #MACRO_ICON_FILENAMES, 10 do
					local t = {}
					iconList[#iconList+1] = t
					for o = i, i+9 do
						if (MACRO_ICON_FILENAMES[o]) then
							t[#t+1] = MACRO_ICON_FILENAMES[o]
						end
					end
				end
			end

			--set data and refresh
			scroll:SetData(iconList)
			scroll:Refresh()
		end

		--create the lines and button of the scroll box
		for i = 1, linesAmount do
			scroll:CreateLine(function(self, index)
				local line = CreateFrame("button", "$parentLine" .. index, self, "BackdropTemplate")
				line:SetPoint("topleft", self, "topleft", 1, -((index-1)*(lineHeight+1)) - 1)
				line:SetSize(width - 2, lineHeight)
				line:SetBackdrop({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true})
				line:SetBackdropColor(.2, .2, .2, .5)
				line.buttons = {}

				local lastButton

				for o = 1, 10 do
					local button = CreateFrame("button", "$parentIcon" .. o, line)
					if (not lastButton) then
						button:SetPoint("left", line, "left", 0, 0)
					else
						button:SetPoint("left", lastButton, "right", 1, 0)
					end
					button:SetSize(lineHeight, lineHeight)
					button.icon = button:CreateTexture("$parentIcon", "overlay")
					button.icon:SetAllPoints()
					button.icon:SetTexCoord(.1, .9, .1, .9)
					line.buttons[o] = button

					button:SetScript("OnEnter", onEnter)
					button:SetScript("OnLeave", onLeave)
					button:SetScript("OnClick", onClickFunction)

					lastButton = button
				end

				return line
			end)
		end

		detailsFramework.IconPickFrameScroll = scroll
		detailsFramework.IconPickFrame:Hide()
	end

	detailsFramework.IconPickFrame.param1, detailsFramework.IconPickFrame.param2 = param1, param2
	detailsFramework.IconPickFrame:Show()
	detailsFramework.IconPickFrame.callback = callback or detailsFramework.IconPickFrame.emptyFunction
	detailsFramework.IconPickFrame.click_close = close_when_select
	detailsFramework.IconPickFrameScroll.RefreshIcons()

end

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

function detailsFramework:ShowPanicWarning (text)
	if (not detailsFramework.PanicWarningWindow) then
		detailsFramework.PanicWarningWindow = CreateFrame("frame", "DetailsFrameworkPanicWarningWindow", UIParent, "BackdropTemplate")
		detailsFramework.PanicWarningWindow:SetHeight(80)
		detailsFramework.PanicWarningWindow:SetBackdrop({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true})
		detailsFramework.PanicWarningWindow:SetBackdropColor(1, 0, 0, 0.2)
		detailsFramework.PanicWarningWindow:SetPoint("topleft", UIParent, "topleft", 0, -250)
		detailsFramework.PanicWarningWindow:SetPoint("topright", UIParent, "topright", 0, -250)

		detailsFramework.PanicWarningWindow.text = detailsFramework.PanicWarningWindow:CreateFontString(nil, "overlay", "GameFontNormal")
		detailsFramework.PanicWarningWindow.text:SetPoint("center", detailsFramework.PanicWarningWindow, "center")
		detailsFramework.PanicWarningWindow.text:SetTextColor(1, 0.6, 0)
	end

	detailsFramework.PanicWarningWindow.text:SetText(text)
	detailsFramework.PanicWarningWindow:Show()
end

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


local simple_panel_mouse_down = function(self, button)
	if (button == "RightButton") then
		if (self.IsMoving) then
			self.IsMoving = false
			self:StopMovingOrSizing()
			if (self.db and self.db.position) then
				detailsFramework:SavePositionOnScreen (self)
			end
		end
		if (not self.DontRightClickClose) then
			self:Hide()
		end
		return
	end
	if (not self.IsMoving and not self.IsLocked) then
		self.IsMoving = true
		self:StartMoving()
	end
end
local simple_panel_mouse_up = function(self, button)
	if (self.IsMoving) then
		self.IsMoving = false
		self:StopMovingOrSizing()
		if (self.db and self.db.position) then
			detailsFramework:SavePositionOnScreen (self)
		end
	end
end
local simple_panel_settitle = function(self, title)
	self.Title:SetText(title)
end

local simple_panel_close_click = function(self)
	self:GetParent():GetParent():Hide()
end

local SimplePanel_frame_backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}
local SimplePanel_frame_backdrop_color = {0, 0, 0, 0.9}
local SimplePanel_frame_backdrop_border_color = {0, 0, 0, 1}

--with_label was making the frame stay in place while its parent moves
--the slider was anchoring to with_label and here here were anchoring the slider again
function detailsFramework:CreateScaleBar(frame, config) --~scale
	local scaleBar, text = detailsFramework:CreateSlider(frame, 120, 14, 0.6, 1.6, 0.1, config.scale, true, "ScaleBar", nil, "Scale:", detailsFramework:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE"), detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
	scaleBar.thumb:SetWidth(24)
	scaleBar:SetValueStep(0.1)
	scaleBar:SetObeyStepOnDrag(true)
	scaleBar.mouseDown = false
	rawset(scaleBar, "lockdown", true)

	--create a custom editbox to enter the scale from text
	local editbox = CreateFrame("editbox", nil, scaleBar.widget, "BackdropTemplate")
	editbox:SetSize(40, 20)
	editbox:SetJustifyH("center")
	editbox:SetBackdrop({bgFile = [[Interface\ACHIEVEMENTFRAME\UI-GuildAchievement-Parchment-Horizontal-Desaturated]],
	edgeFile = [[Interface\Buttons\WHITE8X8]],
	tile = true, edgeSize = 1, tileSize = 64})
	editbox:SetFontObject("GameFontHighlightSmall")
	editbox:SetBackdropColor(0, 0, 0, 1)

	editbox:SetScript("OnEditFocusGained", function()
	end)

	editbox:SetScript("OnEnterPressed", function()
		editbox:ClearFocus()
		editbox:Hide()
		local text = editbox:GetText()
		local newScale = detailsFramework.TextToFloor(text)

		if (newScale) then
			config.scale = newScale
			scaleBar:SetValue(newScale)
			frame:SetScale(newScale)
			editbox.defaultValue = newScale
		end
	end)

	editbox:SetScript("OnEscapePressed", function()
		editbox:ClearFocus()
		editbox:Hide()
		editbox:SetText(editbox.defaultValue)
	end)

	scaleBar:SetScript("OnMouseDown", function(_, mouseButton)
		if (mouseButton == "RightButton") then
			editbox:Show()
			editbox:SetAllPoints()
			editbox:SetText(config.scale)
			editbox:SetFocus(true)
			editbox.defaultValue = config.scale

		elseif (mouseButton == "LeftButton") then
			scaleBar.mouseDown  = true
		end
	end)

	scaleBar:SetScript("OnMouseUp", function(_, mouseButton)
		if (mouseButton == "LeftButton") then
			scaleBar.mouseDown  = false
			frame:SetScale(config.scale)
			editbox.defaultValue = config.scale
		end
	end)

	text:SetPoint("topleft", frame, "topleft", 12, -7)
	scaleBar:SetFrameLevel(detailsFramework.FRAMELEVEL_OVERLAY)
	scaleBar.OnValueChanged = function(_, _, value)
		if (scaleBar.mouseDown) then
			config.scale = value
		end
	end

	scaleBar:SetAlpha(0.70)
	editbox.defaultValue = config.scale
	editbox:SetFocus(false)
	editbox:SetAutoFocus(false)
	editbox:ClearFocus()

	C_Timer.After(1, function()
		editbox:SetFocus(false)
		editbox:SetAutoFocus(false)
		editbox:ClearFocus()
	end)

	return scaleBar
end

local no_options = {}
--[=[
	options available to panel_options:
	NoScripts = false, --if true, won't set OnMouseDown and OnMouseUp (won't be movable)
	NoTUISpecialFrame = false, --if true, won't add the frame to 'UISpecialFrames'
	DontRightClickClose = false, --if true, won't make the frame close when clicked with the right mouse button
	UseScaleBar = false, --if true, will create a scale bar in the top left corner (require a table on 'db' to save the scale)
	UseStatusBar = false, --if true, creates a status bar at the bottom of the frame (frame.StatusBar)
	NoCloseButton = false, --if true, won't show the close button
	NoTitleBar = false, --if true, don't create the title bar
]=]
function detailsFramework:CreateSimplePanel(parent, width, height, title, frameName, panelOptions, savedVariableTable)
	if (savedVariableTable and frameName and not savedVariableTable[frameName]) then
		savedVariableTable[frameName] = {
			scale = 1
		}
	end

	if (not frameName) then
		frameName = "DetailsFrameworkSimplePanel" .. detailsFramework.SimplePanelCounter
		detailsFramework.SimplePanelCounter = detailsFramework.SimplePanelCounter + 1
	end
	if (not parent) then
		parent = UIParent
	end

	panelOptions = panelOptions or no_options

	local simplePanel = CreateFrame("frame", frameName, UIParent,"BackdropTemplate")
	simplePanel:SetSize(width or 400, height or 250)
	simplePanel:SetPoint("center", UIParent, "center", 0, 0)
	simplePanel:SetFrameStrata("FULLSCREEN")
	simplePanel:EnableMouse()
	simplePanel:SetMovable(true)
	simplePanel:SetBackdrop(SimplePanel_frame_backdrop)
	simplePanel:SetBackdropColor(unpack(SimplePanel_frame_backdrop_color))
	simplePanel:SetBackdropBorderColor(unpack(SimplePanel_frame_backdrop_border_color))

	simplePanel.DontRightClickClose = panelOptions.DontRightClickClose

	if (not panelOptions.NoTUISpecialFrame) then
		tinsert(UISpecialFrames, frameName)
	end

	if (panelOptions.UseStatusBar) then
		local statusBar = detailsFramework:CreateStatusBar(simplePanel)
		simplePanel.StatusBar = statusBar
	end

	local titleBar = CreateFrame("frame", frameName .. "TitleBar", simplePanel,"BackdropTemplate")
	titleBar:SetPoint("topleft", simplePanel, "topleft", 2, -3)
	titleBar:SetPoint("topright", simplePanel, "topright", -2, -3)
	titleBar:SetHeight(20)
	titleBar:SetBackdrop(SimplePanel_frame_backdrop)
	titleBar:SetBackdropColor(.2, .2, .2, 1)
	titleBar:SetBackdropBorderColor(0, 0, 0, 1)
	simplePanel.TitleBar = titleBar

	local close = CreateFrame("button", frameName and frameName .. "CloseButton", titleBar)
	close:SetFrameLevel(detailsFramework.FRAMELEVEL_OVERLAY)
	close:SetSize(16, 16)

	close:SetNormalTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	close:SetHighlightTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	close:SetPushedTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	close:GetNormalTexture():SetDesaturated(true)
	close:GetHighlightTexture():SetDesaturated(true)
	close:GetPushedTexture():SetDesaturated(true)

	close:SetAlpha(0.7)
	close:SetScript("OnClick", simple_panel_close_click)
	simplePanel.Close = close

	local titleText = titleBar:CreateFontString(frameName and frameName .. "Title", "overlay", "GameFontNormal")
	titleText:SetTextColor(.8, .8, .8, 1)
	titleText:SetText(title or "")
	simplePanel.Title = titleText

	if (panelOptions.UseScaleBar and savedVariableTable [frameName]) then
		detailsFramework:CreateScaleBar (simplePanel, savedVariableTable [frameName])
		simplePanel:SetScale(savedVariableTable [frameName].scale)
	end

	simplePanel.Title:SetPoint("center", titleBar, "center")
	simplePanel.Close:SetPoint("right", titleBar, "right", -2, 0)

	if (panelOptions.NoCloseButton) then
		simplePanel.Close:Hide()
	end

	if (panelOptions.NoTitleBar) then
		simplePanel.TitleBar:Hide()
	end

	if (not panelOptions.NoScripts) then
		simplePanel:SetScript("OnMouseDown", simple_panel_mouse_down)
		simplePanel:SetScript("OnMouseUp", simple_panel_mouse_up)
	end

	simplePanel.SetTitle = simple_panel_settitle

	return simplePanel
end

local Panel1PxBackdrop = {bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 64,
edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, insets = {left = 2, right = 2, top = 3, bottom = 3}}

local Panel1PxOnClickClose = function(self)
	self:GetParent():Hide()
end
local Panel1PxOnToggleLock = function(self)
	if (self.IsLocked) then
		self.IsLocked = false
		self:SetMovable(true)
		self:EnableMouse(true)
		self.Lock:GetNormalTexture():SetTexCoord(16/64, 32/64, 0, 1)
		self.Lock:GetHighlightTexture():SetTexCoord(16/32, 32/64, 0, 1)
		self.Lock:GetPushedTexture():SetTexCoord(16/64, 32/64, 0, 1)
		if (self.OnUnlock) then
			self:OnUnlock()
		end
		if (self.db) then
			self.db.IsLocked = self.IsLocked
		end
	else
		self.IsLocked = true
		self:SetMovable(false)
		self:EnableMouse(false)
		self.Lock:GetNormalTexture():SetTexCoord(0/64, 16/64, 0, 1)
		self.Lock:GetHighlightTexture():SetTexCoord(0/64, 16/64, 0, 1)
		self.Lock:GetPushedTexture():SetTexCoord(0/64, 16/64, 0, 1)
		if (self.OnLock) then
			self:OnLock()
		end
		if (self.db) then
			self.db.IsLocked = self.IsLocked
		end
	end
end
local Panel1PxOnClickLock = function(self)
	local f = self:GetParent()
	Panel1PxOnToggleLock (f)
end
local Panel1PxSetTitle = function(self, text)
	self.Title:SetText(text or "")
end

local Panel1PxSetLocked= function(self, lock_state)
	if (type(lock_state) ~= "boolean") then
		return
	end
	if (lock_state) then
		-- lock it
		self.IsLocked = false
		Panel1PxOnClickLock (self.Lock)
	else
		-- unlockit
		self.IsLocked = true
		Panel1PxOnClickLock (self.Lock)
	end
end

local Panel1PxReadConfig = function(self)
	local db = self.db
	if (db) then
		db.IsLocked = db.IsLocked or false
		self.IsLocked = db.IsLocked
		db.position = db.position or {x = 0, y = 0}
		db.position.x = db.position.x or 0
		db.position.y = db.position.y or 0
		detailsFramework:RestoreFramePosition (self)
	end
end

function detailsFramework:SavePositionOnScreen (frame)
	if (frame.db and frame.db.position) then
		local x, y = detailsFramework:GetPositionOnScreen (frame)
		--print("saving...", x, y, frame:GetName())
		if (x and y) then
			frame.db.position.x, frame.db.position.y = x, y
		end
	end
end

function detailsFramework:GetPositionOnScreen (frame)
	local xOfs, yOfs = frame:GetCenter()
	if (not xOfs) then
		return
	end
	local scale = frame:GetEffectiveScale()
	local UIscale = UIParent:GetScale()
	xOfs = xOfs*scale - GetScreenWidth()*UIscale/2
	yOfs = yOfs*scale - GetScreenHeight()*UIscale/2
	return xOfs/UIscale, yOfs/UIscale
end

function detailsFramework:RestoreFramePosition (frame)
	if (frame.db and frame.db.position) then
		local scale, UIscale = frame:GetEffectiveScale(), UIParent:GetScale()
		frame:ClearAllPoints()
		frame.db.position.x = frame.db.position.x or 0
		frame.db.position.y = frame.db.position.y or 0
		frame:SetPoint("center", UIParent, "center", frame.db.position.x * UIscale / scale, frame.db.position.y * UIscale / scale)
	end
end

local Panel1PxSavePosition= function(self)
	detailsFramework:SavePositionOnScreen (self)
end

local Panel1PxHasPosition = function(self)
	local db = self.db
	if (db) then
		if (db.position and db.position.x and (db.position.x ~= 0 or db.position.y ~= 0)) then
			return true
		end
	end
end

function detailsFramework:Create1PxPanel(parent, width, height, title, name, config, titleAnchor, noSpecialFrame)
	local newFrame = CreateFrame("frame", name, parent or UIParent, "BackdropTemplate")
	newFrame:SetSize(width or 100, height or 75)
	newFrame:SetPoint("center", UIParent, "center", 0, 0)

	if (name and not noSpecialFrame) then
		tinsert(UISpecialFrames, name)
	end

	newFrame:SetScript("OnMouseDown", simple_panel_mouse_down)
	newFrame:SetScript("OnMouseUp", simple_panel_mouse_up)

	newFrame:SetBackdrop(Panel1PxBackdrop)
	newFrame:SetBackdropColor(0, 0, 0, 0.5)

	newFrame.IsLocked = (config and config.IsLocked ~= nil and config.IsLocked) or false
	newFrame:SetMovable(true)
	newFrame:EnableMouse(true)
	newFrame:SetUserPlaced (true)

	newFrame.db = config
	Panel1PxReadConfig(newFrame)

	local closeButton = CreateFrame("button", name and name .. "CloseButton", newFrame, "BackdropTemplate")
	closeButton:SetSize(16, 16)
	closeButton:SetNormalTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	closeButton:SetHighlightTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	closeButton:SetPushedTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	closeButton:GetNormalTexture():SetDesaturated(true)
	closeButton:GetHighlightTexture():SetDesaturated(true)
	closeButton:GetPushedTexture():SetDesaturated(true)
	closeButton:SetAlpha(0.7)

	local lockButton = CreateFrame("button", name and name .. "LockButton", newFrame, "BackdropTemplate")
	lockButton:SetSize(16, 16)
	lockButton:SetNormalTexture([[Interface\GLUES\CharacterSelect\Glues-AddOn-Icons]])
	lockButton:SetHighlightTexture([[Interface\GLUES\CharacterSelect\Glues-AddOn-Icons]])
	lockButton:SetPushedTexture([[Interface\GLUES\CharacterSelect\Glues-AddOn-Icons]])
	lockButton:GetNormalTexture():SetDesaturated(true)
	lockButton:GetHighlightTexture():SetDesaturated(true)
	lockButton:GetPushedTexture():SetDesaturated(true)
	lockButton:SetAlpha(0.7)

	closeButton:SetPoint("topright", newFrame, "topright", -3, -3)
	lockButton:SetPoint("right", closeButton, "left", 3, 0)

	closeButton:SetScript("OnClick", Panel1PxOnClickClose)
	lockButton:SetScript("OnClick", Panel1PxOnClickLock)

	local titleString = newFrame:CreateFontString(name and name .. "Title", "overlay", "GameFontNormal")
	titleString:SetPoint("topleft", newFrame, "topleft", 5, -5)

	detailsFramework.Language.SetTextIfLocTableOrDefault(titleString, title or "")

	if (titleAnchor) then
		if (titleAnchor == "top") then
			titleString:ClearAllPoints()
			titleString:SetPoint("bottomleft", newFrame, "topleft", 0, 0)

			closeButton:ClearAllPoints()
			closeButton:SetPoint("bottomright", newFrame, "topright", 0, 0)
		end
		newFrame.title_anchor = titleAnchor
	end

	newFrame.SetTitle = Panel1PxSetTitle
	newFrame.Title = titleString
	newFrame.Lock = lockButton
	newFrame.Close = closeButton
	newFrame.HasPosition = Panel1PxHasPosition
	newFrame.SavePosition = Panel1PxSavePosition

	newFrame.IsLocked = not newFrame.IsLocked
	newFrame.SetLocked = Panel1PxSetLocked
	Panel1PxOnToggleLock(newFrame)

	return newFrame
end

------------------------------------------------------------------------------------------------------------------------------------------------
-- ~prompt
--@dontOverride: won't show another prompt if theres already a shown prompt
function detailsFramework:ShowPromptPanel(message, trueCallback, falseCallback, dontOverride, width)
	if (not DetailsFrameworkPromptSimple) then
		local promptFrame = CreateFrame("frame", "DetailsFrameworkPromptSimple", UIParent, "BackdropTemplate")
		promptFrame:SetSize(400, 80)
		promptFrame:SetFrameStrata("DIALOG")
		promptFrame:SetPoint("center", UIParent, "center", 0, 300)
		detailsFramework:ApplyStandardBackdrop(promptFrame)
		tinsert(UISpecialFrames, "DetailsFrameworkPromptSimple")

		detailsFramework:CreateTitleBar(promptFrame, "Prompt!")
		detailsFramework:ApplyStandardBackdrop(promptFrame)

		local prompt = promptFrame:CreateFontString(nil, "overlay", "GameFontNormal")
		prompt:SetPoint("top", promptFrame, "top", 0, -28)
		prompt:SetJustifyH("center")
		promptFrame.prompt = prompt

		local button_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")
		local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")

		local buttonTrue = detailsFramework:CreateButton(promptFrame, nil, 60, 20, "Yes", nil, nil, nil, nil, nil, nil, options_dropdown_template)
		buttonTrue:SetPoint("bottomright", promptFrame, "bottomright", -5, 5)
		promptFrame.button_true = buttonTrue

		local buttonFalse = detailsFramework:CreateButton(promptFrame, nil, 60, 20, "No", nil, nil, nil, nil, nil, nil, options_dropdown_template)
		buttonFalse:SetPoint("bottomleft", promptFrame, "bottomleft", 5, 5)
		promptFrame.button_false = buttonFalse

		buttonTrue:SetClickFunction(function()
			local my_func = buttonTrue.true_function
			if (my_func) then
				local okey, errormessage = pcall(my_func, true)
				if (not okey) then
					print("error:", errormessage)
				end
				promptFrame:Hide()
			end
		end)

		buttonFalse:SetClickFunction(function()
			local my_func = buttonFalse.false_function
			if (my_func) then
				local okey, errormessage = pcall(my_func, true)
				if (not okey) then
					print("error:", errormessage)
				end
				promptFrame:Hide()
			end
		end)

		promptFrame.ShowAnimation = detailsFramework:CreateAnimationHub(promptFrame, function()
			promptFrame:SetBackdropBorderColor(0, 0, 0, 0)
			promptFrame.TitleBar:SetBackdropBorderColor(0, 0, 0, 0)
		end,
		function()
			promptFrame:SetBackdropBorderColor(0, 0, 0, 1)
			promptFrame.TitleBar:SetBackdropBorderColor(0, 0, 0, 1)
		end)

		detailsFramework:CreateAnimation(promptFrame.ShowAnimation, "scale", 1, .075, .2, .2, 1.1, 1.1, "center", 0, 0)
		detailsFramework:CreateAnimation(promptFrame.ShowAnimation, "scale", 2, .075, 1, 1, .90, .90, "center", 0, 0)

		promptFrame.FlashTexture = promptFrame:CreateTexture(nil, "overlay")
		promptFrame.FlashTexture:SetColorTexture(1, 1, 1, 1)
		promptFrame.FlashTexture:SetAllPoints()

		promptFrame.FlashAnimation = detailsFramework:CreateAnimationHub(promptFrame.FlashTexture, function() promptFrame.FlashTexture:Show() end, function() promptFrame.FlashTexture:Hide() end)
		detailsFramework:CreateAnimation(promptFrame.FlashAnimation, "alpha", 1, .075, 0, .25)
		detailsFramework:CreateAnimation(promptFrame.FlashAnimation, "alpha", 2, .075, .35, 0)

		promptFrame:Hide()
		detailsFramework.promtp_panel = promptFrame
	end

	assert(type(trueCallback) == "function" and type(falseCallback) == "function", "ShowPromptPanel expects two functions.")

	if (dontOverride) then
		if (detailsFramework.promtp_panel:IsShown()) then
			return
		end
	end

	if (width) then
		detailsFramework.promtp_panel:SetWidth(width)
	else
		detailsFramework.promtp_panel:SetWidth(400)
	end

	detailsFramework.promtp_panel.prompt:SetText(message)
	detailsFramework.promtp_panel.button_true.true_function = trueCallback
	detailsFramework.promtp_panel.button_false.false_function = falseCallback

	detailsFramework.promtp_panel:Show()

	detailsFramework.promtp_panel.ShowAnimation:Play()
	detailsFramework.promtp_panel.FlashAnimation:Play()
end


function detailsFramework:ShowTextPromptPanel(message, callback)
	if (not detailsFramework.text_prompt_panel) then
		local promptFrame = CreateFrame("frame", "DetailsFrameworkPrompt", UIParent, "BackdropTemplate")
		promptFrame:SetSize(400, 120)
		promptFrame:SetFrameStrata("FULLSCREEN")
		promptFrame:SetPoint("center", UIParent, "center", 0, 100)
		promptFrame:EnableMouse(true)
		promptFrame:SetMovable(true)
		promptFrame:RegisterForDrag ("LeftButton")
		promptFrame:SetScript("OnDragStart", function() promptFrame:StartMoving() end)
		promptFrame:SetScript("OnDragStop", function() promptFrame:StopMovingOrSizing() end)
		promptFrame:SetScript("OnMouseDown", function(self, button) if (button == "RightButton") then promptFrame.EntryBox:ClearFocus() promptFrame:Hide() end end)
		tinsert(UISpecialFrames, "DetailsFrameworkPrompt")

		detailsFramework:CreateTitleBar (promptFrame, "Prompt!")
		detailsFramework:ApplyStandardBackdrop(promptFrame)

		local prompt = promptFrame:CreateFontString(nil, "overlay", "GameFontNormal")
		prompt:SetPoint("top", promptFrame, "top", 0, -25)
		prompt:SetJustifyH("center")
		prompt:SetSize(360, 36)
		promptFrame.prompt = prompt

		local button_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")
		local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")

		local textbox = detailsFramework:CreateTextEntry(promptFrame, function()end, 380, 20, "textbox", nil, nil, options_dropdown_template)
		textbox:SetPoint("topleft", promptFrame, "topleft", 10, -60)
		promptFrame.EntryBox = textbox

		local buttonTrue = detailsFramework:CreateButton(promptFrame, nil, 60, 20, "Okey", nil, nil, nil, nil, nil, nil, options_dropdown_template)
		buttonTrue:SetPoint("bottomright", promptFrame, "bottomright", -10, 5)
		promptFrame.button_true = buttonTrue

		local buttonFalse = detailsFramework:CreateButton(promptFrame, function() promptFrame.textbox:ClearFocus() promptFrame:Hide() end, 60, 20, "Cancel", nil, nil, nil, nil, nil, nil, options_dropdown_template)
		buttonFalse:SetPoint("bottomleft", promptFrame, "bottomleft", 10, 5)
		promptFrame.button_false = buttonFalse

		local executeCallback = function()
			local my_func = buttonTrue.true_function
			if (my_func) then
				local okey, errormessage = pcall(my_func, textbox:GetText())
				textbox:ClearFocus()
				if (not okey) then
					print("error:", errormessage)
				end
				promptFrame:Hide()
			end
		end

		buttonTrue:SetClickFunction(function()
			executeCallback()
		end)

		textbox:SetHook("OnEnterPressed", function()
			executeCallback()
		end)

		promptFrame:Hide()
		detailsFramework.text_prompt_panel = promptFrame
	end

	detailsFramework.text_prompt_panel:Show()
	DetailsFrameworkPrompt.EntryBox:SetText("")
	detailsFramework.text_prompt_panel.prompt:SetText(message)
	detailsFramework.text_prompt_panel.button_true.true_function = callback
	detailsFramework.text_prompt_panel.textbox:SetFocus(true)
end


------------------------------------------------------------------------------------------------------------------------------------------------
--chart panel -- ~chart

local chart_panel_backdrop = {bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16,
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 32, insets = {left = 5, right = 5, top = 5, bottom = 5}}

local chart_panel_align_timelabels = function(self, elapsed_time)
	self.TimeScale = elapsed_time

	local linha = self.TimeLabels[17]
	local minutes, seconds = math.floor(elapsed_time / 60), math.floor(elapsed_time % 60)
	if (seconds < 10) then
		seconds = "0" .. seconds
	end

	if (minutes > 0) then
		if (minutes < 10) then
			minutes = "0" .. minutes
		end
		linha:SetText(minutes .. ":" .. seconds)
	else
		linha:SetText("00:" .. seconds)
	end

	local time_div = elapsed_time / 16 --786 -- 49.125

	for i = 2, 16 do
		local linha = self.TimeLabels [i]
		local this_time = time_div * (i-1)
		local minutes, seconds = math.floor(this_time / 60), math.floor(this_time % 60)

		if (seconds < 10) then
			seconds = "0" .. seconds
		end

		if (minutes > 0) then
			if (minutes < 10) then
				minutes = "0" .. minutes
			end
			linha:SetText(minutes .. ":" .. seconds)
		else
			linha:SetText("00:" .. seconds)
		end
	end
end

local chart_panel_set_scale = function(self, amt, func, text)
	if (type(amt) ~= "number") then
		return
	end

	--each line amount, then multiply the line index by this number
	local piece = amt / 8

	for i = 1, 8 do
		if (func) then
			self ["dpsamt" .. math.abs(i-9)]:SetText(func (piece*i))
		else
			if (piece*i > 1) then
				self ["dpsamt" .. math.abs(i-9)]:SetText(detailsFramework.FormatNumber (piece*i))
			else
				self ["dpsamt" .. math.abs(i-9)]:SetText(format("%.3f", piece*i))
			end
		end
	end
end

local chart_panel_can_move = function(self, can)
	self.can_move = can
end

local chart_panel_overlay_reset = function(self)
	self.OverlaysAmount = 1
	for index, pack in ipairs(self.Overlays) do
		for index2, texture in ipairs(pack) do
			texture:Hide()
		end
	end
end

local chart_panel_reset = function(self)
	self.Graphic:ResetData()
	self.Graphic.max_value = 0

	self.TimeScale = nil
	self.BoxLabelsAmount = 1
	table.wipe(self.GData)
	table.wipe(self.OData)

	for index, box in ipairs(self.BoxLabels) do
		box.check:Hide()
		box.button:Hide()
		box.box:Hide()
		box.text:Hide()
		box.border:Hide()
		box.showing = false
	end

	chart_panel_overlay_reset (self)
end

local chart_panel_enable_line = function(f, thisbox)
	local index = thisbox.index
	local boxType = thisbox.type

	if (thisbox.enabled) then
		--disable
		thisbox.check:Hide()
		thisbox.enabled = false
	else
		--enable
		thisbox.check:Show()
		thisbox.enabled = true
	end

	if (boxType == "graphic") then
		f.Graphic:ResetData()
		f.Graphic.max_value = 0

		local max = 0
		local max_time = 0

		for index, box in ipairs(f.BoxLabels) do
			if (box.type == boxType and box.showing and box.enabled) then
				local data = f.GData[index]

				f.Graphic:AddDataSeries(data[1], data[2], nil, data[3])

				if (data[4] > max) then
					max = data[4]
				end
				if (data [5] > max_time) then
					max_time = data [5]
				end
			end
		end

		f:SetScale(max)
		f:SetTime (max_time)

	elseif (boxType == "overlay") then
		chart_panel_overlay_reset (f)

		for index, box in ipairs(f.BoxLabels) do
			if (box.type == boxType and box.showing and box.enabled) then
				f:AddOverlay(box.index)
			end
		end
	end
end

local create_box = function(self, next_box)
	local thisbox = {}
	self.BoxLabels [next_box] = thisbox

	local box = detailsFramework:NewImage(self.Graphic, nil, 16, 16, "border")
	local text = detailsFramework:NewLabel(self.Graphic)

	local border = detailsFramework:NewImage(self.Graphic, [[Interface\DialogFrame\UI-DialogBox-Gold-Corner]], 30, 30, "artwork")
	border:SetPoint("center", box, "center", -3, -4)
	border:SetTexture([[Interface\DialogFrame\UI-DialogBox-Gold-Corner]])

	local checktexture = detailsFramework:NewImage(self.Graphic, [[Interface\Buttons\UI-CheckBox-Check]], 18, 18, "overlay")
	checktexture:SetPoint("center", box, "center", 0, -1)
	checktexture:SetTexture([[Interface\Buttons\UI-CheckBox-Check]])

	thisbox.box = box
	thisbox.text = text
	thisbox.border = border
	thisbox.check = checktexture
	thisbox.enabled = true

	local button = CreateFrame("button", nil, self.Graphic, "BackdropTemplate")
	button:SetSize(20, 20)
	button:SetScript("OnClick", function()
		chart_panel_enable_line (self, thisbox)
	end)
	button:SetPoint("topleft", box.widget or box, "topleft", 0, 0)
	button:SetPoint("bottomright", box.widget or box, "bottomright", 0, 0)

	button:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true})
	button:SetBackdropColor(0, 0, 0, 0.0)
	button:SetBackdropBorderColor(0, 0, 0, 1)

	thisbox.button = button

	thisbox.box:SetPoint("right", text, "left", -4, 0)

	if (next_box == 1) then
		thisbox.text:SetPoint("topright", self, "topright", -35, -16)
	else
		thisbox.text:SetPoint("right", self.BoxLabels [next_box-1].box, "left", -17, 0)
	end

	return thisbox

end

local realign_labels = function(self)
	if (not self.ShowHeader) then
		for _, box in ipairs(self.BoxLabels) do
			box.check:Hide()
			box.button:Hide()
			box.border:Hide()
			box.box:Hide()
			box.text:Hide()
		end
		return
	end

	local width = self:GetWidth() - 108

	local first_box = self.BoxLabels [1]
	first_box.text:SetPoint("topright", self, "topright", -35, -16)

	local line_width = first_box.text:GetStringWidth() + 26

	for i = 2, #self.BoxLabels do

		local box = self.BoxLabels [i]

		if (box.box:IsShown()) then

			line_width = line_width + box.text:GetStringWidth() + 26

			if (line_width > width) then
				line_width = box.text:GetStringWidth() + 26
				box.text:SetPoint("topright", self, "topright", -35, -40)
			else
				box.text:SetPoint("right", self.BoxLabels [i-1].box, "left", -27, 0)
			end
		else
			break
		end
	end

	if (self.HeaderOnlyIndicator) then
		for _, box in ipairs(self.BoxLabels) do
				box.check:Hide()
			box.button:Hide()
		end
		return
	end
end

local chart_panel_add_label = function(self, color, name, type, number)
	local next_box = self.BoxLabelsAmount
	local thisbox = self.BoxLabels [next_box]

	if (not thisbox) then
		thisbox = create_box (self, next_box)
	end

	self.BoxLabelsAmount = self.BoxLabelsAmount + 1

	thisbox.type = type
	thisbox.index = number

	thisbox.box:SetColorTexture(unpack(color))
	thisbox.text:SetText(name)

	thisbox.check:Show()
	thisbox.button:Show()
	thisbox.border:Hide()
	thisbox.box:Show()
	thisbox.text:Show()

	thisbox.showing = true
	thisbox.enabled = true

	realign_labels(self)
end

local line_default_color = {1, 1, 1}
local draw_overlay = function(self, this_overlay, overlayData, color)

	local pixel = self.Graphic:GetWidth() / self.TimeScale
	local index = 1
	local r, g, b, a = unpack(color or line_default_color)

	for i = 1, #overlayData, 2 do
		local aura_start = overlayData [i]
		local aura_end = overlayData [i+1]

		local this_block = this_overlay [index]
		if (not this_block) then
			this_block = self.Graphic:CreateTexture(nil, "border")
			tinsert(this_overlay, this_block)
		end
		this_block:SetHeight(self.Graphic:GetHeight())

		this_block:SetPoint("left", self.Graphic, "left", pixel * aura_start, 0)
		if (aura_end) then
			this_block:SetWidth((aura_end-aura_start)*pixel)
		else
			--malformed table
			this_block:SetWidth(pixel*5)
		end

		this_block:SetColorTexture(r, g, b, a or 0.25)
		this_block:Show()

		index = index + 1
	end
end

local chart_panel_add_overlay = function(self, overlayData, color, name, icon)
	if (not self.TimeScale) then
		error("Use SetTime (time) before adding an overlay.")
	end

	if (type(overlayData) == "number") then
		local overlay_index = overlayData
		draw_overlay (self, self.Overlays [self.OverlaysAmount], self.OData [overlay_index][1], self.OData [overlay_index][2])
	else
		local this_overlay = self.Overlays [self.OverlaysAmount]
		if (not this_overlay) then
			this_overlay = {}
			tinsert(self.Overlays, this_overlay)
		end

		draw_overlay (self, this_overlay, overlayData, color)

		tinsert(self.OData, {overlayData, color or line_default_color})
		if (name and self.HeaderShowOverlays) then
			self:AddLabel (color or line_default_color, name, "overlay", #self.OData)
		end
	end

	self.OverlaysAmount = self.OverlaysAmount + 1
end

-- Define the tricube weight function
function calc_cubeweight (i, j, d)
    local w = ( 1 - math.abs((j-i)/d)^3)^3
    if w < 0 then
        w = 0
    end
    return w
end

local calc_lowess_smoothing = function(self, data, bandwidth)
	local length = #data
	local newData = {}

	for i = 1, length do
		local A = 0
		local B = 0
		local C = 0
		local D = 0
		local E = 0

		-- Calculate span of values to be included in the regression
		local jmin = floor(i-bandwidth/2)
		local jmax = ceil (i+bandwidth/2)
		if jmin < 1 then
			jmin = 1
		end
		if jmax > length then
			jmax = length
		end

		-- For all the values in the span, compute the weight and then the linear fit

		for j = jmin, jmax do
			w = calc_cubeweight (i, j, bandwidth/2)
			x = j
			y = data [j]

			A = A + w*x
			B = B + w*y
			C = C + w*x^2
			D = D + w*x*y
			E = E + w
		end

		-- Calculate a (slope) and b (offset) for the linear fit
		local a = (A*B-D*E)/(A^2 - C*E)
		local b = (A*D-B*C)/(A^2 - C*E)

		-- Calculate the smoothed value by the formula y=a*x+b (x <- i)
		newData [i] = a*i+b
	end
	return newData
end

local calc_stddev = function(self, data)
	local total = 0
	for i = 1, #data do
		total = total + data[i]
	end
	local mean = total / #data

	local totalDistance = 0
	for i = 1, #data do
		totalDistance = totalDistance + ((data[i] - mean) ^ 2)
	end

	local deviation = math.sqrt (totalDistance / #data)
	return deviation
end

local SMA_table = {}
local SMA_max = 0
local reset_SMA = function()
	table.wipe(SMA_table)
	SMA_max = 0
end

local calc_SMA
calc_SMA = function(a, b, ...)
	if (b) then
		return calc_SMA (a + b, ...)
	else
		return a
	end
end

local do_SMA = function(value, max_value)
	if (#SMA_table == 10) then
		tremove(SMA_table, 1)
	end

	SMA_table [#SMA_table + 1] = value

	local new_value = calc_SMA (unpack(SMA_table)) / #SMA_table

	if (new_value > SMA_max) then
		SMA_max = new_value
		return new_value, SMA_max
	else
		return new_value
	end
end

local chart_panel_onresize = function(self)
	local width, height = self:GetSize()
	local spacement = width - 78 - 60
	spacement = spacement / 16

	for i = 1, 17 do
		local label = self.TimeLabels [i]
		label:SetPoint("bottomleft", self, "bottomleft", 78 + ((i-1)*spacement), self.TimeLabelsHeight)
		label.line:SetHeight(height - 45)
	end

	local spacement = (self.Graphic:GetHeight()) / 8
	for i = 1, 8 do
		self ["dpsamt"..i]:SetPoint("TOPLEFT", self, "TOPLEFT", 27, -25 + (-(spacement* (i-1))) )
		self ["dpsamt"..i].line:SetWidth(width-20)
	end

	self.Graphic:SetSize(width - 135, height - 67)
	self.Graphic:SetPoint("topleft", self, "topleft", 108, -35)
end

local chart_panel_add_data = function(self, graphicData, color, name, elapsedTime, lineTexture, smoothLevel, firstIndex)
	local chartPanel = self --chartPanel from the framework CreateChartPanel
	local LibGraphChartFrame = self.Graphic

	local builtData = {}
	local maxValue = graphicData.max_value
	local scaleWidth = 1 / LibGraphChartFrame:GetWidth()
	local content = graphicData

	--smooth the start and end of the chart
	tinsert(content, 1, 0)
	tinsert(content, 1, 0)
	tinsert(content, #content+1, 0)
	tinsert(content, #content+1, 0)

	local index = 3
	local graphMaxDps = math.max(LibGraphChartFrame.max_value, maxValue)

	--do smoothness progress
	if (not smoothLevel) then
		while (index <= #content-2) do
			local value = (content[index-2] + content[index-1] + content[index] + content[index+1] + content[index+2]) / 5 --normalize
			builtData[#builtData+1] = {scaleWidth * (index-2), value / graphMaxDps} -- x and y coords
			index = index + 1
		end

	elseif (smoothLevel == "SHORT") then
		while (index <= #content-2) do
			local value = (content[index] + content[index+1]) / 2
			builtData [#builtData+1] = {scaleWidth*(index-2), value}
			builtData [#builtData+1] = {scaleWidth*(index-2), value}
			index = index + 2
		end

	elseif (smoothLevel == "SMA") then
		reset_SMA()
		while (index <= #content-2) do
			local value, is_new_max_value = do_SMA(content[index], maxValue)
			if (is_new_max_value) then
				maxValue = is_new_max_value
			end
			builtData [#builtData+1] = {scaleWidth * (index-2), value} -- x and y coords
			index = index + 1
		end

	elseif (smoothLevel == -1) then
		while (index <= #content-2) do
			local current = content[index]

			local minus_2 = content[index-2] * 0.6
			local minus_1 = content[index-1] * 0.8
			local plus_1 = content[index+1] * 0.8
			local plus_2 = content[index+2] * 0.6

			local value = (current + minus_2 + minus_1 + plus_1 + plus_2) / 5 --normalize
			builtData [#builtData+1] = {scaleWidth * (index-2), value / graphMaxDps} -- x and y coords
			index = index + 1
		end

	elseif (smoothLevel == 1) then
		index = 2
		while (index <= #content-1) do
			local value = (content[index-1]+content[index]+content[index+1])/3 --normalize
			builtData [#builtData+1] = {scaleWidth*(index-1), value/graphMaxDps} -- x and y coords
			index = index + 1
		end

	elseif (smoothLevel == 2) then
		index = 1
		while (index <= #content) do
			local value = content[index] --do not normalize
			builtData [#builtData+1] = {scaleWidth*(index), value/graphMaxDps} -- x and y coords
			index = index + 1
		end
	end

	tremove(content, 1)
	tremove(content, 1)
	tremove(content, #graphicData)
	tremove(content, #graphicData)

	if (maxValue > LibGraphChartFrame.max_value) then
		--normalize previous data
		if (LibGraphChartFrame.max_value > 0) then
			local normalizePercent = LibGraphChartFrame.max_value / maxValue
			for dataIndex, Data in ipairs(LibGraphChartFrame.Data) do
				local Points = Data.Points
				for i = 1, #Points do
					Points[i][2] = Points[i][2] * normalizePercent
				end
			end
		end

		LibGraphChartFrame.max_value = maxValue
		chartPanel:SetScale(maxValue)
	end

	tinsert(chartPanel.GData, {builtData, color or line_default_color, lineTexture, maxValue, elapsedTime})
	if (name) then
		chartPanel:AddLabel(color or line_default_color, name, "graphic", #chartPanel.GData)
	end

	local newLineTexture = "Interface\\AddOns\\Details\\Libs\\LibGraph-2.0\\line"

	if (firstIndex) then
		table.insert(LibGraphChartFrame.Data, 1, {Points = builtData, Color = color or line_default_color, lineTexture = newLineTexture, ElapsedTime = elapsedTime})
		LibGraphChartFrame.NeedsUpdate = true
	else
		LibGraphChartFrame:AddDataSeries(builtData, color or line_default_color, nil, newLineTexture)
		LibGraphChartFrame.Data[#LibGraphChartFrame.Data].ElapsedTime = elapsedTime
	end

	local maxTime = 0
	for _, data in ipairs(LibGraphChartFrame.Data) do
		if (data.ElapsedTime > maxTime) then
			maxTime = data.ElapsedTime
		end
	end

	chartPanel:SetTime(maxTime)
	chart_panel_onresize(chartPanel)
end


local chart_panel_vlines_on = function(self)
	for i = 1, 17 do
		local label = self.TimeLabels[i]
		label.line:Show()
	end
end

local chart_panel_vlines_off = function(self)
	for i = 1, 17 do
		local label = self.TimeLabels[i]
		label.line:Hide()
	end
end

local chart_panel_set_title = function(self, title)
	self.chart_title.text = title
end

local chart_panel_mousedown = function(self, button)
	if (button == "LeftButton" and self.can_move) then
		if (not self.isMoving) then
			self:StartMoving()
			self.isMoving = true
		end
	elseif (button == "RightButton" and not self.no_right_click_close) then
		if (not self.isMoving) then
			self:Hide()
		end
	end
end

local chart_panel_mouseup = function(self, button)
	if (button == "LeftButton" and self.isMoving) then
		self:StopMovingOrSizing()
		self.isMoving = nil
	end
end

local chart_panel_hide_close_button = function(self)
	self.CloseButton:Hide()
end

local chart_panel_right_click_close = function(self, value)
	if (type(value) == "boolean") then
		if (value) then
			self.no_right_click_close = nil
		else
			self.no_right_click_close = true
		end
	end
end

function detailsFramework:CreateChartPanel(parent, width, height, name)
	if (not name) then
		name = "DFPanel" .. detailsFramework.PanelCounter
		detailsFramework.PanelCounter = detailsFramework.PanelCounter + 1
	end

	parent = parent or UIParent
	width = width or 800
	height = height or 500

	local chartFrame = CreateFrame("frame", name, parent, "BackdropTemplate")
	chartFrame:SetSize(width or 500, height or 400)
	chartFrame:EnableMouse(true)
	chartFrame:SetMovable(true)

	chartFrame:SetScript("OnMouseDown", chart_panel_mousedown)
	chartFrame:SetScript("OnMouseUp", chart_panel_mouseup)

	chartFrame:SetBackdrop(chart_panel_backdrop)
	chartFrame:SetBackdropColor(.3, .3, .3, .3)

	local closeButton = CreateFrame("Button", nil, chartFrame, "UIPanelCloseButton", "BackdropTemplate")
	closeButton:SetWidth(32)
	closeButton:SetHeight(32)
	closeButton:SetPoint("TOPRIGHT",  chartFrame, "TOPRIGHT", -3, -7)
	closeButton:SetFrameLevel(chartFrame:GetFrameLevel()+1)
	closeButton:SetAlpha(0.9)
	chartFrame.CloseButton = closeButton

	local title = detailsFramework:NewLabel(chartFrame, nil, "$parentTitle", "chart_title", "Chart!", nil, 20, {1, 1, 0})
	title:SetPoint("topleft", chartFrame, "topleft", 110, -13)

	chartFrame.Overlays = {}
	chartFrame.OverlaysAmount = 1

	chartFrame.BoxLabels = {}
	chartFrame.BoxLabelsAmount = 1

	chartFrame.ShowHeader = true
	chartFrame.HeaderOnlyIndicator = false
	chartFrame.HeaderShowOverlays = true

	--graphic
		local g = LibStub:GetLibrary("LibGraph-2.0"):CreateGraphLine (name .. "Graphic", chartFrame, "topleft","topleft", 108, -35, width - 120, height - 67)
		g:SetXAxis (-1,1)
		g:SetYAxis (-1,1)
		g:SetGridSpacing (false, false)
		g:SetGridColor ({0.5,0.5,0.5,0.3})
		g:SetAxisDrawing (false,false)
		g:SetAxisColor({1.0,1.0,1.0,1.0})
		g:SetAutoScale (true)
		g:SetLineTexture ("smallline")
		g:SetBorderSize ("right", 0.001)
		g:SetBorderSize ("left", 0.000)
		g:SetBorderSize ("top", 0.002)
		g:SetBorderSize ("bottom", 0.001)
		g.VerticalLines = {}
		g.max_value = 0

		g:SetLineTexture ("line")

		chartFrame.Graphic = g
		chartFrame.GData = {}
		chartFrame.OData = {}
		chartFrame.ChartFrames = {}

	--div lines
		for i = 1, 8, 1 do
			local line = g:CreateTexture(nil, "overlay")
			line:SetColorTexture(1, 1, 1, .05)
			line:SetWidth(670)
			line:SetHeight(1.1)

			local s = chartFrame:CreateFontString(nil, "overlay", "GameFontHighlightSmall")
			chartFrame ["dpsamt"..i] = s
			s:SetText("100k")
			s:SetPoint("topleft", chartFrame, "topleft", 27, -61 + (-(24.6*i)))

			line:SetPoint("topleft", s, "bottom", -27, 0)
			line:SetPoint("topright", g, "right", 0, 0)
			s.line = line
		end

	--create time labels and the bottom texture to use as a background to these labels
		chartFrame.TimeLabels = {}
		chartFrame.TimeLabelsHeight = 16

		for i = 1, 17 do
			local timeString = chartFrame:CreateFontString(nil, "overlay", "GameFontHighlightSmall")
			timeString:SetText("00:00")
			timeString:SetPoint("bottomleft", chartFrame, "bottomleft", 78 + ((i-1)*36), chartFrame.TimeLabelsHeight)
			chartFrame.TimeLabels [i] = timeString

			local line = chartFrame:CreateTexture(nil, "border")
			line:SetSize(1, height-45)
			line:SetColorTexture(1, 1, 1, .1)
			line:SetPoint("bottomleft", timeString, "topright", 0, -10)
			line:Hide()
			timeString.line = line
		end

		local bottom_texture = detailsFramework:NewImage(chartFrame, nil, 702, 25, "background", nil, nil, "$parentBottomTexture")
		bottom_texture:SetColorTexture(.1, .1, .1, .7)
		bottom_texture:SetPoint("topright", g, "bottomright", 0, 0)
		bottom_texture:SetPoint("bottomleft", chartFrame, "bottomleft", 8, 12)



	chartFrame.SetTime = chart_panel_align_timelabels
	chartFrame.EnableVerticalLines = chart_panel_vlines_on
	chartFrame.DisableVerticalLines = chart_panel_vlines_off
	chartFrame.SetTitle = chart_panel_set_title
	chartFrame.SetScale = chart_panel_set_scale
	chartFrame.Reset = chart_panel_reset
	chartFrame.AddLine = chart_panel_add_data
	chartFrame.CanMove = chart_panel_can_move
	chartFrame.AddLabel = chart_panel_add_label
	chartFrame.AddOverlay = chart_panel_add_overlay
	chartFrame.HideCloseButton = chart_panel_hide_close_button
	chartFrame.RightClickClose = chart_panel_right_click_close
	chartFrame.CalcStdDev = calc_stddev
	chartFrame.CalcLowessSmoothing = calc_lowess_smoothing

	chartFrame:SetScript("OnSizeChanged", chart_panel_onresize)
	chart_panel_onresize(chartFrame)

	return chartFrame
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~gframe
local gframe_on_enter_line = function(self)
	self:SetBackdropColor(0, 0, 0, 0)

	local parent = self:GetParent()
	local ball = self.ball
	ball:SetBlendMode("ADD")

	local on_enter = parent._onenter_line
	if (on_enter) then
		return on_enter (self, parent)
	end
end

local gframe_on_leave_line = function(self)
	self:SetBackdropColor(0, 0, 0, .6)

	local parent = self:GetParent()
	local ball = self.ball
	ball:SetBlendMode("BLEND")

	local on_leave = parent._onleave_line
	if (on_leave) then
		return on_leave (self, parent)
	end
end

local gframe_create_line = function(self)
	local index = #self._lines+1

	local f = CreateFrame("frame", nil, self, "BackdropTemplate")
	self._lines [index] = f
	f.id = index
	f:SetScript("OnEnter", gframe_on_enter_line)
	f:SetScript("OnLeave", gframe_on_leave_line)

	f:SetWidth(self._linewidth)

	if (index == 1) then
		f:SetPoint("topleft", self, "topleft")
		f:SetPoint("bottomleft", self, "bottomleft")
	else
		local previous_line = self._lines [index-1]
		f:SetPoint("topleft", previous_line, "topright")
		f:SetPoint("bottomleft", previous_line, "bottomright")
	end

	local t = f:CreateTexture(nil, "background")
	t:SetWidth(1)
	t:SetPoint("topright", f, "topright")
	t:SetPoint("bottomright", f, "bottomright")
	t:SetColorTexture(1, 1, 1, .1)
	f.grid = t

	local b = f:CreateTexture(nil, "overlay")
	b:SetTexture([[Interface\COMMON\Indicator-Yellow]])
	b:SetSize(16, 16)
	f.ball = b
	local anchor = CreateFrame("frame", nil, f, "BackdropTemplate")
	anchor:SetAllPoints(b)
	b.tooltip_anchor = anchor

	local spellicon = f:CreateTexture(nil, "artwork")
	spellicon:SetPoint("bottom", b, "bottom", 0, 10)
	spellicon:SetSize(16, 16)
	f.spellicon = spellicon

	local text = f:CreateFontString(nil, "overlay", "GameFontNormal")
	local textBackground = f:CreateTexture(nil, "artwork")
	textBackground:SetSize(30, 16)
	textBackground:SetColorTexture(0, 0, 0, 0.5)
	textBackground:SetPoint("bottom", f.ball, "top", 0, -6)
	text:SetPoint("center", textBackground, "center")
	detailsFramework:SetFontSize(text, 10)
	f.text = text
	f.textBackground = textBackground

	local timeline = f:CreateFontString(nil, "overlay", "GameFontNormal")
	timeline:SetPoint("bottomright", f, "bottomright", -2, 0)
	detailsFramework:SetFontSize(timeline, 8)
	f.timeline = timeline

	return f
end

local gframe_getline = function(self, index)
	local line = self._lines [index]
	if (not line) then
		line = gframe_create_line (self)
	end
	return line
end

local gframe_reset = function(self)
	for i, line in ipairs(self._lines) do
		line:Hide()
	end
	if (self.GraphLib_Lines_Used) then
		for i = #self.GraphLib_Lines_Used, 1, -1 do
			local line = tremove(self.GraphLib_Lines_Used)
			tinsert(self.GraphLib_Lines, line)
			line:Hide()
		end
	end
end

local gframe_update = function(self, lines)
	local g = LibStub:GetLibrary ("LibGraph-2.0")
	local h = self:GetHeight()/100
	local amtlines = #lines
	local linewidth = self._linewidth

	local max_value = 0
	for i = 1, amtlines do
		if (lines [i].value > max_value) then
			max_value = lines [i].value
		end
	end

	self.MaxValue = max_value

	local o = 1
	local lastvalue = self:GetHeight()/2
	max_value = math.max(max_value, 0.0000001)

	for i = 1, min (amtlines, self._maxlines) do

		local data = lines [i]

		local pvalue = data.value / max_value * 100
		if (pvalue > 98) then
			pvalue = 98
		end
		pvalue = pvalue * h

		g:DrawLine (self, (o-1)*linewidth, lastvalue, o*linewidth, pvalue, linewidth, {1, 1, 1, 1}, "overlay")
		lastvalue = pvalue

		local line = self:GetLine (i)
		line:Show()
		line.ball:Show()

		line.ball:SetPoint("bottomleft", self, "bottomleft", (o*linewidth)-8, pvalue-8)
		line.spellicon:SetTexture(nil)
		line.timeline:SetText(data.text)
		line.timeline:Show()

		if (data.utext) then
			line.text:Show()
			line.textBackground:Show()
			line.text:SetText(data.utext)
		else
			line.text:Hide()
			line.textBackground:Hide()
		end

		line.data = data

		o = o + 1
	end
end

function detailsFramework:CreateGFrame(parent, width, height, lineWidth, onEnter, onLeave, member, name)
	local newGraphicFrame = CreateFrame("frame", name, parent, "BackdropTemplate")
	newGraphicFrame:SetSize(width or 450, height or 150)

	if (member) then
		parent[member] = newGraphicFrame
	end

	newGraphicFrame.CreateLine = gframe_create_line
	newGraphicFrame.GetLine = gframe_getline
	newGraphicFrame.Reset = gframe_reset
	newGraphicFrame.UpdateLines = gframe_update

	newGraphicFrame.MaxValue = 0

	newGraphicFrame._lines = {}

	newGraphicFrame._onenter_line = onEnter
	newGraphicFrame._onleave_line = onLeave

	newGraphicFrame._linewidth = lineWidth or 50
	newGraphicFrame._maxlines = floor(newGraphicFrame:GetWidth() / newGraphicFrame._linewidth)

	return newGraphicFrame
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--options tabs and buttons -dot

function detailsFramework:FindHighestParent(self)
	local highestParent
	if (self:GetParent() == UIParent) then
		highestParent = self
	end

	if (not highestParent) then
		highestParent = self
		for i = 1, 6 do
			local parent = highestParent:GetParent()
			if (parent == UIParent) then
				break
			else
				highestParent = parent
			end
		end
	end

	return highestParent
end

detailsFramework.TabContainerFunctions = {}

local button_tab_template = detailsFramework.table.copy({}, detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"))
button_tab_template.backdropbordercolor = nil

detailsFramework.TabContainerFunctions.CreateUnderlineGlow = function(button)
	local selectedGlow = button:CreateTexture(nil, "background", nil, -4)
	selectedGlow:SetPoint("topleft", button.widget, "bottomleft", -7, 0)
	selectedGlow:SetPoint("topright", button.widget, "bottomright", 7, 0)
	selectedGlow:SetTexture([[Interface\BUTTONS\UI-Panel-Button-Glow]])
	selectedGlow:SetTexCoord(0, 95/128, 30/64, 38/64)
	selectedGlow:SetBlendMode("ADD")
	selectedGlow:SetHeight(8)
	selectedGlow:SetAlpha(.75)
	selectedGlow:Hide()
	button.selectedUnderlineGlow = selectedGlow
end

detailsFramework.TabContainerFunctions.OnMouseDown = function(self, button)
	--search for UIParent
	local f = detailsFramework:FindHighestParent (self)
	local container = self:GetParent()

	if (button == "LeftButton") then
		if (not f.IsMoving and f:IsMovable()) then
			f:StartMoving()
			f.IsMoving = true
		end
	elseif (button == "RightButton") then
		if (not f.IsMoving and container.IsContainer) then
			if (self.IsFrontPage) then
				if (container.CanCloseWithRightClick) then
					if (f.CloseFunction) then
						f:CloseFunction()
					else
						f:Hide()
					end
				end
			else
				--goes back to front page
				detailsFramework.TabContainerFunctions.SelectIndex (self, _, 1)
			end
		end
	end
end

detailsFramework.TabContainerFunctions.OnMouseUp = function(self, button)
	local f = detailsFramework:FindHighestParent (self)
	if (f.IsMoving) then
		f:StopMovingOrSizing()
		f.IsMoving = false
	end
end

detailsFramework.TabContainerFunctions.SelectIndex = function(self, fixedParam, menuIndex)
	local mainFrame = self.AllFrames and self or self.mainFrame or self:GetParent()

	for i = 1, #mainFrame.AllFrames do
		mainFrame.AllFrames[i]:Hide()
		if (mainFrame.ButtonNotSelectedBorderColor) then
			mainFrame.AllButtons[i]:SetBackdropBorderColor(unpack(mainFrame.ButtonNotSelectedBorderColor))
		end
		if (mainFrame.AllButtons[i].selectedUnderlineGlow) then
			mainFrame.AllButtons[i].selectedUnderlineGlow:Hide()
		end
	end

	mainFrame.AllFrames[menuIndex]:Show()
	if mainFrame.AllFrames[menuIndex].RefreshOptions then
		mainFrame.AllFrames[menuIndex]:RefreshOptions()
	end
	if (mainFrame.ButtonSelectedBorderColor) then
		mainFrame.AllButtons[menuIndex]:SetBackdropBorderColor(unpack(mainFrame.ButtonSelectedBorderColor))
	end
	if (mainFrame.AllButtons[menuIndex].selectedUnderlineGlow) then
		mainFrame.AllButtons[menuIndex].selectedUnderlineGlow:Show()
	end
	mainFrame.CurrentIndex = menuIndex

	if (mainFrame.hookList.OnSelectIndex) then
		detailsFramework:QuickDispatch(mainFrame.hookList.OnSelectIndex, mainFrame, mainFrame.AllButtons[menuIndex])
	end
end

detailsFramework.TabContainerFunctions.SetIndex = function(self, index)
	self.CurrentIndex = index
end

local tab_container_on_show = function(self)
	local index = self.CurrentIndex
	self.SelectIndex (self.AllButtons[index], nil, index)
end

function detailsFramework:CreateTabContainer (parent, title, frame_name, frameList, options_table, hookList)
	local options_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")
	local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")
	local options_switch_template = detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE")
	local options_slider_template = detailsFramework:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE")
	local options_button_template = detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE")

	options_table = options_table or {}
	local parentFrameWidth = parent:GetWidth()
	local y_offset = options_table.y_offset or 0
	local buttonWidth = options_table.button_width or 160
	local buttonHeight = options_table.button_height or 20
	local buttonAnchorX = options_table.button_x or 230
	local buttonAnchorY = options_table.button_y or -32
	local button_text_size = options_table.button_text_size or 10
	local containerWidthOffset = options_table.container_width_offset or 0

	local mainFrame = CreateFrame("frame", frame_name, parent.widget or parent, "BackdropTemplate")
	mainFrame:SetAllPoints()
	detailsFramework:Mixin(mainFrame, detailsFramework.TabContainerFunctions)
	mainFrame.hookList = hookList or {}

	local mainTitle = detailsFramework:CreateLabel(mainFrame, title, 24, "white")
	mainTitle:SetPoint("topleft", mainFrame, "topleft", 10, -30 + y_offset)

	mainFrame:SetFrameLevel(200)

	mainFrame.AllFrames = {}
	mainFrame.AllButtons = {}
	mainFrame.CurrentIndex = 1
	mainFrame.IsContainer = true
	mainFrame.ButtonSelectedBorderColor = options_table.button_selected_border_color or {1, 1, 0, 1}
	mainFrame.ButtonNotSelectedBorderColor = options_table.button_border_color or {0, 0, 0, 0}

	if (options_table.right_click_interact ~= nil) then
		mainFrame.CanCloseWithRightClick = options_table.right_click_interact
	else
		mainFrame.CanCloseWithRightClick = true
	end

	for i, frame in ipairs(frameList) do
		local f = CreateFrame("frame", "$parent" .. frame.name, mainFrame, "BackdropTemplate")
		f:SetAllPoints()
		f:SetFrameLevel(210)
		f:Hide()

		local title = detailsFramework:CreateLabel(f, frame.title, 16, "silver")
		title:SetPoint("topleft", mainTitle, "bottomleft", 0, 0)
		f.titleText = title

		local tabButton = detailsFramework:CreateButton(mainFrame, detailsFramework.TabContainerFunctions.SelectIndex, buttonWidth, buttonHeight, frame.title, i, nil, nil, nil, "$parentTabButton" .. frame.name, false, button_tab_template)
		PixelUtil.SetSize(tabButton, buttonWidth, buttonHeight)
		tabButton:SetFrameLevel(220)
		tabButton.textsize = button_text_size
		tabButton.mainFrame = mainFrame
		detailsFramework.TabContainerFunctions.CreateUnderlineGlow (tabButton)

		local right_click_to_back
		if (i == 1 or options_table.rightbutton_always_close) then
			right_click_to_back = detailsFramework:CreateLabel(f, "right click to close", 10, "gray")
			right_click_to_back:SetPoint("bottomright", f, "bottomright", -1, options_table.right_click_y or 0)
			if (options_table.close_text_alpha) then
				right_click_to_back:SetAlpha(options_table.close_text_alpha)
			end
			f.IsFrontPage = true
		else
			right_click_to_back = detailsFramework:CreateLabel(f, "right click to go back to main menu", 10, "gray")
			right_click_to_back:SetPoint("bottomright", f, "bottomright", -1, options_table.right_click_y or 0)
			if (options_table.close_text_alpha) then
				right_click_to_back:SetAlpha(options_table.close_text_alpha)
			end
		end

		if (options_table.hide_click_label) then
			right_click_to_back:Hide()
		end

		f:SetScript("OnMouseDown", detailsFramework.TabContainerFunctions.OnMouseDown)
		f:SetScript("OnMouseUp", detailsFramework.TabContainerFunctions.OnMouseUp)

		tinsert(mainFrame.AllFrames, f)
		tinsert(mainFrame.AllButtons, tabButton)
	end

	--order buttons
	local x = buttonAnchorX
	local y = buttonAnchorY
	local spaceBetweenButtons = 3

	local allocatedSpaceForButtons = parentFrameWidth - ((#frameList - 2) * spaceBetweenButtons) - buttonAnchorX + containerWidthOffset
	local amountButtonsPerRow = floor(allocatedSpaceForButtons / buttonWidth)

	mainFrame.AllButtons[1]:SetPoint("topleft", mainTitle, "topleft", x, y)
	x = x + buttonWidth + 2

	for i = 2, #mainFrame.AllButtons do
		local button = mainFrame.AllButtons[i]
		PixelUtil.SetPoint(button, "topleft", mainTitle, "topleft", x, y)
		x = x + buttonWidth + 2

		if (i % amountButtonsPerRow == 0) then
			x = buttonAnchorX
			y = y - buttonHeight - 1
		end
	end

	--when show the frame, reset to the current internal index
	mainFrame:SetScript("OnShow", tab_container_on_show)
	--select the first frame
	mainFrame.SelectIndex (mainFrame.AllButtons[1], nil, 1)

	return mainFrame
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~right ~click to ~close

function detailsFramework:CreateRightClickToClose(parent, xOffset, yOffset, color, fontSize)
	--default values
	xOffset = xOffset or 0
	yOffset = yOffset or 0
	color = color or "white"
	fontSize = fontSize or 10

	local label = detailsFramework:CreateLabel(parent, "right click to close", fontSize, color)
	label:SetPoint("bottomright", parent, "bottomright", -4 + xOffset, 5 + yOffset)

	return label
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~listbox

local simple_list_box_ResetWidgets = function(self)
	for _, widget in ipairs(self.widgets) do
		widget:Hide()
	end
	self.nextWidget = 1
end

local simple_list_box_onenter = function(self, capsule)
	self:GetParent().options.onenter (self, capsule, capsule.value)
end

local simple_list_box_onleave = function(self, capsule)
	self:GetParent().options.onleave (self, capsule, capsule.value)
	GameTooltip:Hide()
end

local simple_list_box_GetOrCreateWidget = function(self)
	local index = self.nextWidget
	local widget = self.widgets [index]
	if (not widget) then
		widget = detailsFramework:CreateButton(self, function()end, self.options.width, self.options.row_height, "", nil, nil, nil, nil, nil, nil, detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"))
		widget:SetHook("OnEnter", simple_list_box_onenter)
		widget:SetHook("OnLeave", simple_list_box_onleave)
		widget.textcolor = self.options.textcolor
		widget.textsize = self.options.text_size
		widget.onleave_backdrop = self.options.backdrop_color

		widget.XButton = detailsFramework:CreateButton(widget, function()end, 16, 16)
		widget.XButton:SetPoint("topright", widget.widget, "topright")
		widget.XButton:SetIcon ([[Interface\BUTTONS\UI-Panel-MinimizeButton-Up]], 16, 16, "overlay", nil, nil, 0, -4, 0, false)
		widget.XButton.icon:SetDesaturated(true)

		if (not self.options.show_x_button) then
			widget.XButton:Hide()
		end

		tinsert(self.widgets, widget)
	end
	self.nextWidget = self.nextWidget + 1
	return widget
end

local simple_list_box_RefreshWidgets = function(self)
	self:ResetWidgets()
	local amt = 0
	for value, _ in pairs(self.list_table) do
		local widget = self:GetOrCreateWidget()
		widget:SetPoint("topleft", self, "topleft", 1, -self.options.row_height * (self.nextWidget-2) - 4)
		widget:SetPoint("topright", self, "topright", -1, -self.options.row_height * (self.nextWidget-2) - 4)

		widget:SetClickFunction(self.func, value)

		if (self.options.show_x_button) then
			widget.XButton:SetClickFunction(self.options.x_button_func, value)
			widget.XButton.value = value
			widget.XButton:Show()
		else
			widget.XButton:Hide()
		end

		widget.value = value

		if (self.options.icon) then
			if (type(self.options.icon) == "string" or type(self.options.icon) == "number") then
				local coords = type(self.options.iconcoords) == "table" and self.options.iconcoords or {0, 1, 0, 1}
				widget:SetIcon (self.options.icon, self.options.row_height - 2, self.options.row_height - 2, "overlay", coords)

			elseif (type(self.options.icon) == "function") then
				local icon = self.options.icon (value)
				if (icon) then
					local coords = type(self.options.iconcoords) == "table" and self.options.iconcoords or {0, 1, 0, 1}
					widget:SetIcon (icon, self.options.row_height - 2, self.options.row_height - 2, "overlay", coords)
				end
			end
		else
			widget:SetIcon ("", self.options.row_height, self.options.row_height)
		end

		if (self.options.text) then
			if (type(self.options.text) == "function") then
				local text = self.options.text (value)
				if (text) then
					widget:SetText(text)
				else
					widget:SetText("")
				end
			else
				widget:SetText(self.options.text or "")
			end
		else
			widget:SetText("")
		end

		widget.value = value

		local r, g, b, a = detailsFramework:ParseColors(self.options.backdrop_color)
		widget:SetBackdropColor(r, g, b, a)

		widget:Show()
		amt = amt + 1
	end
	if (amt == 0) then
		self.EmptyLabel:Show()
	else
		self.EmptyLabel:Hide()
	end
end

local backdrop = {bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16, edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1}
local default_options = {
	height = 400,
	row_height = 16,
	width = 230,
	icon = false,
	text = "",
	text_size = 10,
	textcolor = "wheat",

	backdrop_color = {1, 1, 1, .5},
	panel_border_color = {0, 0, 0, 0.5},

	onenter = function(self, capsule)
		if (capsule) then
			capsule.textcolor = "white"
		end
	end,
	onleave = function(self, capsule)
		if (capsule) then
			capsule.textcolor = self:GetParent().options.textcolor
		end
		GameTooltip:Hide()
	end,
}

local simple_list_box_SetData = function(self, t)
	self.list_table = t
end

function detailsFramework:CreateSimpleListBox (parent, name, title, empty_text, list_table, onclick, options)
	local f = CreateFrame("frame", name, parent, "BackdropTemplate")

	f.ResetWidgets = simple_list_box_ResetWidgets
	f.GetOrCreateWidget = simple_list_box_GetOrCreateWidget
	f.Refresh = simple_list_box_RefreshWidgets
	f.SetData = simple_list_box_SetData
	f.nextWidget = 1
	f.list_table = list_table
	f.func = function(self, button, value)
		--onclick (value)
		detailsFramework:QuickDispatch(onclick, value)
		f:Refresh()
	end
	f.widgets = {}

	detailsFramework:ApplyStandardBackdrop(f)

	f.options = options or {}
	self.table.deploy(f.options, default_options)

	if (f.options.x_button_func) then
		local original_X_function = f.options.x_button_func
		f.options.x_button_func = function(self, button, value)
			detailsFramework:QuickDispatch(original_X_function, value)
			f:Refresh()
		end
	end

	f:SetBackdropBorderColor(unpack(f.options.panel_border_color))

	f:SetSize(f.options.width + 2, f.options.height)

	local name = detailsFramework:CreateLabel(f, title, 12, "silver")
	name:SetTemplate(detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE"))
	name:SetPoint("bottomleft", f, "topleft", 0, 2)
	f.Title = name

	local emptyLabel = detailsFramework:CreateLabel(f, empty_text, 12, "gray")
	emptyLabel:SetAlpha(.6)
	emptyLabel:SetSize(f.options.width-10, f.options.height)
	emptyLabel:SetPoint("center", 0, 0)
	emptyLabel:Hide()
	emptyLabel.align = "center"
	f.EmptyLabel = emptyLabel

	return f
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~scrollbox

function detailsFramework:CreateScrollBox (parent, name, refreshFunc, data, width, height, lineAmount, lineHeight, createLineFunc, autoAmount, noScroll)
	local scroll = CreateFrame("scrollframe", name, parent, "FauxScrollFrameTemplate, BackdropTemplate")

	detailsFramework:ApplyStandardBackdrop(scroll)

	scroll:SetSize(width, height)
	scroll.LineAmount = lineAmount
	scroll.LineHeight = lineHeight
	scroll.IsFauxScroll = true
	scroll.HideScrollBar = noScroll
	scroll.Frames = {}
	scroll.ReajustNumFrames = autoAmount
	scroll.CreateLineFunc = createLineFunc

	detailsFramework:Mixin(scroll, detailsFramework.SortFunctions)
	detailsFramework:Mixin(scroll, detailsFramework.ScrollBoxFunctions)

	scroll.refresh_func = refreshFunc
	scroll.data = data

	scroll:SetScript("OnVerticalScroll", scroll.OnVerticalScroll)
	scroll:SetScript("OnSizeChanged", detailsFramework.ScrollBoxFunctions.OnSizeChanged)

	return scroll
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~resizers

function detailsFramework:CreateResizeGrips (parent)
	if (parent) then
		local parentName = parent:GetName()

		local leftResizer = CreateFrame("button", parentName and parentName .. "LeftResizer" or nil, parent, "BackdropTemplate")
		local rightResizer = CreateFrame("button", parentName and parentName .. "RightResizer" or nil, parent, "BackdropTemplate")

		leftResizer:SetPoint("bottomleft", parent, "bottomleft")
		rightResizer:SetPoint("bottomright", parent, "bottomright")
		leftResizer:SetSize(16, 16)
		rightResizer:SetSize(16, 16)

		rightResizer:SetNormalTexture([[Interface\CHATFRAME\UI-ChatIM-SizeGrabber-Up]])
		rightResizer:SetHighlightTexture([[Interface\CHATFRAME\UI-ChatIM-SizeGrabber-Highlight]])
		rightResizer:SetPushedTexture([[Interface\CHATFRAME\UI-ChatIM-SizeGrabber-Down]])
		leftResizer:SetNormalTexture([[Interface\CHATFRAME\UI-ChatIM-SizeGrabber-Up]])
		leftResizer:SetHighlightTexture([[Interface\CHATFRAME\UI-ChatIM-SizeGrabber-Highlight]])
		leftResizer:SetPushedTexture([[Interface\CHATFRAME\UI-ChatIM-SizeGrabber-Down]])

		leftResizer:GetNormalTexture():SetTexCoord(1, 0, 0, 1)
		leftResizer:GetHighlightTexture():SetTexCoord(1, 0, 0, 1)
		leftResizer:GetPushedTexture():SetTexCoord(1, 0, 0, 1)

		return leftResizer, rightResizer
	end
end


---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~keybind


--------------------------------
--keybind frame ~key


local ignoredKeys = {
	["LSHIFT"] = true,
	["RSHIFT"] = true,
	["LCTRL"] = true,
	["RCTRL"] = true,
	["LALT"] = true,
	["RALT"] = true,
	["UNKNOWN"] = true,
}

local mouseKeys = {
	["LeftButton"] = "type1",
	["RightButton"] = "type2",
	["MiddleButton"] = "type3",
	["Button4"] = "type4",
	["Button5"] = "type5",
	["Button6"] = "type6",
	["Button7"] = "type7",
	["Button8"] = "type8",
	["Button9"] = "type9",
	["Button10"] = "type10",
	["Button11"] = "type11",
	["Button12"] = "type12",
	["Button13"] = "type13",
	["Button14"] = "type14",
	["Button15"] = "type15",
	["Button16"] = "type16",
}

local keysToMouse = {
	["type1"] = "LeftButton",
	["type2"] = "RightButton",
	["type3"] = "MiddleButton",
	["type4"] = "Button4",
	["type5"] = "Button5",
	["type6"] = "Button6",
	["type7"] = "Button7",
	["type8"] = "Button8",
	["type9"] = "Button9",
	["type10"] = "Button10",
	["type11"] = "Button11",
	["type12"] = "Button12",
	["type13"] = "Button13",
	["type14"] = "Button14",
	["type15"] = "Button15",
	["type16"] = "Button16",
}

local keybind_set_data = function(self, new_data_table)
	self.Data = new_data_table
	self.keybindScroll:UpdateScroll()
end

function detailsFramework:CreateKeybindBox (parent, name, data, callback, width, height, line_amount, line_height)

	local options_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")
	local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")
	local options_switch_template = detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE")
	local options_slider_template = detailsFramework:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE")
	local options_button_template = detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE")

	local SCROLL_ROLL_AMOUNT = line_amount

	--keybind set frame
	local new_keybind_frame = CreateFrame("frame", name, parent, "BackdropTemplate")
	new_keybind_frame:SetSize(width, height)

	-- keybind scrollframe
	local keybindScroll = CreateFrame("scrollframe", "$parentScrollFrame", new_keybind_frame, "FauxScrollFrameTemplate, BackdropTemplate")
	keybindScroll:SetSize(1019, 348)
	keybindScroll.Frames = {}
	new_keybind_frame.keybindScroll = keybindScroll

	--waiting the player to press a key
	new_keybind_frame.IsListening = false

	--check for valid data table
	if (type(data) ~= "table") then
		print("error: data must be a table. DF > CreateKeybindBox()")
		return
	end

	if (not next (data)) then
		--build data table for the character class
		local _, unitClass = UnitClass("player")
		if (unitClass) then
			local specIds = detailsFramework:GetClassSpecIDs(unitClass)
			if (specIds) then
				for _, specId in ipairs(specIds) do
					data [specId] = {}
				end
			end
		end
	end

	new_keybind_frame.Data = data
	new_keybind_frame.SetData = keybind_set_data

	new_keybind_frame.EditingSpec = detailsFramework:GetCurrentSpec()
	new_keybind_frame.CurrentKeybindEditingSet = new_keybind_frame.Data [new_keybind_frame.EditingSpec]

	local allSpecButtons = {}
	local switch_spec = function(self, button, specID)
		new_keybind_frame.EditingSpec = specID
		new_keybind_frame.CurrentKeybindEditingSet = new_keybind_frame.Data [specID]

		for _, button in ipairs(allSpecButtons) do
			button.selectedTexture:Hide()
		end
		self.MyObject.selectedTexture:Show()

		--feedback ao jogador uma vez que as keybinds podem ter o mesmo valor
		C_Timer.After(.04, function() new_keybind_frame:Hide() end)
		C_Timer.After(.06, function() new_keybind_frame:Show() end)

		--atualiza a scroll
		keybindScroll:UpdateScroll()
	end

	--choose which spec to use
	local spec1 = detailsFramework:CreateButton(new_keybind_frame, switch_spec, 160, 20, "Spec1 Placeholder Text", 1, _, _, "SpecButton1", _, 0, options_button_template, options_text_template)
	local spec2 = detailsFramework:CreateButton(new_keybind_frame, switch_spec, 160, 20, "Spec2 Placeholder Text", 1, _, _, "SpecButton2", _, 0, options_button_template, options_text_template)
	local spec3 = detailsFramework:CreateButton(new_keybind_frame, switch_spec, 160, 20, "Spec3 Placeholder Text", 1, _, _, "SpecButton3", _, 0, options_button_template, options_text_template)
	local spec4 = detailsFramework:CreateButton(new_keybind_frame, switch_spec, 160, 20, "Spec4 Placeholder Text", 1, _, _, "SpecButton4", _, 0, options_button_template, options_text_template)

	--format the button label and icon with the spec information
	local className, class = UnitClass("player")
	local i = 1
	local specIds = detailsFramework:GetClassSpecIDs(class)

	for index, specId in ipairs(specIds) do
		local button = new_keybind_frame ["SpecButton" .. index]
		local spec_id, spec_name, spec_description, spec_icon, spec_background, spec_role, spec_class = DetailsFramework.GetSpecializationInfoByID (specId)
		button.text = spec_name
		button:SetClickFunction(switch_spec, specId)
		button:SetIcon (spec_icon)
		button.specID = specId

		local selectedTexture = button:CreateTexture(nil, "background")
		selectedTexture:SetAllPoints()
		selectedTexture:SetColorTexture(1, 1, 1, 0.5)
		if (specId ~= new_keybind_frame.EditingSpec) then
			selectedTexture:Hide()
		end
		button.selectedTexture = selectedTexture

		tinsert(allSpecButtons, button)
		i = i + 1
	end

	local specsTitle = detailsFramework:CreateLabel(new_keybind_frame, "Config keys for spec:", 12, "silver")
	specsTitle:SetPoint("topleft", new_keybind_frame, "topleft", 10, mainStartY)

	keybindScroll:SetPoint("topleft", specsTitle.widget, "bottomleft", 0, -120)

	spec1:SetPoint("topleft", specsTitle, "bottomleft", 0, -10)
	spec2:SetPoint("topleft", specsTitle, "bottomleft", 0, -30)
	spec3:SetPoint("topleft", specsTitle, "bottomleft", 0, -50)
	if (class == "DRUID") then
		spec4:SetPoint("topleft", specsTitle, "bottomleft", 0, -70)
	end

	local enter_the_key = CreateFrame("frame", nil, new_keybind_frame, "BackdropTemplate")
	enter_the_key:SetFrameStrata("tooltip")
	enter_the_key:SetSize(200, 60)
	enter_the_key:SetBackdrop({bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16, edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1})
	enter_the_key:SetBackdropColor(0, 0, 0, 1)
	enter_the_key:SetBackdropBorderColor(1, 1, 1, 1)
	enter_the_key.text = detailsFramework:CreateLabel(enter_the_key, "- Press a keyboard key to bind.\n- Click to bind a mouse button.\n- Press escape to cancel.", 11, "orange")
	enter_the_key.text:SetPoint("center", enter_the_key, "center")
	enter_the_key:Hide()

	local registerKeybind = function(self, key)
		if (ignoredKeys [key]) then
			return
		end
		if (key == "ESCAPE") then
			enter_the_key:Hide()
			new_keybind_frame.IsListening = false
			new_keybind_frame:SetScript("OnKeyDown", nil)
			return
		end

		local bind = (IsShiftKeyDown() and "SHIFT-" or "") .. (IsControlKeyDown() and "CTRL-" or "") .. (IsAltKeyDown() and "ALT-" or "")
		bind = bind .. key

		--adiciona para a tabela de keybinds
		local keybind = new_keybind_frame.CurrentKeybindEditingSet [self.keybindIndex]
		keybind.key = bind

		new_keybind_frame.IsListening = false
		new_keybind_frame:SetScript("OnKeyDown", nil)

		enter_the_key:Hide()
		new_keybind_frame.keybindScroll:UpdateScroll()

		detailsFramework:QuickDispatch(callback)
	end

	local set_keybind_key = function(self, button, keybindIndex)
		if (new_keybind_frame.IsListening) then
			key = mouseKeys [button] or button
			return registerKeybind (new_keybind_frame, key)
		end
		new_keybind_frame.IsListening = true
		new_keybind_frame.keybindIndex = keybindIndex
		new_keybind_frame:SetScript("OnKeyDown", registerKeybind)

		enter_the_key:Show()
		enter_the_key:SetPoint("bottom", self, "top")
	end

	local new_key_bind = function(self, button, specID)
		tinsert(new_keybind_frame.CurrentKeybindEditingSet, {key = "-none-", action = "_target", actiontext = ""})
		FauxScrollFrame_SetOffset (new_keybind_frame.keybindScroll, max(#new_keybind_frame.CurrentKeybindEditingSet-SCROLL_ROLL_AMOUNT, 0))
		new_keybind_frame.keybindScroll:UpdateScroll()
	end

	local set_action_text = function(keybindIndex, _, text)
		local keybind = new_keybind_frame.CurrentKeybindEditingSet [keybindIndex]
		keybind.actiontext = text
		detailsFramework:QuickDispatch(callback)
	end

	local set_action_on_espace_press = function(textentry, capsule)
		capsule = capsule or textentry.MyObject
		local keybind = new_keybind_frame.CurrentKeybindEditingSet [capsule.CurIndex]
		textentry:SetText(keybind.actiontext)
		detailsFramework:QuickDispatch(callback)
	end

	local lock_textentry = {
		["_target"] = true,
		["_taunt"] = true,
		["_interrupt"] = true,
		["_dispel"] = true,
		["_spell"] = false,
		["_macro"] = false,
	}

	local change_key_action = function(self, keybindIndex, value)
		local keybind = new_keybind_frame.CurrentKeybindEditingSet [keybindIndex]
		keybind.action = value
		new_keybind_frame.keybindScroll:UpdateScroll()
		detailsFramework:QuickDispatch(callback)
	end
	local fill_action_dropdown = function()

		local locClass, class = UnitClass("player")

		local taunt = ""
		local interrupt = ""
		local dispel = ""

		if (type(dispel) == "table") then
			local dispelString = "\n"
			for specID, spellid in pairs(dispel) do
				local specid, specName = DetailsFramework.GetSpecializationInfoByID (specID)
				local spellName = GetSpellInfo(spellid)
				dispelString = dispelString .. "|cFFE5E5E5" .. (specName or "") .. "|r: |cFFFFFFFF" .. spellName .. "\n"
			end
			dispel = dispelString
		else
			dispel = ""
		end

		return {
			--{value = "_target", label = "Target", onclick = change_key_action, desc = "Target the unit"},
			--{value = "_taunt", label = "Taunt", onclick = change_key_action, desc = "Cast the taunt spell for your class\n\n|cFFFFFFFFSpell: " .. taunt},
			--{value = "_interrupt", label = "Interrupt", onclick = change_key_action, desc = "Cast the interrupt spell for your class\n\n|cFFFFFFFFSpell: " .. interrupt},
			--{value = "_dispel", label = "Dispel", onclick = change_key_action, desc = "Cast the interrupt spell for your class\n\n|cFFFFFFFFSpell: " .. dispel},
			{value = "_spell", label = "Cast Spell", onclick = change_key_action, desc = "Type the spell name in the text box"},
			{value = "_macro", label = "Run Macro", onclick = change_key_action, desc = "Type your macro in the text box"},
		}
	end

	local copy_keybind = function(self, button, keybindIndex)
		local keybind = new_keybind_frame.CurrentKeybindEditingSet [keybindIndex]
		for specID, t in pairs(new_keybind_frame.Data) do
			if (specID ~= new_keybind_frame.EditingSpec) then
				local key = CopyTable (keybind)
				local specid, specName = DetailsFramework.GetSpecializationInfoByID (specID)
				tinsert(new_keybind_frame.Data [specID], key)
				detailsFramework:Msg("Keybind copied to " .. (specName or ""))
			end
		end
		detailsFramework:QuickDispatch(callback)
	end

	local delete_keybind = function(self, button, keybindIndex)
		tremove(new_keybind_frame.CurrentKeybindEditingSet, keybindIndex)
		new_keybind_frame.keybindScroll:UpdateScroll()
		detailsFramework:QuickDispatch(callback)
	end

	local newTitle = detailsFramework:CreateLabel(new_keybind_frame, "Create a new Keybind:", 12, "silver")
	newTitle:SetPoint("topleft", new_keybind_frame, "topleft", 200, mainStartY)
	local createNewKeybind = detailsFramework:CreateButton(new_keybind_frame, new_key_bind, 160, 20, "New Key Bind", 1, _, _, "NewKeybindButton", _, 0, options_button_template, options_text_template)
	createNewKeybind:SetPoint("topleft", newTitle, "bottomleft", 0, -10)
	--createNewKeybind:SetIcon ([[Interface\Buttons\UI-GuildButton-PublicNote-Up]])

	local update_keybind_list = function(self)

		local keybinds = new_keybind_frame.CurrentKeybindEditingSet
		FauxScrollFrame_Update (self, #keybinds, SCROLL_ROLL_AMOUNT, 21)
		local offset = FauxScrollFrame_GetOffset (self)

		for i = 1, SCROLL_ROLL_AMOUNT do
			local index = i + offset
			local f = self.Frames [i]
			local data = keybinds [index]

			if (data) then
				--index
				f.Index.text = index
				--keybind
				local keyBindText = keysToMouse [data.key] or data.key

				keyBindText = keyBindText:gsub("type1", "LeftButton")
				keyBindText = keyBindText:gsub("type2", "RightButton")
				keyBindText = keyBindText:gsub("type3", "MiddleButton")

				f.KeyBind.text = keyBindText
				f.KeyBind:SetClickFunction(set_keybind_key, index, nil, "left")
				f.KeyBind:SetClickFunction(set_keybind_key, index, nil, "right")
				--action
				f.ActionDrop:SetFixedParameter(index)
				f.ActionDrop:Select(data.action)
				--action text
				f.ActionText.text = data.actiontext
				f.ActionText:SetEnterFunction (set_action_text, index)
				f.ActionText.CurIndex = index

				if (lock_textentry [data.action]) then
					f.ActionText:Disable()
				else
					f.ActionText:Enable()
				end

				--copy
				f.Copy:SetClickFunction(copy_keybind, index)
				--delete
				f.Delete:SetClickFunction(delete_keybind, index)

				f:Show()
			else
				f:Hide()
			end
		end

		self:Show()
	end



	keybindScroll:SetScript("OnVerticalScroll", function(self, offset)
		FauxScrollFrame_OnVerticalScroll (self, offset, 21, update_keybind_list)
	end)
	keybindScroll.UpdateScroll = update_keybind_list

	local backdropColor = {.3, .3, .3, .3}
	local backdropColorOnEnter = {.6, .6, .6, .6}
	local on_enter = function(self)
		self:SetBackdropColor(unpack(backdropColorOnEnter))
	end
	local on_leave = function(self)
		self:SetBackdropColor(unpack(backdropColor))
	end

	local font = "GameFontHighlightSmall"

	for i = 1, SCROLL_ROLL_AMOUNT do
		local f = CreateFrame("frame", "$KeyBindFrame" .. i, keybindScroll, "BackdropTemplate")
		f:SetSize(1009, 20)
		f:SetPoint("topleft", keybindScroll, "topleft", 0, -(i-1)*29)
		f:SetBackdrop({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true})
		f:SetBackdropColor(unpack(backdropColor))
		f:SetScript("OnEnter", on_enter)
		f:SetScript("OnLeave", on_leave)
		tinsert(keybindScroll.Frames, f)

		f.Index = detailsFramework:CreateLabel(f, "1")
		f.KeyBind = detailsFramework:CreateButton(f, set_key_bind, 100, 20, "", _, _, _, "SetNewKeybindButton", _, 0, options_button_template, options_text_template)
		f.ActionDrop = detailsFramework:CreateDropDown (f, fill_action_dropdown, 0, 120, 20, "ActionDropdown", _, options_dropdown_template)
		f.ActionText = detailsFramework:CreateTextEntry(f, function()end, 660, 20, "TextBox", _, _, options_dropdown_template)
		f.Copy = detailsFramework:CreateButton(f, copy_keybind, 20, 20, "", _, _, _, "CopyKeybindButton", _, 0, options_button_template, options_text_template)
		f.Delete = detailsFramework:CreateButton(f, delete_keybind, 16, 20, "", _, _, _, "DeleteKeybindButton", _, 2, options_button_template, options_text_template)

		f.Index:SetPoint("left", f, "left", 10, 0)
		f.KeyBind:SetPoint("left", f, "left", 43, 0)
		f.ActionDrop:SetPoint("left", f, "left", 150, 0)
		f.ActionText:SetPoint("left", f, "left", 276, 0)
		f.Copy:SetPoint("left", f, "left", 950, 0)
		f.Delete:SetPoint("left", f, "left", 990, 0)

		f.Copy:SetIcon ([[Interface\Buttons\UI-GuildButton-PublicNote-Up]], nil, nil, nil, nil, nil, nil, 4)
		f.Delete:SetIcon ([[Interface\Buttons\UI-StopButton]], nil, nil, nil, nil, nil, nil, 4)

		f.Copy.tooltip = "copy this keybind to other specs"
		f.Delete.tooltip = "erase this keybind"

		--editbox
		f.ActionText:SetJustifyH("left")
		f.ActionText:SetHook("OnEscapePressed", set_action_on_espace_press)
		f.ActionText:SetHook("OnEditFocusGained", function()
			local playerSpells = {}
			local tab, tabTex, offset, numSpells = GetSpellTabInfo (2)
			for i = 1, numSpells do
				local index = offset + i
				local spellType, spellId = GetSpellBookItemInfo (index, "player")
				if (spellType == "SPELL") then
					local spellName = GetSpellInfo(spellId)
					tinsert(playerSpells, spellName)
				end
			end
			f.ActionText.WordList = playerSpells
		end)

		f.ActionText:SetAsAutoComplete ("WordList")
	end

	local header = CreateFrame("frame", "$parentOptionsPanelFrameHeader", keybindScroll, "BackdropTemplate")
	header:SetPoint("bottomleft", keybindScroll, "topleft", 0, 2)
	header:SetPoint("bottomright", keybindScroll, "topright", 0, 2)
	header:SetHeight(16)

	header.Index = detailsFramework:CreateLabel  (header, "Index", detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE"))
	header.Key = detailsFramework:CreateLabel  (header, "Key", detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE"))
	header.Action = detailsFramework:CreateLabel  (header, "Action", detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE"))
	header.Macro = detailsFramework:CreateLabel  (header, "Spell Name / Macro", detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE"))
	header.Copy = detailsFramework:CreateLabel  (header, "Copy", detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE"))
	header.Delete = detailsFramework:CreateLabel  (header, "Delete", detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE"))

	header.Index:SetPoint("left", header, "left", 10, 0)
	header.Key:SetPoint("left", header, "left", 43, 0)
	header.Action:SetPoint("left", header, "left", 150, 0)
	header.Macro:SetPoint("left", header, "left", 276, 0)
	header.Copy:SetPoint("left", header, "left", 950, 0)
	header.Delete:SetPoint("left", header, "left", 990, 0)

	new_keybind_frame:SetScript("OnShow", function()

		--new_keybind_frame.EditingSpec = EnemyGrid.CurrentSpec
		--new_keybind_frame.CurrentKeybindEditingSet = EnemyGrid.CurrentKeybindSet

		for _, button in ipairs(allSpecButtons) do
			if (new_keybind_frame.EditingSpec ~= button.specID) then
				button.selectedTexture:Hide()
			else
				button.selectedTexture:Show()
			end
		end

		keybindScroll:UpdateScroll()
	end)

	new_keybind_frame:SetScript("OnHide", function()
		if (new_keybind_frame.IsListening) then
			new_keybind_frame.IsListening = false
			new_keybind_frame:SetScript("OnKeyDown", nil)
		end
	end)

	return new_keybind_frame
end

function detailsFramework:BuildKeybindFunctions (data, prefix)

	--~keybind
	local classLoc, class = UnitClass("player")
	local bindingList = data

	local bindString = "self:ClearBindings()"
	local bindKeyBindTypeFunc = [[local unitFrame = ...]]
	local bindMacroTextFunc = [[local unitFrame = ...]]
	local isMouseBinding

	for i = 1, #bindingList do
		local bind = bindingList [i]
		local bindType

		--which button to press
		if (bind.key:find("type")) then
			local keyNumber = tonumber(bind.key:match ("%d"))
			bindType = keyNumber
			isMouseBinding = true
		else
			bindType = prefix .. "" .. i
			bindString = bindString .. "self:SetBindingClick (0, '" .. bind.key .. "', self:GetName(), '" .. bindType .. "')"
			bindType = "-" .. prefix .. "" .. i
			isMouseBinding = nil
		end

		--keybind type
		local shift, alt, ctrl = bind.key:match ("SHIFT"), bind.key:match ("ALT"), bind.key:match ("CTRL")
		local CommandKeys = alt and alt .. "-" or ""
		CommandKeys = ctrl and CommandKeys .. ctrl .. "-" or CommandKeys
		CommandKeys = shift and CommandKeys .. shift .. "-" or CommandKeys

		local keyBindType
		if (isMouseBinding) then
			keyBindType = [[unitFrame:SetAttribute ("@COMMANDtype@BINDTYPE", "macro")]]
		else
			keyBindType = [[unitFrame:SetAttribute ("type@BINDTYPE", "macro")]]
		end

		keyBindType = keyBindType:gsub("@BINDTYPE", bindType)
		keyBindType = keyBindType:gsub("@COMMAND", CommandKeys)
		bindKeyBindTypeFunc = bindKeyBindTypeFunc .. keyBindType

		--spell or macro
		if (bind.action == "_spell") then
			local macroTextLine
			if (isMouseBinding) then
				macroTextLine = [[unitFrame:SetAttribute ("@COMMANDmacrotext@BINDTYPE", "/cast [@mouseover] @SPELL")]]
			else
				macroTextLine = [[unitFrame:SetAttribute ("macrotext@BINDTYPE", "/cast [@mouseover] @SPELL")]]
			end
			macroTextLine = macroTextLine:gsub("@BINDTYPE", bindType)
			macroTextLine = macroTextLine:gsub("@SPELL", bind.actiontext)
			macroTextLine = macroTextLine:gsub("@COMMAND", CommandKeys)
			bindMacroTextFunc = bindMacroTextFunc .. macroTextLine

		elseif (bind.action == "_macro") then
			local macroTextLine
			if (isMouseBinding) then
				macroTextLine = [[unitFrame:SetAttribute ("@COMMANDmacrotext@BINDTYPE", "@MACRO")]]
			else
				macroTextLine = [[unitFrame:SetAttribute ("macrotext@BINDTYPE", "@MACRO")]]
			end
			macroTextLine = macroTextLine:gsub("@BINDTYPE", bindType)
			macroTextLine = macroTextLine:gsub("@MACRO", bind.actiontext)
			macroTextLine = macroTextLine:gsub("@COMMAND", CommandKeys)
			bindMacroTextFunc = bindMacroTextFunc .. macroTextLine

		end
	end

	--~key
	local bindTypeFuncLoaded = loadstring (bindKeyBindTypeFunc)
	local bindMacroFuncLoaded = loadstring (bindMacroTextFunc)

	if (not bindMacroFuncLoaded or not bindTypeFuncLoaded) then
		return
	end

	return bindString, bindTypeFuncLoaded, bindMacroFuncLoaded
end


function detailsFramework:SetKeybindsOnProtectedFrame (frame, bind_string, bind_type_func, bind_macro_func)

	bind_type_func (frame)
	bind_macro_func (frame)
	frame:SetAttribute ("_onenter", bind_string)

end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~standard backdrop

function detailsFramework:ApplyStandardBackdrop(frame, solidColor, alphaScale)
	alphaScale = alphaScale or 0.95

	if (not frame.SetBackdrop)then
		--print(debugstack(1,2,1))
		Mixin(frame, BackdropTemplateMixin)
	end

	local red, green, blue, alpha = detailsFramework:GetDefaultBackdropColor()

	if (solidColor) then
		local colorDeviation = 0.05
		frame:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Buttons\WHITE8X8]], tileSize = 32, tile = true})
		frame:SetBackdropColor(red, green, blue, 0.872)
		frame:SetBackdropBorderColor(0, 0, 0, 0.95)

	else
		frame:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true})
		frame:SetBackdropColor(red, green, blue, alpha * alphaScale)
		frame:SetBackdropBorderColor(0, 0, 0, 0.95)
	end

	if (not frame.__background) then
		frame.__background = frame:CreateTexture(nil, "background")
		frame.__background:SetColorTexture(red, green, blue)
		frame.__background:SetAllPoints()
	end

	frame.__background:SetAlpha(alpha * alphaScale)
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~title bar

detailsFramework.TitleFunctions = {

	SetTitle = function(self, titleText, titleColor, font, size)
		self.TitleLabel:SetText(titleText or self.TitleLabel:GetText())

		if (titleColor) then
			local r, g, b, a = detailsFramework:ParseColors(titleColor)
			self.TitleLabel:SetTextColor(r, g, b, a)
		end

		if (font) then
			detailsFramework:SetFontFace (self.TitleLabel, font)
		end

		if (size) then
			detailsFramework:SetFontSize(self.TitleLabel, size)
		end
	end


}

function detailsFramework:CreateTitleBar (f, titleText)

	local titleBar = CreateFrame("frame", f:GetName() and f:GetName() .. "TitleBar" or nil, f,"BackdropTemplate")
	titleBar:SetPoint("topleft", f, "topleft", 2, -3)
	titleBar:SetPoint("topright", f, "topright", -2, -3)
	titleBar:SetHeight(20)
	titleBar:SetBackdrop(SimplePanel_frame_backdrop) --it's an upload from this file
	titleBar:SetBackdropColor(.2, .2, .2, 1)
	titleBar:SetBackdropBorderColor(0, 0, 0, 1)

	local closeButton = CreateFrame("button", titleBar:GetName() and titleBar:GetName() .. "CloseButton" or nil, titleBar, "BackdropTemplate")
	closeButton:SetSize(16, 16)

	closeButton:SetNormalTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	closeButton:SetHighlightTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	closeButton:SetPushedTexture([[Interface\GLUES\LOGIN\Glues-CheckBox-Check]])
	closeButton:GetNormalTexture():SetDesaturated(true)
	closeButton:GetHighlightTexture():SetDesaturated(true)
	closeButton:GetPushedTexture():SetDesaturated(true)

	closeButton:SetAlpha(0.7)
	closeButton:SetScript("OnClick", simple_panel_close_click) --upvalue from this file

	local titleLabel = titleBar:CreateFontString(titleBar:GetName() and titleBar:GetName() .. "TitleText" or nil, "overlay", "GameFontNormal")
	titleLabel:SetTextColor(.8, .8, .8, 1)
	titleLabel:SetText(titleText or "")

	--anchors
	closeButton:SetPoint("right", titleBar, "right", -2, 0)
	titleLabel:SetPoint("center", titleBar, "center")

	--members
	f.TitleBar = titleBar
	f.CloseButton = closeButton
	f.TitleLabel = titleLabel

	titleBar.CloseButton = closeButton
	titleBar.Text = titleLabel

	detailsFramework:Mixin(f, detailsFramework.TitleFunctions)

	return titleBar
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ~icon row

detailsFramework.IconRowFunctions = {

	GetIcon = function(self)
		local iconFrame = self.IconPool [self.NextIcon]

		if (not iconFrame) then
			local newIconFrame = CreateFrame("frame", "$parentIcon" .. self.NextIcon, self, "BackdropTemplate")
			newIconFrame.parentIconRow = self

			newIconFrame.Texture = newIconFrame:CreateTexture(nil, "artwork")
			PixelUtil.SetPoint(newIconFrame.Texture, "topleft", newIconFrame, "topleft", 1, -1)
			PixelUtil.SetPoint(newIconFrame.Texture, "bottomright", newIconFrame, "bottomright", -1, 1)

			newIconFrame.Border = newIconFrame:CreateTexture(nil, "background")
			newIconFrame.Border:SetAllPoints()
			newIconFrame.Border:SetColorTexture(0, 0, 0)

			newIconFrame:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1})
			newIconFrame:SetBackdropBorderColor(0, 0, 0, 0)
			newIconFrame:EnableMouse(false)

			local cooldownFrame = CreateFrame("cooldown", "$parentIconCooldown" .. self.NextIcon, newIconFrame, "CooldownFrameTemplate, BackdropTemplate")
			cooldownFrame:SetAllPoints()
			cooldownFrame:EnableMouse(false)
			cooldownFrame:SetFrameLevel(newIconFrame:GetFrameLevel()+1)
			cooldownFrame:SetHideCountdownNumbers (self.options.surpress_blizzard_cd_timer)
			cooldownFrame.noCooldownCount = self.options.surpress_tulla_omni_cc

			newIconFrame.CountdownText = cooldownFrame:CreateFontString(nil, "overlay", "GameFontNormal")
			--newIconFrame.CountdownText:SetPoint("center")
			newIconFrame.CountdownText:SetPoint(self.options.text_anchor or "center", newIconFrame, self.options.text_rel_anchor or "center", self.options.text_x_offset or 0, self.options.text_y_offset or 0)
			newIconFrame.CountdownText:Hide()

			newIconFrame.StackText = newIconFrame:CreateFontString(nil, "overlay", "GameFontNormal")
			--newIconFrame.StackText:SetPoint("bottomright")
			newIconFrame.StackText:SetPoint(self.options.stack_text_anchor or "center", newIconFrame, self.options.stack_text_rel_anchor or "bottomright", self.options.stack_text_x_offset or 0, self.options.stack_text_y_offset or 0)
			newIconFrame.StackText:Hide()

			newIconFrame.Desc = newIconFrame:CreateFontString(nil, "overlay", "GameFontNormal")
			--newIconFrame.Desc:SetPoint("bottom", newIconFrame, "top", 0, 2)
			newIconFrame.Desc:SetPoint(self.options.desc_text_anchor or "bottom", newIconFrame, self.options.desc_text_rel_anchor or "top", self.options.desc_text_x_offset or 0, self.options.desc_text_y_offset or 2)
			newIconFrame.Desc:Hide()

			newIconFrame.Cooldown = cooldownFrame

			self.IconPool [self.NextIcon] = newIconFrame
			iconFrame = newIconFrame
		end

		iconFrame:ClearAllPoints()

		local anchor = self.options.anchor
		local anchorTo = self.NextIcon == 1 and self or self.IconPool [self.NextIcon - 1]
		local xPadding = self.NextIcon == 1 and self.options.left_padding or self.options.icon_padding or 1
		local growDirection = self.options.grow_direction

		if (growDirection == 1) then --grow to right
			if (self.NextIcon == 1) then
				PixelUtil.SetPoint(iconFrame, "left", anchorTo, "left", xPadding, 0)
			else
				PixelUtil.SetPoint(iconFrame, "left", anchorTo, "right", xPadding, 0)
			end

		elseif (growDirection == 2) then --grow to left
			if (self.NextIcon == 1) then
				PixelUtil.SetPoint(iconFrame, "right", anchorTo, "right", xPadding, 0)
			else
				PixelUtil.SetPoint(iconFrame, "right", anchorTo, "left", xPadding, 0)
			end

		end

		detailsFramework:SetFontColor(iconFrame.CountdownText, self.options.text_color)

		self.NextIcon = self.NextIcon + 1
		return iconFrame
	end,

	--adds only if not existing already in the cache
	AddSpecificIcon = function(self, identifierKey, spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff)
		if not identifierKey or identifierKey == "" then
			return
		end

		if not self.AuraCache[identifierKey] then
			local icon = self:SetIcon (spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff or false)
			icon.identifierKey = identifierKey
			self.AuraCache[identifierKey] = true
		end
	end,

	SetIcon = function(self, spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff, modRate)

		local actualSpellName, _, spellIcon = GetSpellInfo(spellId)

		if forceTexture then
			spellIcon = forceTexture
		end

		spellName = spellName or actualSpellName or "unknown_aura"
		modRate = modRate or 1

		if (spellIcon) then
			local iconFrame = self:GetIcon()
			iconFrame.Texture:SetTexture(spellIcon)
			iconFrame.Texture:SetTexCoord(unpack(self.options.texcoord))

			if (borderColor) then
				iconFrame:SetBackdropBorderColor(Plater:ParseColors(borderColor))
			else
				iconFrame:SetBackdropBorderColor(0, 0, 0 ,0)
			end

			if (startTime) then
				CooldownFrame_Set (iconFrame.Cooldown, startTime, duration, true, true, modRate)

				if (self.options.show_text) then
					iconFrame.CountdownText:Show()

					local now = GetTime()

					iconFrame.timeRemaining = (startTime + duration - now) / modRate
					iconFrame.expirationTime = startTime + duration

					local formattedTime = (iconFrame.timeRemaining > 0) and self.options.decimal_timer and iconFrame.parentIconRow.FormatCooldownTimeDecimal(iconFrame.timeRemaining) or iconFrame.parentIconRow.FormatCooldownTime(iconFrame.timeRemaining) or ""
					iconFrame.CountdownText:SetText(formattedTime)

					iconFrame.CountdownText:SetPoint(self.options.text_anchor or "center", iconFrame, self.options.text_rel_anchor or "center", self.options.text_x_offset or 0, self.options.text_y_offset or 0)
					detailsFramework:SetFontSize(iconFrame.CountdownText, self.options.text_size)
					detailsFramework:SetFontFace (iconFrame.CountdownText, self.options.text_font)
					detailsFramework:SetFontOutline (iconFrame.CountdownText, self.options.text_outline)

					if self.options.on_tick_cooldown_update then
						iconFrame.lastUpdateCooldown = now
						iconFrame:SetScript("OnUpdate", self.OnIconTick)
					else
						iconFrame:SetScript("OnUpdate", nil)
					end

				else
					iconFrame:SetScript("OnUpdate", nil)
					iconFrame.CountdownText:Hide()
				end

				iconFrame.Cooldown:SetReverse (self.options.cooldown_reverse)
				iconFrame.Cooldown:SetDrawSwipe (self.options.cooldown_swipe_enabled)
				iconFrame.Cooldown:SetEdgeTexture (self.options.cooldown_edge_texture)
				iconFrame.Cooldown:SetHideCountdownNumbers (self.options.surpress_blizzard_cd_timer)
			else
				iconFrame.timeRemaining = nil
				iconFrame.expirationTime = nil
				iconFrame:SetScript("OnUpdate", nil)
				iconFrame.CountdownText:Hide()
			end

			if (descText and self.options.desc_text) then
				iconFrame.Desc:Show()
				iconFrame.Desc:SetText(descText.text)
				iconFrame.Desc:SetTextColor(detailsFramework:ParseColors(descText.text_color or self.options.desc_text_color))
				iconFrame.Desc:SetPoint(self.options.desc_text_anchor or "bottom", iconFrame, self.options.desc_text_rel_anchor or "top", self.options.desc_text_x_offset or 0, self.options.desc_text_y_offset or 2)
				detailsFramework:SetFontSize(iconFrame.Desc, descText.text_size or self.options.desc_text_size)
				detailsFramework:SetFontFace (iconFrame.Desc, self.options.desc_text_font)
				detailsFramework:SetFontOutline (iconFrame.Desc, self.options.desc_text_outline)
			else
				iconFrame.Desc:Hide()
			end

			if (count and count > 1 and self.options.stack_text) then
				iconFrame.StackText:Show()
				iconFrame.StackText:SetText(count)
				iconFrame.StackText:SetTextColor(detailsFramework:ParseColors(self.options.desc_text_color))
				iconFrame.StackText:SetPoint(self.options.stack_text_anchor or "center", iconFrame, self.options.stack_text_rel_anchor or "bottomright", self.options.stack_text_x_offset or 0, self.options.stack_text_y_offset or 0)
				detailsFramework:SetFontSize(iconFrame.StackText, self.options.stack_text_size)
				detailsFramework:SetFontFace (iconFrame.StackText, self.options.stack_text_font)
				detailsFramework:SetFontOutline (iconFrame.StackText, self.options.stack_text_outline)
			else
				iconFrame.StackText:Hide()
			end

			PixelUtil.SetSize(iconFrame, self.options.icon_width, self.options.icon_height)
			iconFrame:Show()

			--update the size of the frame
			self:SetWidth((self.options.left_padding * 2) + (self.options.icon_padding * (self.NextIcon-2)) + (self.options.icon_width * (self.NextIcon - 1)))
			self:SetHeight(self.options.icon_height + (self.options.top_padding * 2))

			--make information available
			iconFrame.spellId = spellId
			iconFrame.startTime = startTime
			iconFrame.duration = duration
			iconFrame.count = count
			iconFrame.debuffType = debuffType
			iconFrame.caster = caster
			iconFrame.canStealOrPurge = canStealOrPurge
			iconFrame.isBuff = isBuff
			iconFrame.spellName = spellName

			iconFrame.identifierKey = nil -- only used for "specific" add/remove

			--add the spell into the cache
			self.AuraCache [spellId or -1] = true
			self.AuraCache [spellName] = true
			self.AuraCache.canStealOrPurge = self.AuraCache.canStealOrPurge or canStealOrPurge
			self.AuraCache.hasEnrage = self.AuraCache.hasEnrage or debuffType == "" --yes, enrages are empty-string...

			--show the frame
			self:Show()

			return iconFrame
		end
	end,

	OnIconTick = function(self, deltaTime)
		local now = GetTime()
		if (self.lastUpdateCooldown + 0.05) <= now then
			self.timeRemaining = self.expirationTime - now
			if self.timeRemaining > 0 then
				if self.parentIconRow.options.decimal_timer then
					self.CountdownText:SetText(self.parentIconRow.FormatCooldownTimeDecimal(self.timeRemaining))
				else
					self.CountdownText:SetText(self.parentIconRow.FormatCooldownTime(self.timeRemaining))
				end
			else
				self.CountdownText:SetText("")
			end
			self.lastUpdateCooldown = now
		end
	end,

	FormatCooldownTime = function(formattedTime)
		if (formattedTime >= 3600) then
			formattedTime = floor(formattedTime / 3600) .. "h"

		elseif (formattedTime >= 60) then
			formattedTime = floor(formattedTime / 60) .. "m"

		else
			formattedTime = floor(formattedTime)
		end
		return formattedTime
	end,

	FormatCooldownTimeDecimal = function(formattedTime)
        if formattedTime < 10 then
            return ("%.1f"):format(formattedTime)
        elseif formattedTime < 60 then
            return ("%d"):format(formattedTime)
        elseif formattedTime < 3600 then
            return ("%d:%02d"):format(formattedTime/60%60, formattedTime%60)
        elseif formattedTime < 86400 then
            return ("%dh %02dm"):format(formattedTime/(3600), formattedTime/60%60)
        else
            return ("%dd %02dh"):format(formattedTime/86400, (formattedTime/3600) - (floor(formattedTime/86400) * 24))
        end
	end,

	RemoveSpecificIcon = function(self, identifierKey)
		if not identifierKey or identifierKey == "" then
			return
		end

		table.wipe(self.AuraCache)

		local iconPool = self.IconPool
		local countStillShown = 0
		for i = 1, self.NextIcon -1 do
			local iconFrame = iconPool[i]
			if iconFrame.identifierKey and iconFrame.identifierKey == identifierKey then
				iconFrame:Hide()
				iconFrame:ClearAllPoints()
				iconFrame.identifierKey = nil
			else
				self.AuraCache [iconFrame.spellId] = true
				self.AuraCache [iconFrame.spellName] = true
				self.AuraCache.canStealOrPurge = self.AuraCache.canStealOrPurge or iconFrame.canStealOrPurge
				self.AuraCache.hasEnrage = self.AuraCache.hasEnrage or iconFrame.debuffType == "" --yes, enrages are empty-string...
				countStillShown = countStillShown + 1
			end
		end

		self:AlignAuraIcons()

	end,

	ClearIcons = function(self, resetBuffs, resetDebuffs)
		resetBuffs = resetBuffs ~= false
		resetDebuffs = resetDebuffs ~= false
		table.wipe(self.AuraCache)

		local iconPool = self.IconPool
		for i = 1, self.NextIcon -1 do
			local iconFrame = iconPool[i]
			if iconFrame.isBuff == nil then
				iconFrame:Hide()
				iconFrame:ClearAllPoints()
			elseif resetBuffs and iconFrame.isBuff then
				iconFrame:Hide()
				iconFrame:ClearAllPoints()
			elseif resetDebuffs and not iconFrame.isBuff then
				iconFrame:Hide()
				iconFrame:ClearAllPoints()
			else
				self.AuraCache [iconFrame.spellId] = true
				self.AuraCache [iconFrame.spellName] = true
				self.AuraCache.canStealOrPurge = self.AuraCache.canStealOrPurge or iconFrame.canStealOrPurge
				self.AuraCache.hasEnrage = self.AuraCache.hasEnrage or iconFrame.debuffType == "" --yes, enrages are empty-string...
			end
		end

		self:AlignAuraIcons()

	end,

	AlignAuraIcons = function(self)

		local iconPool = self.IconPool
		local iconAmount = #iconPool
		local countStillShown = 0

		table.sort (iconPool, function(i1, i2) return i1:IsShown() and not i2:IsShown() end)

		if iconAmount == 0 then
			self:Hide()
		else
			-- re-anchor not hidden
			for i = 1, iconAmount do
				local iconFrame = iconPool[i]
				local anchor = self.options.anchor
				local anchorTo = i == 1 and self or self.IconPool [i - 1]
				local xPadding = i == 1 and self.options.left_padding or self.options.icon_padding or 1
				local growDirection = self.options.grow_direction

				countStillShown = countStillShown + (iconFrame:IsShown() and 1 or 0)

				iconFrame:ClearAllPoints()
				if (growDirection == 1) then --grow to right
					if (i == 1) then
						PixelUtil.SetPoint(iconFrame, "left", anchorTo, "left", xPadding, 0)
					else
						PixelUtil.SetPoint(iconFrame, "left", anchorTo, "right", xPadding, 0)
					end

				elseif (growDirection == 2) then --grow to left
					if (i == 1) then
						PixelUtil.SetPoint(iconFrame, "right", anchorTo, "right", xPadding, 0)
					else
						PixelUtil.SetPoint(iconFrame, "right", anchorTo, "left", xPadding, 0)
					end

				end
			end
		end

		self.NextIcon = countStillShown + 1

	end,

	GetIconGrowDirection = function(self)
		local side = self.options.anchor.side

		if (side == 1) then
			return 1
		elseif (side == 2) then
			return 2
		elseif (side == 3) then
			return 1
		elseif (side == 4) then
			return 1
		elseif (side == 5) then
			return 2
		elseif (side == 6) then
			return 1
		elseif (side == 7) then
			return 2
		elseif (side == 8) then
			return 1
		elseif (side == 9) then
			return 1
		elseif (side == 10) then
			return 1
		elseif (side == 11) then
			return 2
		elseif (side == 12) then
			return 1
		elseif (side == 13) then
			return 1
		end
	end,

	OnOptionChanged = function(self, optionName)
		self:SetBackdropColor(unpack(self.options.backdrop_color))
		self:SetBackdropBorderColor(unpack(self.options.backdrop_border_color))
	end,
}

local default_icon_row_options = {
	icon_width = 20,
	icon_height = 20,
	texcoord = {.1, .9, .1, .9},
	show_text = true,
	text_color = {1, 1, 1, 1},
	text_size = 12,
	text_font = "Arial Narrow",
	text_outline = "NONE",
	text_anchor = "center",
	text_rel_anchor = "center",
	text_x_offset = 0,
	text_y_offset = 0,
	desc_text = true,
	desc_text_color = {1, 1, 1, 1},
	desc_text_size = 7,
	desc_text_font = "Arial Narrow",
	desc_text_outline = "NONE",
	desc_text_anchor = "bottom",
	desc_text_rel_anchor = "top",
	desc_text_x_offset = 0,
	desc_text_y_offset = 2,
	stack_text = true,
	stack_text_color = {1, 1, 1, 1},
	stack_text_size = 10,
	stack_text_font = "Arial Narrow",
	stack_text_outline = "NONE",
	stack_text_anchor = "center",
	stack_text_rel_anchor = "bottomright",
	stack_text_x_offset = 0,
	stack_text_y_offset = 0,
	left_padding = 1, --distance between right and left
	top_padding = 1, --distance between top and bottom
	icon_padding = 1, --distance between each icon
	backdrop = {},
	backdrop_color = {0, 0, 0, 0.5},
	backdrop_border_color = {0, 0, 0, 1},
	anchor = {side = 6, x = 2, y = 0},
	grow_direction = 1, --1 = to right 2 = to left
	surpress_blizzard_cd_timer = false,
	surpress_tulla_omni_cc = false,
	on_tick_cooldown_update = true,
	decimal_timer = false,
	cooldown_reverse = false,
	cooldown_swipe_enabled = true,
	cooldown_edge_texture = "Interface\\Cooldown\\edge",
}

function detailsFramework:CreateIconRow (parent, name, options)
	local f = CreateFrame("frame", name, parent, "BackdropTemplate")
	f.IconPool = {}
	f.NextIcon = 1
	f.AuraCache = {}

	detailsFramework:Mixin(f, detailsFramework.IconRowFunctions)
	detailsFramework:Mixin(f, detailsFramework.OptionsFunctions)

	f:BuildOptionsTable (default_icon_row_options, options)

	f:SetSize(f.options.icon_width, f.options.icon_height + (f.options.top_padding * 2))

	f:SetBackdrop(f.options.backdrop)
	f:SetBackdropColor(unpack(f.options.backdrop_color))
	f:SetBackdropBorderColor(unpack(f.options.backdrop_border_color))

	return f
end


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--~header

--mixed functions
detailsFramework.HeaderFunctions = {
	AddFrameToHeaderAlignment = function(self, frame)
		self.FramesToAlign = self.FramesToAlign or {}
		tinsert(self.FramesToAlign, frame)
	end,

	GetFramesFromHeaderAlignment = function(self, frame)
		return self.FramesToAlign or {}
	end,

	--@self: an object like a line
	--@headerFrame: the main header frame
	--@anchor: which side the columnHeaders are attach
	AlignWithHeader = function(self, headerFrame, anchor)
		local columnHeaderFrames = headerFrame.columnHeadersCreated
		anchor = anchor or "topleft"

		for i = 1, #self.FramesToAlign do
			local frame = self.FramesToAlign[i]
			frame:ClearAllPoints()

			local columnHeader = columnHeaderFrames[i]
			local offset = 0

			if (columnHeader.columnAlign == "right") then
				offset = columnHeader:GetWidth()
				if (frame:GetObjectType() == "FontString") then
					frame:SetJustifyH("right")
				end
			end

			frame:SetPoint(columnHeader.columnAlign, self, anchor, columnHeader.XPosition + columnHeader.columnOffset + offset, 0)
		end
	end,

	--@self: column header button
	OnClick = function(self, buttonClicked)

		--get the header main frame
		local headerFrame = self:GetParent()

		--if this header does not have a clickable header, just ignore
		if (not headerFrame.columnSelected) then
			return
		end

		--get the latest column header selected
		local previousColumnHeader = headerFrame.columnHeadersCreated[headerFrame.columnSelected]
		previousColumnHeader.Arrow:Hide()
		headerFrame:ResetColumnHeaderBackdrop(previousColumnHeader)
		headerFrame:SetBackdropColorForSelectedColumnHeader(self)

		if (headerFrame.columnSelected == self.columnIndex) then
			self.order = self.order ~= "ASC" and "ASC" or "DESC"
		end
		headerFrame.columnOrder = self.order

		--set the new column header selected
		headerFrame.columnSelected = self.columnIndex

		headerFrame:UpdateSortArrow(self)

		if (headerFrame.options.header_click_callback) then
			--callback with the main header frame, column header, column index and column order as payload
			local okay, errortext = pcall(headerFrame.options.header_click_callback, headerFrame, self, self.columnIndex, self.order)
			if (not okay) then
				print("DF: Header onClick callback error:", errortext)
			end
		end
	end,
}

detailsFramework.HeaderCoreFunctions = {
	GetColumnWidth = function(self, columnId)
		return self.HeaderTable[columnId].width
	end,

	SetHeaderTable = function(self, newTable)
		self.columnHeadersCreated = self.columnHeadersCreated or {}
		self.HeaderTable = newTable
		self.NextHeader = 1
		self.HeaderWidth = 0
		self.HeaderHeight = 0
		self:Refresh()
	end,

	--return which header is current selected and the the order ASC DESC
	GetSelectedColumn = function(self)
		return self.columnSelected, self.columnHeadersCreated[self.columnSelected or 1].order
	end,

	--clean up and rebuild the header following the header options
	--@self: main header frame
	Refresh = function(self)
		--refresh background frame
		self:SetBackdrop(self.options.backdrop)
		self:SetBackdropColor(unpack(self.options.backdrop_color))
		self:SetBackdropBorderColor(unpack(self.options.backdrop_border_color))

		--reset all header frames
		for i = 1, #self.columnHeadersCreated do
			local columnHeader = self.columnHeadersCreated[i]
			columnHeader.InUse = false
			columnHeader:Hide()
		end

		local previousColumnHeader
		local growDirection = string.lower(self.options.grow_direction)

		--update header frames
		local headerSize = #self.HeaderTable
		for i = 1, headerSize do
			--get the header button, a new one is created if it doesn't exists yet
			local columnHeader = self:GetNextHeader()
			self:UpdateColumnHeader(columnHeader, i)

			--grow direction
			if (not previousColumnHeader) then
				columnHeader:SetPoint("topleft", self, "topleft", 0, 0)

				if (growDirection == "right") then
					if (self.options.use_line_separators) then
						columnHeader.Separator:Show()
						columnHeader.Separator:SetWidth(self.options.line_separator_width)
						columnHeader.Separator:SetColorTexture(unpack(self.options.line_separator_color))

						columnHeader.Separator:ClearAllPoints()
						if (self.options.line_separator_gap_align) then
							columnHeader.Separator:SetPoint("topleft", columnHeader, "topright", 0, 0)
						else
							columnHeader.Separator:SetPoint("topright", columnHeader, "topright", 0, 0)
						end
						columnHeader.Separator:SetHeight(self.options.line_separator_height)
					end
				end
			else
				if (growDirection == "right") then
					columnHeader:SetPoint("topleft", previousColumnHeader, "topright", self.options.padding, 0)

					if (self.options.use_line_separators) then
						columnHeader.Separator:Show()
						columnHeader.Separator:SetWidth(self.options.line_separator_width)
						columnHeader.Separator:SetColorTexture(unpack(self.options.line_separator_color))

						columnHeader.Separator:ClearAllPoints()
						if (self.options.line_separator_gap_align) then
							columnHeader.Separator:SetPoint("topleft", columnHeader, "topright", 0, 0)
						else
							columnHeader.Separator:SetPoint("topleft", columnHeader, "topright", 0, 0)
						end
						columnHeader.Separator:SetHeight(self.options.line_separator_height)

						if (headerSize == i) then
							columnHeader.Separator:Hide()
						end
					end

				elseif (growDirection == "left") then
					columnHeader:SetPoint("topright", previousColumnHeader, "topleft", -self.options.padding, 0)

				elseif (growDirection == "bottom") then
					columnHeader:SetPoint("topleft", previousColumnHeader, "bottomleft", 0, -self.options.padding)

				elseif (growDirection == "top") then
					columnHeader:SetPoint("bottomleft", previousColumnHeader, "topleft", 0, self.options.padding)
				end
			end

			previousColumnHeader = columnHeader
		end

		self:SetSize(self.HeaderWidth, self.HeaderHeight)
	end,

	--@self: main header frame
	UpdateSortArrow = function(self, columnHeader, defaultShown, defaultOrder)
		local options = self.options
		local order = defaultOrder or columnHeader.order
		local arrowIcon = columnHeader.Arrow

		if (type(defaultShown) ~= "boolean") then
			arrowIcon:Show()
		else
			arrowIcon:SetShown(defaultShown)
			if (defaultShown) then
				self:SetBackdropColorForSelectedColumnHeader(columnHeader)
			end
		end

		arrowIcon:SetAlpha(options.arrow_alpha)

		if (order == "ASC") then
			arrowIcon:SetTexture(options.arrow_up_texture)
			arrowIcon:SetTexCoord(unpack(options.arrow_up_texture_coords))
			arrowIcon:SetSize(unpack(options.arrow_up_size))

		elseif (order == "DESC") then
			arrowIcon:SetTexture(options.arrow_down_texture)
			arrowIcon:SetTexCoord(unpack(options.arrow_down_texture_coords))
			arrowIcon:SetSize(unpack(options.arrow_down_size))
		end
	end,

	--@self: main header frame
	UpdateColumnHeader = function(self, columnHeader, headerIndex)
		local headerData = self.HeaderTable[headerIndex]

		if (headerData.icon) then
			columnHeader.Icon:SetTexture(headerData.icon)

			if (headerData.texcoord) then
				columnHeader.Icon:SetTexCoord(unpack(headerData.texcoord))
			else
				columnHeader.Icon:SetTexCoord(0, 1, 0, 1)
			end

			columnHeader.Icon:SetPoint("left", columnHeader, "left", self.options.padding, 0)
			columnHeader.Icon:Show()
		end

		if (headerData.text) then
			columnHeader.Text:SetText(headerData.text)

			--text options
			detailsFramework:SetFontColor(columnHeader.Text, self.options.text_color)
			detailsFramework:SetFontSize(columnHeader.Text, self.options.text_size)
			detailsFramework:SetFontOutline(columnHeader.Text, self.options.text_shadow)

			--point
			if (not headerData.icon) then
				columnHeader.Text:SetPoint("left", columnHeader, "left", self.options.padding, 0)
			else
				columnHeader.Text:SetPoint("left", columnHeader.Icon, "right", self.options.padding, 0)
			end

			columnHeader.Text:Show()
		end

		--column header index
		columnHeader.columnIndex = headerIndex

		if (headerData.canSort) then
			columnHeader.order = "DESC"
			columnHeader.Arrow:SetTexture(self.options.arrow_up_texture)
		else
			columnHeader.Arrow:Hide()
		end

		if (headerData.selected) then
			columnHeader.Arrow:Show()
			columnHeader.Arrow:SetAlpha(.843)
			self:UpdateSortArrow(columnHeader, true, columnHeader.order)
			self.columnSelected = headerIndex
		else
			if (headerData.canSort) then
				self:UpdateSortArrow(columnHeader, false, columnHeader.order)
			end
		end

		--size
		if (headerData.width) then
			columnHeader:SetWidth(headerData.width)
		end
		if (headerData.height) then
			columnHeader:SetHeight(headerData.height)
		end

		columnHeader.XPosition = self.HeaderWidth -- + self.options.padding
		columnHeader.YPosition = self.HeaderHeight -- + self.options.padding

		columnHeader.columnAlign = headerData.align or "left"
		columnHeader.columnOffset = headerData.offset or 0

		--add the header piece size to the total header size
		local growDirection = string.lower(self.options.grow_direction)

		if (growDirection == "right" or growDirection == "left") then
			self.HeaderWidth = self.HeaderWidth + columnHeader:GetWidth() + self.options.padding
			self.HeaderHeight = math.max(self.HeaderHeight, columnHeader:GetHeight())

		elseif (growDirection == "top" or growDirection == "bottom") then
			self.HeaderWidth =  math.max(self.HeaderWidth, columnHeader:GetWidth())
			self.HeaderHeight = self.HeaderHeight + columnHeader:GetHeight() + self.options.padding
		end

		columnHeader:Show()
		columnHeader.InUse = true
	end,

	--reset column header backdrop
	--@self: main header frame
	ResetColumnHeaderBackdrop = function(self, columnHeader)
		columnHeader:SetBackdrop(self.options.header_backdrop)
		columnHeader:SetBackdropColor(unpack(self.options.header_backdrop_color))
		columnHeader:SetBackdropBorderColor(unpack(self.options.header_backdrop_border_color))
	end,

	--@self: main header frame
	SetBackdropColorForSelectedColumnHeader = function(self, columnHeader)
		columnHeader:SetBackdropColor(unpack(self.options.header_backdrop_color_selected))
	end,

	--clear the column header
	--@self: main header frame
	ClearColumnHeader = function(self, columnHeader)
		columnHeader:SetSize(self.options.header_width, self.options.header_height)
		self:ResetColumnHeaderBackdrop(columnHeader)

		columnHeader:ClearAllPoints()

		columnHeader.Icon:SetTexture("")
		columnHeader.Icon:Hide()
		columnHeader.Text:SetText("")
		columnHeader.Text:Hide()
	end,

	--get the next column header, create one if doesn't exists
	--@self: main header frame
	GetNextHeader = function(self)
		local nextHeader = self.NextHeader
		local columnHeader = self.columnHeadersCreated[nextHeader]

		if (not columnHeader) then
			--create a new column header
			local newHeader = CreateFrame("button", "$parentHeaderIndex" .. nextHeader, self, "BackdropTemplate")
			newHeader:SetScript("OnClick", detailsFramework.HeaderFunctions.OnClick)

			--header icon
			detailsFramework:CreateImage(newHeader, "", self.options.header_height, self.options.header_height, "ARTWORK", nil, "Icon", "$parentIcon")
			--header separator
			detailsFramework:CreateImage(newHeader, "", 1, 1, "ARTWORK", nil, "Separator", "$parentSeparator")
			--header name text
			detailsFramework:CreateLabel(newHeader, "", self.options.text_size, self.options.text_color, "GameFontNormal", "Text", "$parentText", "ARTWORK")
			--header selected and order icon
			detailsFramework:CreateImage(newHeader, self.options.arrow_up_texture, 12, 12, "ARTWORK", nil, "Arrow", "$parentArrow")

			newHeader.Arrow:SetPoint("right", newHeader, "right", -1, 0)

			newHeader.Separator:Hide()
			newHeader.Arrow:Hide()

			self:UpdateSortArrow(newHeader, false, "DESC")

			tinsert(self.columnHeadersCreated, newHeader)
			columnHeader = newHeader
		end

		self:ClearColumnHeader(columnHeader)
		self.NextHeader = self.NextHeader + 1
		return columnHeader
	end,

	NextHeader = 1,
	HeaderWidth = 0,
	HeaderHeight = 0,
}

local default_header_options = {
	backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	backdrop_color = {0, 0, 0, 0.2},
	backdrop_border_color = {0.1, 0.1, 0.1, .2},

	text_color = {1, 1, 1, 1},
	text_size = 10,
	text_shadow = false,
	grow_direction = "RIGHT",
	padding = 2,

	--each piece of the header
	header_backdrop = {bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	header_backdrop_color = {0, 0, 0, 0.5},
	header_backdrop_color_selected = {0.3, 0.3, 0.3, 0.5},
	header_backdrop_border_color = {0, 0, 0, 0},
	header_width = 120,
	header_height = 20,

	arrow_up_texture = [[Interface\Buttons\Arrow-Up-Down]],
	arrow_up_texture_coords = {0, 1, 6/16, 1},
	arrow_up_size = {12, 11},
	arrow_down_texture = [[Interface\Buttons\Arrow-Down-Down]],
	arrow_down_texture_coords = {0, 1, 0, 11/16},
	arrow_down_size = {12, 11},
	arrow_alpha = 0.659,

	use_line_separators = false,
	line_separator_color = {.1, .1, .1, .6},
	line_separator_width = 1,
	line_separator_height = 200,
	line_separator_gap_align = false,
}

function detailsFramework:CreateHeader(parent, headerTable, options, frameName)
	local newHeader = CreateFrame("frame", frameName or "$parentHeaderLine", parent, "BackdropTemplate")

	detailsFramework:Mixin(newHeader, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(newHeader, detailsFramework.HeaderCoreFunctions)

	newHeader:BuildOptionsTable(default_header_options, options)

	newHeader:SetBackdrop(newHeader.options.backdrop)
	newHeader:SetBackdropColor(unpack(newHeader.options.backdrop_color))
	newHeader:SetBackdropBorderColor(unpack(newHeader.options.backdrop_border_color))

	newHeader:SetHeaderTable(headerTable)

	return newHeader
end

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--radio group

local default_radiogroup_options = {
	width = 1,
	height = 1,
	backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	backdrop_color = {0, 0, 0, 0.2},
	backdrop_border_color = {0.1, 0.1, 0.1, .2},
	is_radio = false,
}

detailsFramework.RadioGroupCoreFunctions = {
	Disable = function(self)
		local frameList = self:GetAllCheckboxes()
		for _, checkbox in ipairs(frameList) do
			checkbox = checkbox.GetCapsule and checkbox:GetCapsule() or checkbox
			checkbox:Disable()
		end
	end,

	Enable = function(self)
		local frameList = self:GetAllCheckboxes()
		for _, checkbox in ipairs(frameList) do
			checkbox = checkbox.GetCapsule and checkbox:GetCapsule() or checkbox
			checkbox:Enable()
		end
	end,

	DeselectAll = function(self)
		local frameList = self:GetAllCheckboxes()
		for _, checkbox in ipairs(frameList) do
			checkbox = checkbox.GetCapsule and checkbox:GetCapsule() or checkbox
			checkbox:SetValue(false)
		end
	end,

	FadeIn = function(self)
		local frameList = self:GetAllCheckboxes()
		for _, checkbox in ipairs(frameList) do
			checkbox:SetAlpha(1)
		end
	end,

	FadeOut = function(self)
		local frameList = self:GetAllCheckboxes()
		for _, checkbox in ipairs(frameList) do
			checkbox:SetAlpha(.7)
		end
	end,

	SetFadeState = function(self, state)
		if (state) then
			self:FadeIn()
		else
			self:FadeOut()
		end
	end,

	GetAllCheckboxes = function(self)
		return {self:GetChildren()}
	end,

	GetCheckbox = function(self, checkboxId)
		local allCheckboxes = self:GetAllCheckboxes()
		local checkbox = allCheckboxes[checkboxId]
		if (not checkbox) then
			checkbox = self:CreateCheckbox()
		end
		return checkbox
	end,

	CreateCheckbox = function(self)
		local checkbox = detailsFramework:CreateSwitch(self, function()end, false, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_BRIGHT_TEMPLATE"))
		checkbox:SetAsCheckBox()
		checkbox.Icon = detailsFramework:CreateImage(checkbox, "", 16, 16)
		checkbox.Label = detailsFramework:CreateLabel(checkbox, "")

		return checkbox
	end,

	ResetAllCheckboxes = function(self)
		local radioCheckboxes = self:GetAllCheckboxes()
		for i = 1, #radioCheckboxes do
			local checkBox = radioCheckboxes[i]
			checkBox:Hide()
		end
	end,

	--if the list of checkboxes are a radio group
	RadioOnClick = function(checkbox, fixedParam, value)
		--turn off all checkboxes
		checkbox:GetParent():DeselectAll()

		--turn on the clicked checkbox
		checkbox:SetValue(true)

		--callback
		if (checkbox._callback) then
			detailsFramework:QuickDispatch(checkbox._callback, fixedParam, checkbox._optionid)
		end
	end,

	RefreshCheckbox = function(self, checkbox, optionTable, optionId)
		checkbox = checkbox.GetCapsule and checkbox:GetCapsule() or checkbox

		local setFunc = self.options.is_radio and self.RadioOnClick or optionTable.set
		checkbox:SetSwitchFunction(setFunc)
		checkbox._callback = optionTable.callback
		checkbox._set = self.options.is_radio and optionTable.callback or optionTable.set
		checkbox._optionid = optionId
		checkbox:SetFixedParameter(optionTable.param or optionId)

		local isChecked = type(optionTable.get) == "function" and detailsFramework:Dispatch(optionTable.get) or false
		checkbox:SetValue(isChecked)

		checkbox.Label:SetText(optionTable.name)

		if (optionTable.texture) then
			checkbox.Icon:SetTexture(optionTable.texture)
			checkbox.Icon:SetPoint("left", checkbox, "right", 2, 0)
			checkbox.Label:SetPoint("left", checkbox.Icon, "right", 2, 0)

			if (optionTable.texcoord) then
				checkbox.Icon:SetTexCoord(unpack(optionTable.texcoord))
			else
				checkbox.Icon:SetTexCoord(0, 1, 0, 1)
			end
		else
			checkbox.Icon:SetTexture("")
			checkbox.Label:SetPoint("left", checkbox, "right", 2, 0)
		end
	end,

	Refresh = function(self)
		self:ResetAllCheckboxes()
		local radioOptions = self:GetOptions()
		local radioCheckboxes = self:GetAllCheckboxes()

		for optionId, optionsTable in ipairs(radioOptions) do
			local checkbox = self:GetCheckbox(optionId)
			checkbox.OptionID = optionId
			checkbox:Show()
			self:RefreshCheckbox(checkbox, optionsTable, optionId)
		end

		--sending false to automatically use the radio group children
		self:ArrangeFrames(false, self.AnchorOptions)
	end,

	SetOptions = function(self, radioOptions)
		self.RadioOptionsTable = radioOptions
		self:Refresh()
	end,

	GetOptions = function(self)
		return self.RadioOptionsTable
	end,
}

--[=[
	radionOptions: an index table with options for the radio group {name = "", set = func (self, param, value), param = value, get = func, texture = "", texcoord = {}}
		set function receives as self the checkbox, use :GetParent() to get the radion group frame
		if get function return nil or false the checkbox isn't checked
	name: the name of the frame
	options: override options for default_radiogroup_options table
	anchorOptions: override options for default_framelayout_options table
--]=]
function detailsFramework:CreateCheckboxGroup(parent, radioOptions, name, options, anchorOptions)
	local f = CreateFrame("frame", name, parent, "BackdropTemplate")

	detailsFramework:Mixin(f, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(f, detailsFramework.RadioGroupCoreFunctions)
	detailsFramework:Mixin(f, detailsFramework.LayoutFrame)

	f:BuildOptionsTable (default_radiogroup_options, options)

	f:SetSize(f.options.width, f.options.height)
	f:SetBackdrop(f.options.backdrop)
	f:SetBackdropColor(unpack(f.options.backdrop_color))
	f:SetBackdropBorderColor(unpack(f.options.backdrop_border_color))

	f.AnchorOptions = anchorOptions or {}

	if (f.options.title) then
		local titleLabel = detailsFramework:CreateLabel(f, f.options.title, detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
		titleLabel:SetPoint("bottomleft", f, "topleft", 0, 2)
		f.Title = titleLabel
	end

	f:SetOptions (radioOptions)

	return f
end

function detailsFramework:CreateRadionGroup(parent, radioOptions, name, options, anchorOptions) --alias for miss spelled old function
	return detailsFramework:CreateRadioGroup(parent, radioOptions, name, options, anchorOptions)
end

function detailsFramework:CreateRadioGroup(parent, radioOptions, name, options, anchorOptions)
	options = options or {}
	options.is_radio = true
	return detailsFramework:CreateCheckboxGroup(parent, radioOptions, name, options, anchorOptions)
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--load conditions panel

--this is the table prototype to hold load conditions settings
local default_load_conditions = {
	class = {},
	spec = {},
	race = {},
	talent = {},
	pvptalent = {},
	group = {},
	role = {},
	affix = {},
	encounter_ids = {},
	map_ids = {},
}

local default_load_conditions_frame_options = {
	title = "Details! Framework: Load Conditions",
	name = "Object",
}

function detailsFramework:CreateLoadFilterParser (callback)
	local f = CreateFrame("frame")
	f:RegisterEvent("PLAYER_ENTERING_WORLD")
	if IS_WOW_PROJECT_MAINLINE then
		f:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
		f:RegisterEvent("PLAYER_TALENT_UPDATE")
	end
	f:RegisterEvent("PLAYER_ROLES_ASSIGNED")
	f:RegisterEvent("ZONE_CHANGED_NEW_AREA")
	if IS_WOW_PROJECT_MAINLINE then
		f:RegisterEvent("CHALLENGE_MODE_START")
	end
	f:RegisterEvent("ENCOUNTER_START")
	f:RegisterEvent("PLAYER_REGEN_ENABLED")

	f:SetScript("OnEvent", function(self, event, ...)
		if (event == "ENCOUNTER_START") then
			local encounterID = ...
			f.EncounterIDCached = encounterID

		elseif (event == "ENCOUNTER_END") then
			f.EncounterIDCached = nil

		elseif (event == "PLAYER_REGEN_ENABLED") then
			--f.EncounterIDCached = nil
			--when the player dies during an encounter, the game is triggering regen enabled

		elseif (event == "PLAYER_SPECIALIZATION_CHANGED") then
			if (DetailsFrameworkLoadConditionsPanel and DetailsFrameworkLoadConditionsPanel:IsShown()) then
				DetailsFrameworkLoadConditionsPanel:Refresh()
			end

			local unit = ...
			if (not unit or not UnitIsUnit("player", unit)) then
				return
			end

		elseif (event == "PLAYER_ROLES_ASSIGNED") then
			local assignedRole = UnitGroupRolesAssigned("player")
			if (assignedRole == "NONE") then
				local spec = DetailsFramework.GetSpecialization()
				if (spec) then
					assignedRole = DetailsFramework.GetSpecializationRole (spec)
				end
			end

			if (detailsFramework.CurrentPlayerRole == assignedRole) then
				return
			end

			detailsFramework.CurrentPlayerRole = assignedRole
		end

		--print("Plater Script Update:", event, ...)

		detailsFramework:QuickDispatch(callback, f.EncounterIDCached)
	end)
end

function detailsFramework:PassLoadFilters(loadTable, encounterID)
	--class
	local passLoadClass
	if (loadTable.class.Enabled) then
		local _, classFileName = UnitClass("player")
		if (not loadTable.class[classFileName]) then
			return false
		else
			passLoadClass = true
		end
	end

	--spec
	if (IS_WOW_PROJECT_MAINLINE and loadTable.spec.Enabled) then
		local canCheckTalents = true

		if (passLoadClass) then
			--if is allowed to load on this class, check if the talents isn't from another class
			local _, classFileName = UnitClass("player")
			local specsForThisClass = detailsFramework:GetClassSpecIDs(classFileName)

			canCheckTalents = false

			for _, specID in ipairs(specsForThisClass) do
				if (loadTable.spec[specID] or loadTable.spec[specID..""]) then
					--theres a talent for this class
					canCheckTalents = true
					break
				end
			end
		end

		if (canCheckTalents) then
			local specIndex = DetailsFramework.GetSpecialization()
			if (specIndex) then
				local specID = DetailsFramework.GetSpecializationInfo(specIndex)
				if not specID or (not loadTable.spec[specID] and not loadTable.spec[specID .. ""]) then
					return false
				end
			else
				return false
			end
		end
	end

	--race
	if (loadTable.race.Enabled) then
		local raceName, raceFileName, raceID = UnitRace ("player")
		if (not loadTable.race [raceFileName]) then
			return false
		end
	end

	--talents
	if (IS_WOW_PROJECT_MAINLINE and loadTable.talent.Enabled) then
		local talentsInUse = detailsFramework:GetCharacterTalents (false, true)
		local hasTalent
		for talentID, _ in pairs(talentsInUse) do
			if talentID and (loadTable.talent [talentID] or loadTable.talent [talentID .. ""]) then
				hasTalent =  true
				break
			end
		end
		if (not hasTalent) then
			return false
		end
	end

	--pvptalent
	if (IS_WOW_PROJECT_MAINLINE and loadTable.pvptalent.Enabled) then
		local talentsInUse = detailsFramework:GetCharacterPvPTalents (false, true)
		local hasTalent
		for talentID, _ in pairs(talentsInUse) do
			if talentID and (loadTable.pvptalent [talentID] or loadTable.pvptalent [talentID .. ""]) then
				hasTalent =  true
				break
			end
		end
		if (not hasTalent) then
			return false
		end
	end

	--group
	if (loadTable.group.Enabled) then
		local _, zoneType = GetInstanceInfo()
		if (not loadTable.group [zoneType]) then
			return
		end
	end

	--role
	if (loadTable.role.Enabled) then
		local assignedRole = UnitGroupRolesAssigned("player")
		if (assignedRole == "NONE") then
			local spec = DetailsFramework.GetSpecialization()
			if (spec) then
				assignedRole = DetailsFramework.GetSpecializationRole (spec)
			end
		end
		if (not loadTable.role [assignedRole]) then
			return false
		end
	end

	--affix
	if (IS_WOW_PROJECT_MAINLINE and loadTable.affix.Enabled) then
		local isInMythicDungeon = C_ChallengeMode.IsChallengeModeActive()
		if (not isInMythicDungeon) then
			return false
		end

		local level, affixes, wasEnergized = C_ChallengeMode.GetActiveKeystoneInfo()
		local hasAffix = false
		for _, affixID in ipairs(affixes) do
			if affixID and (loadTable.affix [affixID] or loadTable.affix [affixID .. ""]) then
				hasAffix = true
				break
			end
		end

		if (not hasAffix) then
			return false
		end
	end

	--encounter id
	if (loadTable.encounter_ids.Enabled) then
		if (not encounterID) then
			return
		end
		local hasEncounter
		for _, ID in pairs(loadTable.encounter_ids) do
			if (ID == encounterID) then
				hasEncounter = true
				break
			end
			if (not hasEncounter) then
				return false
			end
		end
	end

	--map id
	if (loadTable.map_ids.Enabled) then
		local _, _, _, _, _, _, _, zoneMapID = GetInstanceInfo()
		local uiMapID = C_Map.GetBestMapForUnit ("player")

		local hasMapID
		for _, ID in pairs(loadTable.map_ids) do
			if (ID == zoneMapID or ID == uiMapID) then
				hasMapID = true
				break
			end
			if (not hasMapID) then
				return false
			end
		end
	end

	return true
end

--this func will deploy the default values from the prototype into the config table
function detailsFramework:UpdateLoadConditionsTable(configTable)
	configTable = configTable or {}
	detailsFramework.table.deploy(configTable, default_load_conditions)
	return configTable
end

--/run Plater.OpenOptionsPanel()PlaterOptionsPanelContainer:SelectIndex (Plater, 14)

function detailsFramework:OpenLoadConditionsPanel(optionsTable, callback, frameOptions)
	frameOptions = frameOptions or {}
	detailsFramework.table.deploy(frameOptions, default_load_conditions_frame_options)

	detailsFramework:UpdateLoadConditionsTable(optionsTable)

	if (not DetailsFrameworkLoadConditionsPanel) then
		local loadConditionsFrame = detailsFramework:CreateSimplePanel(UIParent, 970, 505, "Load Conditions", "DetailsFrameworkLoadConditionsPanel")
		loadConditionsFrame:SetBackdropColor(0, 0, 0, 1)
		loadConditionsFrame.AllRadioGroups = {}
		loadConditionsFrame.AllTextEntries = {}
		loadConditionsFrame.OptionsTable = optionsTable

		detailsFramework:ApplyStandardBackdrop(loadConditionsFrame, false, 1.1)

		local xStartAt = 10
		local x2StartAt = 500
		local anchorPositions = {
			class = {xStartAt, -70},
			spec = {xStartAt, -170},
			race = {xStartAt, -210},
			role = {xStartAt, -310},
			talent = {xStartAt, -350},
			pvptalent = {x2StartAt, -70},
			group = {x2StartAt, -210},
			affix = {x2StartAt, -270},
			encounter_ids = {x2StartAt, -420},
			map_ids = {x2StartAt, -460},
		}

		local editingLabel = detailsFramework:CreateLabel(loadConditionsFrame, "Load Conditions For:")
		local editingWhatLabel = detailsFramework:CreateLabel(loadConditionsFrame, "")
		editingLabel:SetPoint("topleft", loadConditionsFrame, "topleft", 10, -35)
		editingWhatLabel:SetPoint("left", editingLabel, "right", 2, 0)

		--this label store the name of what is being edited
		loadConditionsFrame.EditingLabel = editingWhatLabel

		--when the user click on an option, run the callback
			loadConditionsFrame.RunCallback = function()
				detailsFramework:Dispatch (loadConditionsFrame.CallbackFunc)
			end

		--when the user click on an option or when the panel is opened
		--check if there's an option enabled and fadein all options, fadeout otherwise
			loadConditionsFrame.OnRadioStateChanged = function(radioGroup, subConfigTable)
				subConfigTable.Enabled = nil
				subConfigTable.Enabled = next (subConfigTable) and true or nil
				radioGroup:SetFadeState (subConfigTable.Enabled)
			end

		--create the radio group for character class
			loadConditionsFrame.OnRadioCheckboxClick = function(self, key, value)
				--hierarchy: DBKey ["class"] key ["HUNTER"] value TRUE
				local DBKey = self:GetParent().DBKey
				loadConditionsFrame.OptionsTable [DBKey] [key and key .. ""] = value and true or nil
				if not value then -- cleanup "number" type values
					loadConditionsFrame.OptionsTable [DBKey] [key] = nil
				end
				loadConditionsFrame.OnRadioStateChanged (self:GetParent(), loadConditionsFrame.OptionsTable [DBKey])
				loadConditionsFrame.RunCallback()
			end

		--create the radio group for classes
			local classes = {}
			for _, classTable in pairs(detailsFramework:GetClassList()) do
				tinsert(classes, {
					name = classTable.Name,
					set = loadConditionsFrame.OnRadioCheckboxClick,
					param = classTable.FileString,
					get = function() return loadConditionsFrame.OptionsTable.class[classTable.FileString] end,
					texture = classTable.Texture,
					texcoord = classTable.TexCoord,
				})
			end

			local classGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, classes, name, {width = 200, height = 200, title = "Character Class"}, {offset_x = 130, amount_per_line = 3})
			classGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.class[1], anchorPositions.class[2])
			classGroup.DBKey = "class"
			tinsert(loadConditionsFrame.AllRadioGroups, classGroup)

		--create the radio group for character spec
			if IS_WOW_PROJECT_MAINLINE then
				local specs = {}
				for _, specID in ipairs(detailsFramework:GetClassSpecIDs(select(2, UnitClass("player")))) do
					local specID, specName, specDescription, specIcon, specBackground, specRole, specClass = DetailsFramework.GetSpecializationInfoByID (specID)
					tinsert(specs, {
						name = specName,
						set = loadConditionsFrame.OnRadioCheckboxClick,
						param = specID,
						get = function() return loadConditionsFrame.OptionsTable.spec[specID] or loadConditionsFrame.OptionsTable.spec[specID..""] end,
						texture = specIcon,
					})
				end
				local specGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, specs, name, {width = 200, height = 200, title = "Character Spec"}, {offset_x = 130, amount_per_line = 4})
				specGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.spec[1], anchorPositions.spec[2])
				specGroup.DBKey = "spec"
				tinsert(loadConditionsFrame.AllRadioGroups, specGroup)
			end

		--create radio group for character races
			local raceList = {}
			for _, raceTable in ipairs(detailsFramework:GetCharacterRaceList()) do
				tinsert(raceList, {
					name = raceTable.Name,
					set = loadConditionsFrame.OnRadioCheckboxClick,
					param = raceTable.FileString,
					get = function() return loadConditionsFrame.OptionsTable.race [raceTable.FileString] end,
				})
			end
			local raceGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, raceList, name, {width = 200, height = 200, title = "Character Race"})
			raceGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.race [1], anchorPositions.race [2])
			raceGroup.DBKey = "race"
			tinsert(loadConditionsFrame.AllRadioGroups, raceGroup)

		--create radio group for talents
			if IS_WOW_PROJECT_MAINLINE then
				local talentList = {}
				for _, talentTable in ipairs(detailsFramework:GetCharacterTalents()) do
					if talentTable.ID then
						tinsert(talentList, {
							name = talentTable.Name,
							set = loadConditionsFrame.OnRadioCheckboxClick,
							param = talentTable.ID,
							get = function() return loadConditionsFrame.OptionsTable.talent [talentTable.ID] or loadConditionsFrame.OptionsTable.talent [talentTable.ID .. ""] end,
							texture = talentTable.Texture,
						})
					end
				end
				local talentGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, talentList, name, {width = 200, height = 200, title = "Characer Talents"}, {offset_x = 150, amount_per_line = 3})
				talentGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.talent [1], anchorPositions.talent [2])
				talentGroup.DBKey = "talent"
				tinsert(loadConditionsFrame.AllRadioGroups, talentGroup)
				loadConditionsFrame.TalentGroup = talentGroup

				do
					--create a frame to show talents selected in other specs or characters
					local otherTalents = CreateFrame("frame", nil, loadConditionsFrame, "BackdropTemplate")
					otherTalents:SetSize(26, 26)
					otherTalents:SetPoint("left", talentGroup.Title.widget, "right", 10, -2)
					otherTalents.Texture = detailsFramework:CreateImage(otherTalents, [[Interface\BUTTONS\AdventureGuideMicrobuttonAlert]], 24, 24)
					otherTalents.Texture:SetAllPoints()

					local removeTalent = function(_, _, talentID)
						loadConditionsFrame.OptionsTable.talent [talentID] = nil
						GameCooltip2:Hide()
						loadConditionsFrame.OnRadioStateChanged (talentGroup, loadConditionsFrame.OptionsTable [talentGroup.DBKey])
						loadConditionsFrame.CanShowTalentWarning()
					end

					local buildTalentMenu = function()
						local playerTalents = detailsFramework:GetCharacterTalents()
						local indexedTalents = {}
						for _, talentTable in ipairs(playerTalents) do
							tinsert(indexedTalents, talentTable.ID)
						end

						--talents selected to load
						GameCooltip2:AddLine("select a talent to remove it (added from a different spec or character)", "", 1, "orange", "orange", 9)
						GameCooltip2:AddLine("$div", nil, nil, -1, -1)

						for talentID, _ in pairs(loadConditionsFrame.OptionsTable.talent) do
							if (type(talentID) == "number" and not detailsFramework.table.find (indexedTalents, talentID)) then
								local talentID, name, texture, selected, available = GetTalentInfoByID (talentID)
								if (name) then
									GameCooltip2:AddLine(name)
									GameCooltip2:AddIcon (texture, 1, 1, 16, 16, .1, .9, .1, .9)
									GameCooltip2:AddMenu (1, removeTalent, talentID)
								end
							end
						end
					end

					otherTalents.CoolTip = {
						Type = "menu",
						BuildFunc = buildTalentMenu,
						OnEnterFunc = function(self) end,
						OnLeaveFunc = function(self) end,
						FixedValue = "none",
						ShowSpeed = 0.05,
						Options = function()
							GameCooltip2:SetOption("TextFont", "Friz Quadrata TT")
							GameCooltip2:SetOption("TextColor", "orange")
							GameCooltip2:SetOption("TextSize", 12)
							GameCooltip2:SetOption("FixedWidth", 220)
							GameCooltip2:SetOption("ButtonsYMod", -4)
							GameCooltip2:SetOption("YSpacingMod", -4)
							GameCooltip2:SetOption("IgnoreButtonAutoHeight", true)

							GameCooltip2:SetColor (1, 0.5, 0.5, 0.5, 0)

							local preset2_backdrop = {bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], edgeFile = [[Interface\Buttons\WHITE8X8]], tile = true, edgeSize = 1, tileSize = 16, insets = {left = 0, right = 0, top = 0, bottom = 0}}
							local gray_table = {0.37, 0.37, 0.37, 0.95}
							local black_table = {0.2, 0.2, 0.2, 1}
							GameCooltip2:SetBackdrop(1, preset2_backdrop, gray_table, black_table)
							GameCooltip2:SetBackdrop(2, preset2_backdrop, gray_table, black_table)
						end,
					}
					GameCooltip2:CoolTipInject (otherTalents)

					function loadConditionsFrame.CanShowTalentWarning()
						local playerTalents = detailsFramework:GetCharacterTalents()
						local indexedTalents = {}
						for _, talentTable in ipairs(playerTalents) do
							tinsert(indexedTalents, talentTable.ID)
						end
						for talentID, _ in pairs(loadConditionsFrame.OptionsTable.talent) do
							if (type(talentID) == "number" and not detailsFramework.table.find (indexedTalents, talentID)) then
								otherTalents:Show()
								return
							end
						end
						otherTalents:Hide()
					end
				end
			end

		--create radio group for pvp talents
			if IS_WOW_PROJECT_MAINLINE then
				local pvpTalentList = {}
				for _, talentTable in ipairs(detailsFramework:GetCharacterPvPTalents()) do
					tinsert(pvpTalentList, {
						name = talentTable.Name,
						set = loadConditionsFrame.OnRadioCheckboxClick,
						param = talentTable.ID,
						get = function() return loadConditionsFrame.OptionsTable.pvptalent [talentTable.ID] or loadConditionsFrame.OptionsTable.pvptalent [talentTable.ID .. ""] end,
						texture = talentTable.Texture,
					})
				end
				local pvpTalentGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, pvpTalentList, name, {width = 200, height = 200, title = "Characer PvP Talents"}, {offset_x = 150, amount_per_line = 3})
				pvpTalentGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.pvptalent [1], anchorPositions.pvptalent [2])
				pvpTalentGroup.DBKey = "pvptalent"
				tinsert(loadConditionsFrame.AllRadioGroups, pvpTalentGroup)
				loadConditionsFrame.PvPTalentGroup = pvpTalentGroup

				do
					--create a frame to show talents selected in other specs or characters
					local otherTalents = CreateFrame("frame", nil, loadConditionsFrame, "BackdropTemplate")
					otherTalents:SetSize(26, 26)
					otherTalents:SetPoint("left", pvpTalentGroup.Title.widget, "right", 10, -2)
					otherTalents.Texture = detailsFramework:CreateImage(otherTalents, [[Interface\BUTTONS\AdventureGuideMicrobuttonAlert]], 24, 24)
					otherTalents.Texture:SetAllPoints()

					local removeTalent = function(_, _, talentID)
						loadConditionsFrame.OptionsTable.pvptalent [talentID] = nil
						GameCooltip2:Hide()
						loadConditionsFrame.OnRadioStateChanged (pvpTalentGroup, loadConditionsFrame.OptionsTable [pvpTalentGroup.DBKey])
						loadConditionsFrame.CanShowPvPTalentWarning()
					end

					local buildTalentMenu = function()
						local playerTalents = detailsFramework:GetCharacterPvPTalents()
						local indexedTalents = {}
						for _, talentTable in ipairs(playerTalents) do
							tinsert(indexedTalents, talentTable.ID)
						end

						--talents selected to load
						GameCooltip2:AddLine("select a talent to remove it (added from a different spec or character)", "", 1, "orange", "orange", 9)
						GameCooltip2:AddLine("$div", nil, nil, -1, -1)

						for talentID, _ in pairs(loadConditionsFrame.OptionsTable.pvptalent) do
							if (type(talentID) == "number" and not detailsFramework.table.find (indexedTalents, talentID)) then
								local _, name, texture = GetPvpTalentInfoByID (talentID)
								if (name) then
									GameCooltip2:AddLine(name)
									GameCooltip2:AddIcon (texture, 1, 1, 16, 16, .1, .9, .1, .9)
									GameCooltip2:AddMenu (1, removeTalent, talentID)
								end
							end
						end
					end

					otherTalents.CoolTip = {
						Type = "menu",
						BuildFunc = buildTalentMenu,
						OnEnterFunc = function(self) end,
						OnLeaveFunc = function(self) end,
						FixedValue = "none",
						ShowSpeed = 0.05,
						Options = function()
							GameCooltip2:SetOption("TextFont", "Friz Quadrata TT")
							GameCooltip2:SetOption("TextColor", "orange")
							GameCooltip2:SetOption("TextSize", 12)
							GameCooltip2:SetOption("FixedWidth", 220)
							GameCooltip2:SetOption("ButtonsYMod", -4)
							GameCooltip2:SetOption("YSpacingMod", -4)
							GameCooltip2:SetOption("IgnoreButtonAutoHeight", true)

							GameCooltip2:SetColor (1, 0.5, 0.5, 0.5, 0)

							local preset2_backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeFile = [[Interface\Buttons\WHITE8X8]], tile = true, edgeSize = 1, tileSize = 16, insets = {left = 0, right = 0, top = 0, bottom = 0}}
							local gray_table = {0.37, 0.37, 0.37, 0.95}
							local black_table = {0.2, 0.2, 0.2, 1}
							GameCooltip2:SetBackdrop(1, preset2_backdrop, gray_table, black_table)
							GameCooltip2:SetBackdrop(2, preset2_backdrop, gray_table, black_table)
						end,
					}
					GameCooltip2:CoolTipInject (otherTalents)

					function loadConditionsFrame.CanShowPvPTalentWarning()
						local playerTalents = detailsFramework:GetCharacterPvPTalents()
						local indexedTalents = {}
						for _, talentTable in ipairs(playerTalents) do
							tinsert(indexedTalents, talentTable.ID)
						end
						for talentID, _ in pairs(loadConditionsFrame.OptionsTable.pvptalent) do
							if (type(talentID) == "number" and not detailsFramework.table.find (indexedTalents, talentID)) then
								otherTalents:Show()
								return
							end
						end
						otherTalents:Hide()
					end
				end
			end

		--create radio for group types
			local groupTypes = {}
			for _, groupTable in ipairs(detailsFramework:GetGroupTypes()) do
				tinsert(groupTypes, {
					name = groupTable.Name,
					set = loadConditionsFrame.OnRadioCheckboxClick,
					param = groupTable.ID,
					get = function() return loadConditionsFrame.OptionsTable.group [groupTable.ID] or loadConditionsFrame.OptionsTable.group [groupTable.ID .. ""] end,
				})
			end
			local groupTypesGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, groupTypes, name, {width = 200, height = 200, title = "Group Types"})
			groupTypesGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.group [1], anchorPositions.group [2])
			groupTypesGroup.DBKey = "group"
			tinsert(loadConditionsFrame.AllRadioGroups, groupTypesGroup)

		--create radio for character roles
			local roleTypes = {}
			for _, roleTable in ipairs(detailsFramework:GetRoleTypes()) do
				tinsert(roleTypes, {
					name = roleTable.Texture .. " " .. roleTable.Name,
					set = loadConditionsFrame.OnRadioCheckboxClick,
					param = roleTable.ID,
					get = function() return loadConditionsFrame.OptionsTable.role [roleTable.ID] or loadConditionsFrame.OptionsTable.role [roleTable.ID .. ""] end,
				})
			end
			local roleTypesGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, roleTypes, name, {width = 200, height = 200, title = "Role Types"})
			roleTypesGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.role [1], anchorPositions.role [2])
			roleTypesGroup.DBKey = "role"
			tinsert(loadConditionsFrame.AllRadioGroups, roleTypesGroup)

		--create radio group for mythic+ affixes
			if IS_WOW_PROJECT_MAINLINE then
				local affixes = {}
				for i = 2, 1000 do
					local affixName, desc, texture = C_ChallengeMode.GetAffixInfo (i)
					if (affixName) then
						tinsert(affixes, {
							name = affixName,
							set = loadConditionsFrame.OnRadioCheckboxClick,
							param = i,
							get = function() return loadConditionsFrame.OptionsTable.affix [i] or loadConditionsFrame.OptionsTable.affix [i .. ""] end,
							texture = texture,
						})
					end
				end
				local affixTypesGroup = detailsFramework:CreateCheckboxGroup (loadConditionsFrame, affixes, name, {width = 200, height = 200, title = "M+ Affixes"})
				affixTypesGroup:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.affix [1], anchorPositions.affix [2])
				affixTypesGroup.DBKey = "affix"
				tinsert(loadConditionsFrame.AllRadioGroups, affixTypesGroup)
			end

		--text entries functions
			local textEntryRefresh = function(self)
				local idList = loadConditionsFrame.OptionsTable [self.DBKey]
				self:SetText("")
				for _, id in pairs(idList) do
					if tonumber(id) then
						self:SetText(self:GetText() .. " " .. id)
					end
				end
				self:SetText(self:GetText():gsub("^ ", ""))
			end

			local textEntryOnEnterPressed = function(_, self)
				wipe (loadConditionsFrame.OptionsTable [self.DBKey])
				local text = self:GetText()

				for _, ID in ipairs({strsplit(" ", text)}) do
					ID = detailsFramework:trim (ID)
					ID = tonumber(ID)
					if (ID) then
						tinsert(loadConditionsFrame.OptionsTable [self.DBKey], ID)
						loadConditionsFrame.OptionsTable [self.DBKey].Enabled = true
					end
				end
			end

		--create the text entry to type the encounter ID
			local encounterIDLabel = detailsFramework:CreateLabel(loadConditionsFrame, "Encounter ID", detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
			local encounterIDEditbox = detailsFramework:CreateTextEntry(loadConditionsFrame, function()end, 200, 20, "EncounterEditbox", _, _, detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE"))
			encounterIDLabel:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.encounter_ids [1], anchorPositions.encounter_ids [2])
			encounterIDEditbox:SetPoint("topleft", encounterIDLabel, "bottomleft", 0, -2)
			encounterIDEditbox.DBKey = "encounter_ids"
			encounterIDEditbox.Refresh = textEntryRefresh
			encounterIDEditbox.tooltip = "Enter multiple IDs separating with a whitespace.\nExample: 35 45 95\n\nSanctum of Domination:\n"
			for _, encounterTable in ipairs(detailsFramework:GetCLEncounterIDs()) do
				encounterIDEditbox.tooltip = encounterIDEditbox.tooltip .. encounterTable.ID .. " - " .. encounterTable.Name .. "\n"
			end
			encounterIDEditbox:SetHook("OnEnterPressed", textEntryOnEnterPressed)
			tinsert(loadConditionsFrame.AllTextEntries, encounterIDEditbox)

		--create the text entry for map ID
			local mapIDLabel = detailsFramework:CreateLabel(loadConditionsFrame, "Map ID", detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
			local mapIDEditbox = detailsFramework:CreateTextEntry(loadConditionsFrame, function()end, 200, 20, "MapEditbox", _, _, detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE"))
			mapIDLabel:SetPoint("topleft", loadConditionsFrame, "topleft", anchorPositions.map_ids [1], anchorPositions.map_ids [2])
			mapIDEditbox:SetPoint("topleft", mapIDLabel, "bottomleft", 0, -2)
			mapIDEditbox.DBKey = "map_ids"
			mapIDEditbox.Refresh = textEntryRefresh
			mapIDEditbox.tooltip = "Enter multiple IDs separating with a whitespace\nExample: 35 45 95"
			mapIDEditbox:SetHook("OnEnterPressed", textEntryOnEnterPressed)
			tinsert(loadConditionsFrame.AllTextEntries, mapIDEditbox)

		function loadConditionsFrame.Refresh (self)
			if IS_WOW_PROJECT_MAINLINE then
				--update the talents (might have changed if the player changed its specialization)
				local talentList = {}
				for _, talentTable in ipairs(detailsFramework:GetCharacterTalents()) do
					if talentTable.ID then
						tinsert(talentList, {
							name = talentTable.Name,
							set = DetailsFrameworkLoadConditionsPanel.OnRadioCheckboxClick,
							param = talentTable.ID,
							get = function() return DetailsFrameworkLoadConditionsPanel.OptionsTable.talent [talentTable.ID] or DetailsFrameworkLoadConditionsPanel.OptionsTable.talent [talentTable.ID .. ""] end,
							texture = talentTable.Texture,
						})
					end
				end
				DetailsFrameworkLoadConditionsPanel.TalentGroup:SetOptions (talentList)
			end

			if IS_WOW_PROJECT_MAINLINE then
				local pvpTalentList = {}
				for _, talentTable in ipairs(detailsFramework:GetCharacterPvPTalents()) do
					tinsert(pvpTalentList, {
						name = talentTable.Name,
						set = DetailsFrameworkLoadConditionsPanel.OnRadioCheckboxClick,
						param = talentTable.ID,
						get = function() return DetailsFrameworkLoadConditionsPanel.OptionsTable.pvptalent [talentTable.ID] or DetailsFrameworkLoadConditionsPanel.OptionsTable.pvptalent [talentTable.ID .. ""] end,
						texture = talentTable.Texture,
					})
				end
				DetailsFrameworkLoadConditionsPanel.PvPTalentGroup:SetOptions (pvpTalentList)
			end

			--refresh the radio group
			for _, radioGroup in ipairs(DetailsFrameworkLoadConditionsPanel.AllRadioGroups) do
				radioGroup:Refresh()
				DetailsFrameworkLoadConditionsPanel.OnRadioStateChanged (radioGroup, DetailsFrameworkLoadConditionsPanel.OptionsTable [radioGroup.DBKey])
			end

			--refresh text entries
			for _, textEntry in ipairs(DetailsFrameworkLoadConditionsPanel.AllTextEntries) do
				textEntry:Refresh()
			end

			if IS_WOW_PROJECT_MAINLINE then
				DetailsFrameworkLoadConditionsPanel.CanShowTalentWarning()
				DetailsFrameworkLoadConditionsPanel.CanShowPvPTalentWarning()
			end
		end

	end

	--set the options table
	DetailsFrameworkLoadConditionsPanel.OptionsTable = optionsTable

	--set the callback func
	DetailsFrameworkLoadConditionsPanel.CallbackFunc = callback
	DetailsFrameworkLoadConditionsPanel.OptionsTable = optionsTable

	--set title
	DetailsFrameworkLoadConditionsPanel.EditingLabel:SetText(frameOptions.name)
	DetailsFrameworkLoadConditionsPanel.Title:SetText(frameOptions.title)

	--show the panel to the user
	DetailsFrameworkLoadConditionsPanel:Show()

	DetailsFrameworkLoadConditionsPanel:Refresh()
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--simple data scroll

detailsFramework.DataScrollFunctions = {
	RefreshScroll = function(self, data, offset, totalLines)
		local filter = self.Filter
		local currentData = {}
		if (type(filter) == "string" and filter ~= "") then
			for i = 1, #data do
				for o = 1, #data[i] do
					if (data[i][o]:find(filter)) then
						tinsert(currentData, data[i])
						break
					end
				end
			end
		else
			currentData = data
		end

		if (self.SortAlphabetical) then
			table.sort (currentData, function(t1, t2) return t1[1] < t2[1] end)
		end

		--update the scroll
		for i = 1, totalLines do
			local index = i + offset
			local thisData = currentData [index]
			if (thisData) then
				local line = self:GetLine (i)
				line:Update (index, thisData)
			end
		end
	end,

	CreateLine = function(self, index)
		--create a new line
		local line = CreateFrame("button", "$parentLine" .. index, self, "BackdropTemplate")
		line.Update = self.options.update_line_func

		--set its parameters
		line:SetPoint("topleft", self, "topleft", 1, -((index-1) * (self.options.line_height+1)) - 1)
		line:SetSize(self.options.width - 2, self.options.line_height)
		line:RegisterForClicks ("LeftButtonDown", "RightButtonDown")

		line:SetScript("OnEnter",	self.options.on_enter)
		line:SetScript("OnLeave",	self.options.on_leave)
		line:SetScript("OnClick",	self.options.on_click)

		line:SetBackdrop(self.options.backdrop)
		line:SetBackdropColor(unpack(self.options.backdrop_color))
		line:SetBackdropBorderColor(unpack(self.options.backdrop_border_color))

		local title = detailsFramework:CreateLabel(line, "", detailsFramework:GetTemplate("font", self.options.title_template))
		local date = detailsFramework:CreateLabel(line, "", detailsFramework:GetTemplate("font", self.options.title_template))
		local text = detailsFramework:CreateLabel(line, "", detailsFramework:GetTemplate("font", self.options.text_tempate))

		title.textsize = 14
		date.textsize = 14
		text:SetSize(self.options.width - 20, self.options.line_height)
		text:SetJustifyV ("top")

		--setup anchors
		if (self.options.show_title) then
			title:SetPoint("topleft", line, "topleft", 2, 0)
			date:SetPoint("topright", line, "topright", -2, 0)
			text:SetPoint("topleft", title, "bottomleft", 0, -4)
		else
			text:SetPoint("topleft", line, "topleft", 2, 0)
		end

		line.Title = title
		line.Date = date
		line.Text = text

		line.backdrop_color = self.options.backdrop_color or {.1, .1, .1, .3}
		line.backdrop_color_highlight = self.options.backdrop_color_highlight or {.3, .3, .3, .5}

		return line
	end,

	LineOnEnter = function(self)
		self:SetBackdropColor(unpack(self.backdrop_color_highlight))
	end,
	LineOnLeave = function(self)
		self:SetBackdropColor(unpack(self.backdrop_color))
	end,

	OnClick = function(self)

	end,

	UpdateLine = function(line, lineIndex, data)
		local parent = line:GetParent()

		if (parent.options.show_title) then
			line.Title.text = data [2] or ""
			line.Date.text = data [3] or ""
			line.Text.text = data [4] or ""
		else
			line.Text.text = data [2] or ""
		end

		if (line:GetParent().OnUpdateLineHook) then
			detailsFramework:CoreDispatch((line:GetName() or "ScrollBoxDataScrollUpdateLineHook") .. ":UpdateLineHook()", line:GetParent().OnUpdateLineHook, line, lineIndex, data)
		end
	end,
}

local default_datascroll_options = {
	width = 400,
	height = 700,
	line_amount = 10,
	line_height = 20,

	show_title = true,

	backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	backdrop_color = {0, 0, 0, 0.2},
	backdrop_color_highlight = {.2, .2, .2, 0.4},
	backdrop_border_color = {0.1, 0.1, 0.1, .2},

	title_template = "ORANGE_FONT_TEMPLATE",
	text_tempate = "OPTIONS_FONT_TEMPLATE",

	create_line_func = detailsFramework.DataScrollFunctions.CreateLine,
	update_line_func = detailsFramework.DataScrollFunctions.UpdateLine,
	refresh_func = detailsFramework.DataScrollFunctions.RefreshScroll,
	on_enter = detailsFramework.DataScrollFunctions.LineOnEnter,
	on_leave = detailsFramework.DataScrollFunctions.LineOnLeave,
	on_click =  detailsFramework.DataScrollFunctions.OnClick,

	data = {},
}

--[=[
	Create a scroll frame to show text in an organized way
	Functions in the options table can be overritten to customize the layout
	@parent = the parent of the frame
	@name = the frame name to use in the CreateFrame call
	@options = options table to override default values from the table above
--]=]
function detailsFramework:CreateDataScrollFrame (parent, name, options)
	--call the mixin with a dummy table to built the default options before the frame creation
	--this is done because CreateScrollBox needs parameters at creation time
	local optionsTable = {}
	detailsFramework.OptionsFunctions.BuildOptionsTable (optionsTable, default_datascroll_options, options)
	optionsTable = optionsTable.options

	--scroll frame
	local newScroll = detailsFramework:CreateScrollBox (parent, name, optionsTable.refresh_func, optionsTable.data, optionsTable.width, optionsTable.height, optionsTable.line_amount, optionsTable.line_height)
	detailsFramework:ReskinSlider(newScroll)

	detailsFramework:Mixin(newScroll, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(newScroll, detailsFramework.LayoutFrame)

	newScroll:BuildOptionsTable (default_datascroll_options, options)

	--create the scrollbox lines
	for i = 1, newScroll.options.line_amount do
		newScroll:CreateLine (newScroll.options.create_line_func)
	end

	newScroll:Refresh()

	return newScroll
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--"WHAT's NEW" window

local default_newsframe_options = {
	width = 400,
	height = 700,

	line_amount = 16,
	line_height = 40,

	backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	backdrop_color = {0, 0, 0, 0.2},
	backdrop_border_color = {0.1, 0.1, 0.1, .2},

	title = "What's New?",
	show_title = true,
}

detailsFramework.NewsFrameFunctions = {

}

--[=[
	Get the amount of news that the player didn't see yet
	@newsTable = an indexed table of tables
	@lastNewsTime = last time the player opened the news window
--]=]
function detailsFramework:GetNumNews (newsTable, lastNewsTime)
	local now = time()
	local nonReadNews = 0

	for _, news in ipairs(newsTable) do
		if (news[1] > lastNewsTime) then
			nonReadNews = nonReadNews + 1
		end
	end

	return nonReadNews
end

--[=[
	Creates a panel with a scroll to show texts organized in separated lines
	@parent =  the parent of the frame
	@name = the frame name to use in the CreateFrame call
	@options = options table to override default values from the table above
	@newsTable = an indexed table of tables
	@db = (optional) an empty table from the addon database to store the position of the frame between game sessions
--]=]
function detailsFramework:CreateNewsFrame (parent, name, options, newsTable, db)

	local f = detailsFramework:CreateSimplePanel(parent, 400, 700, options and options.title or default_newsframe_options.title, name, {UseScaleBar = db and true}, db)
	f:SetFrameStrata("MEDIUM")
	detailsFramework:ApplyStandardBackdrop(f)

	detailsFramework:Mixin(f, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(f, detailsFramework.LayoutFrame)

	f:BuildOptionsTable (default_newsframe_options, options)

	f:SetSize(f.options.width, f.options.height)
	f:SetBackdrop(f.options.backdrop)
	f:SetBackdropColor(unpack(f.options.backdrop_color))
	f:SetBackdropBorderColor(unpack(f.options.backdrop_border_color))

	local scrollOptions = {
		data = newsTable,
		width = f.options.width - 32, --frame distance from walls and scroll bar space
		height = f.options.height - 40 + (not f.options.show_title and 20 or 0),
		line_amount = f.options.line_amount,
		line_height = f.options.line_height,
	}
	local newsScroll = detailsFramework:CreateDataScrollFrame (f, "$parentScroll", scrollOptions)

	if (not f.options.show_title) then
		f.TitleBar:Hide()
		newsScroll:SetPoint("topleft", f, "topleft", 5, -10)
	else
		newsScroll:SetPoint("topleft", f, "topleft", 5, -30)
	end

	f.NewsScroll = newsScroll

	return f
end



------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--statusbar info

--[[
	authorTable = {
		{
			authorName = "author name 1",
			link = "twitter.com/author1Handle",
		}
	}
]]

function detailsFramework:BuildStatusbarAuthorInfo(f, addonBy, authorsNameString)
	local authorName = detailsFramework:CreateLabel(f, "" .. (addonBy or "An addon by ") .. "|cFFFFFFFF" .. (authorsNameString or "Terciob") .. "|r")
	authorName.textcolor = "silver"
	local discordLabel = detailsFramework:CreateLabel(f, "Discord: ")
	discordLabel.textcolor = "silver"

	local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")
	local discordTextEntry = detailsFramework:CreateTextEntry(f, function()end, 200, 18, "DiscordTextBox", _, _, options_dropdown_template)
	discordTextEntry:SetText("https://discord.gg/AGSzAZX")
	discordTextEntry:SetFrameLevel(5000)

	authorName:SetPoint("left", f, "left", 2, 0)
	discordLabel:SetPoint("left", authorName, "right", 20, 0)
	discordTextEntry:SetPoint("left", discordLabel, "right", 2, 0)

	--format
	authorName:SetAlpha(.6)
	discordLabel:SetAlpha(.6)
	discordTextEntry:SetAlpha(.6)
	discordTextEntry:SetBackdropBorderColor(1, 1, 1, 0)

	discordTextEntry:SetHook("OnEditFocusGained", function()
		discordTextEntry:HighlightText()
	end)

	f.authorName = authorName
	f.discordLabel = discordLabel
	f.discordTextEntry = discordTextEntry
end

local statusbar_default_options = {
	attach = "bottom", --bottomleft from statusbar attach to bottomleft of the frame | other option is "top": topleft attach to bottomleft
}

function detailsFramework:CreateStatusBar(f, options)
	local statusBar = CreateFrame("frame", nil, f, "BackdropTemplate")

	detailsFramework:Mixin(statusBar, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(statusBar, detailsFramework.LayoutFrame)

	statusBar:BuildOptionsTable (statusbar_default_options, options)

	if (statusBar.options.attach == "bottom") then
		statusBar:SetPoint("bottomleft", f, "bottomleft")
		statusBar:SetPoint("bottomright", f, "bottomright")

	else
		statusBar:SetPoint("topleft", f, "bottomleft")
		statusBar:SetPoint("topright", f, "bottomright")
	end

	statusBar:SetHeight(20)
	detailsFramework:ApplyStandardBackdrop(statusBar)
	statusBar:SetAlpha(0.8)

	return statusBar
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--statusbar mixin

--[=[
	collection of functions to embed into a statusbar
	statusBar:GetTexture()
	statusBar:SetTexture(texture)
	statusBar:SetColor (unparsed color)
	statusBar:GetColor()
	statusBar:
	statusBar:

--]=]

detailsFramework.StatusBarFunctions = {
	SetTexture = function(self, texture, isTemporary)
		self.barTexture:SetTexture(texture)
		if (not isTemporary) then
			self.barTexture.currentTexture = texture
		end
	end,

	ResetTexture = function(self)
		self.barTexture:SetTexture(self.barTexture.currentTexture)
	end,

	GetTexture = function(self)
		return self.barTexture:GetTexture()
	end,

	SetAtlas = function(self, atlasName)
		self.barTexture:SetAtlas(atlasName)
	end,

	GetAtlas = function(self)
		self.barTexture:GetAtlas()
	end,

	SetTexCoord = function(self, ...)
		return self.barTexture:SetTexCoord(...)
	end,

	GetTexCoord = function(self)
		return self.barTexture:GetTexCoord()
	end,

	SetColor = function(self, r, g, b, a)
		r, g, b, a = detailsFramework:ParseColors(r, g, b, a)
		self:SetStatusBarColor(r, g, b, a)
	end,

	GetColor = function(self)
		return self:GetStatusBarColor()
	end,

	SetMaskTexture = function(self, ...)
		if (not self:HasTextureMask()) then
			return
		end
		self.barTextureMask:SetTexture(...)
	end,

	GetMaskTexture = function(self)
		if (not self:HasTextureMask()) then
			return
		end
		self.barTextureMask:GetTexture()
	end,

	--SetMaskTexCoord = function(self, ...) --MaskTexture doesn't not support texcoord
	--	if (not self:HasTextureMask()) then
	--		return
	--	end
	--	self.barTextureMask:SetTexCoord(...)
	--end,

	--GetMaskTexCoord = function(self, ...)
	--	if (not self:HasTextureMask()) then
	--		return
	--	end
	--	self.barTextureMask:GetTexCoord()
	--end,

	SetMaskAtlas = function(self, atlasName)
		if (not self:HasTextureMask()) then
			return
		end
		self.barTextureMask:SetAtlas(atlasName)
	end,

	GetMaskAtlas = function(self)
		if (not self:HasTextureMask()) then
			return
		end
		self.barTextureMask:GetAtlas()
	end,

	AddMaskTexture = function(self, object)
		if (not self:HasTextureMask()) then
			return
		end
		if (object.GetObjectType and object:GetObjectType() == "Texture") then
			object:AddMaskTexture(self.barTextureMask)
		else
			detailsFramework:Msg("Invalid 'Texture' to object:AddMaskTexture(Texture)", debugstack())
		end
	end,

	CreateTextureMask = function(self)
		local barTexture = self:GetStatusBarTexture() or self.barTexture
		if (not barTexture) then
			detailsFramework:Msg("Object doesn't not have a statubar texture, create one and object:SetStatusBarTexture(textureObject)", debugstack())
			return
		end

		if (self.barTextureMask) then
			return self.barTextureMask
		end

		--statusbar texture mask
		self.barTextureMask = self:CreateMaskTexture(nil, "artwork")
		self.barTextureMask:SetAllPoints()
		self.barTextureMask:SetTexture([[Interface\CHATFRAME\CHATFRAMEBACKGROUND]])

		--border texture
		self.barBorderTextureForMask = self:CreateTexture(nil, "artwork", nil, 7)
		self.barBorderTextureForMask:SetAllPoints()
		self.barBorderTextureForMask:Hide()

		barTexture:AddMaskTexture(self.barTextureMask)

		return self.barTextureMask
	end,

	HasTextureMask = function(self)
		if (not self.barTextureMask) then
			detailsFramework:Msg("Object doesn't not have a texture mask, create one using object:CreateTextureMask()", debugstack())
			return false
		end
		return true
	end,

	SetBorderTexture = function(self, texture)
		if (not self:HasTextureMask()) then
			return
		end

		texture = texture or ""

		self.barBorderTextureForMask:SetTexture(texture)

		if (texture == "") then
			self.barBorderTextureForMask:Hide()
		else
			self.barBorderTextureForMask:Show()
		end
	end,

	GetBorderTexture = function(self)
		if (not self:HasTextureMask()) then
			return
		end
		return self.barBorderTextureForMask:GetTexture()
	end,

	SetBorderColor = function(self, r, g, b, a)
		r, g, b, a = detailsFramework:ParseColors(r, g, b, a)

		if (self.barBorderTextureForMask and self.barBorderTextureForMask:IsShown()) then
			self.barBorderTextureForMask:SetVertexColor(r, g, b, a)

			--if there's a square border on the widget, remove its color
			if (self.border and self.border.UpdateSizes and self.border.SetVertexColor) then
				self.border:SetVertexColor(0, 0, 0, 0)
			end

			return
		end

		if (self.border and self.border.UpdateSizes and self.border.SetVertexColor) then
			self.border:SetVertexColor(r, g, b, a)

			--adjust the mask border texture ask well in case the user set the mask color texture before setting a texture on it
			if (self.barBorderTextureForMask) then
				self.barBorderTextureForMask:SetVertexColor(r, g, b, a)
			end
			return
		end
	end,

	GetBorderColor = function(self)
		if (self.barBorderTextureForMask and self.barBorderTextureForMask:IsShown()) then
			return self.barBorderTextureForMask:GetVertexColor()
		end

		if (self.border and self.border.UpdateSizes and self.border.GetVertexColor) then
			return self.border:GetVertexColor()
		end
	end,
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--health bar frame

--[=[
	DF:CreateHealthBar (parent, name, settingsOverride)
	creates a health bar to show an unit health
	@parent = frame to pass for the CreateFrame function
	@name = absolute name of the frame, if omitted it uses the parent's name .. "HealthBar"
	@settingsOverride = table with keys and values to replace the defaults from the framework

	methods:
	healthbar:SetUnit(unit)
	healthBar:GetTexture()
	healthBar:SetTexture(texture)
--]=]

--debug performance isn't placed anywhere
--how to use debug performance: I don't remember

	--Details:Dump (debugPerformance)

	local debugPerformance = {
		eventCall = {},
		unitCall = {},
		functionCall = {},
		CPUUsageByFunction = {},
	}

	local function CalcPerformance (type, data)
		if (type == "event") then
			debugPerformance.eventCall [data] = (debugPerformance.eventCall [data] or 0) + 1

		elseif (type == "unit") then
			debugPerformance.unitCall [data] = (debugPerformance.unitCall [data] or 0) + 1

		elseif (type == "call") then
			debugPerformance.functionCall [data] = (debugPerformance.functionCall [data] or 0) + 1

		end
	end

	function DF_CalcCpuUsage (name)
		local cpu = debugPerformance.CPUUsageByFunction [name] or {usage = 0, last = 0, active = false}
		debugPerformance.CPUUsageByFunction [name] = cpu

		if (cpu.active) then
			cpu.active = false
			local diff = debugprofilestop() - cpu.last
			cpu.usage = cpu.usage + diff
		else
			cpu.active = true
			cpu.last = debugprofilestop()
		end
	end

	function UnitFrameStats()
		for functionName, functionTable in pairs(debugPerformance.CPUUsageByFunction) do
			debugPerformance.CPUUsageByFunction [functionName] = floor(functionTable.usage)
		end

		for functionName, functionTable in pairs(debugPerformance.CPUUsageByFunction) do
			debugPerformance.CPUUsageByFunction [functionName] = {usage = 0, last = 0, active = false}
		end
	end
--end of performance calcs

--healthBar meta prototype
	local healthBarMetaPrototype = {
		WidgetType = "healthBar",
		dversion = detailsFramework.dversion,
	}

	--check if there's a metaPrototype already existing
	if (_G[detailsFramework.GlobalWidgetControlNames["healthBar"]]) then
		--get the already existing metaPrototype
		local oldMetaPrototype = _G[detailsFramework.GlobalWidgetControlNames ["healthBar"]]
		--check if is older
		if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < detailsFramework.dversion) ) then
			--the version is older them the currently loading one
			--copy the new values into the old metatable
			for funcName, _ in pairs(healthBarMetaPrototype) do
				oldMetaPrototype[funcName] = healthBarMetaPrototype[funcName]
			end
		end
	else
		--first time loading the framework
		_G[detailsFramework.GlobalWidgetControlNames["healthBar"]] = healthBarMetaPrototype
	end

	local healthBarMetaFunctions = _G[detailsFramework.GlobalWidgetControlNames["healthBar"]]
	detailsFramework:Mixin(healthBarMetaFunctions, detailsFramework.ScriptHookMixin)

--hook list
	local defaultHooksForHealthBar = {
		OnHide = {},
		OnShow = {},
		OnHealthChange = {},
		OnHealthMaxChange = {},
	}

	--use the hook already existing
	healthBarMetaFunctions.HookList = healthBarMetaFunctions.HookList or defaultHooksForHealthBar
	--copy the non existing values from a new version to the already existing hook table
	detailsFramework.table.deploy(healthBarMetaFunctions.HookList, defaultHooksForHealthBar)

--Health Bar Meta Functions

	--health bar settings
	healthBarMetaFunctions.Settings = {
		CanTick = false, --if true calls the method 'OnTick' every tick, the function needs to be overloaded, it receives self and deltaTime as parameters
		ShowHealingPrediction = true, --when casting a healing pass, show the amount of health that spell will heal
		ShowShields = true, --indicator of the amount of damage absortion the unit has

		--appearance
		BackgroundColor = detailsFramework:CreateColorTable (.2, .2, .2, .8),
		Texture = [[Interface\RaidFrame\Raid-Bar-Hp-Fill]],
		ShieldIndicatorTexture = [[Interface\RaidFrame\Shield-Fill]],
		ShieldGlowTexture = [[Interface\RaidFrame\Shield-Overshield]],
		ShieldGlowWidth = 16,

		--default size
		Width = 100,
		Height = 20,
	}

	healthBarMetaFunctions.HealthBarEvents = {
		{"PLAYER_ENTERING_WORLD"},
		{"UNIT_HEALTH", true},
		{"UNIT_MAXHEALTH", true},
		{(IS_WOW_PROJECT_NOT_MAINLINE) and "UNIT_HEALTH_FREQUENT", true}, -- this one is classic-only...
		{"UNIT_HEAL_PREDICTION", true},
		{(IS_WOW_PROJECT_MAINLINE) and "UNIT_ABSORB_AMOUNT_CHANGED", true},
		{(IS_WOW_PROJECT_MAINLINE) and "UNIT_HEAL_ABSORB_AMOUNT_CHANGED", true},
	}

	--setup the castbar to be used by another unit
	healthBarMetaFunctions.SetUnit = function(self, unit, displayedUnit)
		if (self.unit ~= unit or self.displayedUnit ~= displayedUnit or unit == nil) then

			self.unit = unit
			self.displayedUnit = displayedUnit or unit

			--register events
			if (unit) then
				self.currentHealth = UnitHealth (unit) or 0
				self.currentHealthMax = UnitHealthMax (unit) or 0

				for _, eventTable in ipairs(self.HealthBarEvents) do
					local event = eventTable [1]
					local isUnitEvent = eventTable [2]
					if event then
						if (isUnitEvent) then
							self:RegisterUnitEvent (event, self.displayedUnit, self.unit)
						else
							self:RegisterEvent(event)
						end
					end
				end

				--check for settings and update some events
				if (not self.Settings.ShowHealingPrediction) then
					self:UnregisterEvent ("UNIT_HEAL_PREDICTION")
					if IS_WOW_PROJECT_MAINLINE then
						self:UnregisterEvent ("UNIT_HEAL_ABSORB_AMOUNT_CHANGED")
					end
					self.incomingHealIndicator:Hide()
					self.healAbsorbIndicator:Hide()
				end
				if (not self.Settings.ShowShields) then
					if IS_WOW_PROJECT_MAINLINE then
						self:UnregisterEvent ("UNIT_ABSORB_AMOUNT_CHANGED")
					end
					self.shieldAbsorbIndicator:Hide()
					self.shieldAbsorbGlow:Hide()
				end

				--set scripts
				self:SetScript("OnEvent", self.OnEvent)

				if (self.Settings.CanTick) then
					self:SetScript("OnUpdate", self.OnTick)
				end

				self:PLAYER_ENTERING_WORLD (self.unit, self.displayedUnit)
			else
				--remove all registered events
				for _, eventTable in ipairs(self.HealthBarEvents) do
					local event = eventTable [1]
					if event then
						self:UnregisterEvent (event)
					end
				end

				--remove scripts
				self:SetScript("OnEvent", nil)
				self:SetScript("OnUpdate", nil)
				self:Hide()
			end
		end
	end

	healthBarMetaFunctions.Initialize = function(self)
		PixelUtil.SetWidth (self, self.Settings.Width, 1)
		PixelUtil.SetHeight(self, self.Settings.Height, 1)

		self:SetTexture(self.Settings.Texture)

		self.background:SetAllPoints()
		self.background:SetColorTexture(self.Settings.BackgroundColor:GetColor())

		--setpoint of these widgets are set inside the function that updates the incoming heal
		self.incomingHealIndicator:SetTexture(self:GetTexture())
		self.healAbsorbIndicator:SetTexture(self:GetTexture())
		self.healAbsorbIndicator:SetVertexColor(.1, .8, .8)
		self.shieldAbsorbIndicator:SetTexture(self.Settings.ShieldIndicatorTexture, true, true)

		self.shieldAbsorbGlow:SetWidth(self.Settings.ShieldGlowWidth)
		self.shieldAbsorbGlow:SetTexture(self.Settings.ShieldGlowTexture)
		self.shieldAbsorbGlow:SetBlendMode("ADD")
		self.shieldAbsorbGlow:SetPoint("topright", self, "topright", 8, 0)
		self.shieldAbsorbGlow:SetPoint("bottomright", self, "bottomright", 8, 0)
		self.shieldAbsorbGlow:Hide()

		self:SetUnit(nil)
	end

	--call every tick
	healthBarMetaFunctions.OnTick = function(self, deltaTime) end --if overrided, set 'CanTick' to true on the settings table

	--when an event happen for this unit, send it to the apropriate function
	healthBarMetaFunctions.OnEvent = function(self, event, ...)
		local eventFunc = self [event]
		if (eventFunc) then
			--the function doesn't receive which event was, only 'self' and the parameters
			eventFunc (self, ...)
		end
	end

	--when the unit max health is changed
	healthBarMetaFunctions.UpdateMaxHealth = function(self)
		local maxHealth = UnitHealthMax (self.displayedUnit)
		self:SetMinMaxValues(0, maxHealth)
		self.currentHealthMax = maxHealth

		self:RunHooksForWidget("OnHealthMaxChange", self, self.displayedUnit)
	end

	healthBarMetaFunctions.UpdateHealth = function(self)
		-- update max health regardless to avoid weird wrong values on UpdateMaxHealth sometimes
		-- local maxHealth = UnitHealthMax (self.displayedUnit)
		-- self:SetMinMaxValues(0, maxHealth)
		-- self.currentHealthMax = maxHealth

		self.oldHealth = self.currentHealth
		local health = UnitHealth(self.displayedUnit)
		self.currentHealth = health
		PixelUtil.SetStatusBarValue(self, health)

		self:RunHooksForWidget("OnHealthChange", self, self.displayedUnit)
	end

	--health and absorbs prediction
	healthBarMetaFunctions.UpdateHealPrediction = function(self)
		local currentHealth = self.currentHealth
		local currentHealthMax = self.currentHealthMax
		local healthPercent = currentHealth / currentHealthMax

		if (not currentHealthMax or currentHealthMax <= 0) then
			return
		end

		--order is: the health of the unit > damage absorb > heal absorb > incoming heal
		local width = self:GetWidth()

		if (self.Settings.ShowHealingPrediction) then
			--incoming heal on the unit from all sources
			local unitHealIncoming = self.displayedUnit and UnitGetIncomingHeals (self.displayedUnit) or 0
			--heal absorbs
			local unitHealAbsorb = IS_WOW_PROJECT_MAINLINE and self.displayedUnit and UnitGetTotalHealAbsorbs (self.displayedUnit) or 0

			if (unitHealIncoming > 0) then
				--calculate what is the percent of health incoming based on the max health the player has
				local incomingPercent = unitHealIncoming / currentHealthMax
				self.incomingHealIndicator:Show()
				self.incomingHealIndicator:SetWidth(max(1, min (width * incomingPercent, abs(healthPercent - 1) * width)))
				self.incomingHealIndicator:SetPoint("topleft", self, "topleft", width * healthPercent, 0)
				self.incomingHealIndicator:SetPoint("bottomleft", self, "bottomleft", width * healthPercent, 0)
			else
				self.incomingHealIndicator:Hide()
			end

			if (unitHealAbsorb > 0) then
				local healAbsorbPercent = unitHealAbsorb / currentHealthMax
				self.healAbsorbIndicator:Show()
				self.healAbsorbIndicator:SetWidth(max(1, min (width * healAbsorbPercent, abs(healthPercent - 1) * width)))
				self.healAbsorbIndicator:SetPoint("topleft", self, "topleft", width * healthPercent, 0)
				self.healAbsorbIndicator:SetPoint("bottomleft", self, "bottomleft", width * healthPercent, 0)
			else
				self.healAbsorbIndicator:Hide()
			end
		end

		if (self.Settings.ShowShields and IS_WOW_PROJECT_MAINLINE) then
			--damage absorbs
			local unitDamageAbsorb = self.displayedUnit and UnitGetTotalAbsorbs (self.displayedUnit) or 0

			if (unitDamageAbsorb > 0) then
				local damageAbsorbPercent = unitDamageAbsorb / currentHealthMax
				self.shieldAbsorbIndicator:Show()
				--set the width where the max width size is what is lower: the absorb size or the missing amount of health in the health bar
				--/dump NamePlate1PlaterUnitFrameHealthBar.shieldAbsorbIndicator:GetSize()
				self.shieldAbsorbIndicator:SetWidth(max(1, min (width * damageAbsorbPercent, abs(healthPercent - 1) * width)))
				self.shieldAbsorbIndicator:SetPoint("topleft", self, "topleft", width * healthPercent, 0)
				self.shieldAbsorbIndicator:SetPoint("bottomleft", self, "bottomleft", width * healthPercent, 0)

				--if the absorb percent pass 100%, show the glow
				if ((healthPercent + damageAbsorbPercent) > 1) then
					self.shieldAbsorbGlow:Show()
				else
					self.shieldAbsorbGlow:Hide()
				end
			else
				self.shieldAbsorbIndicator:Hide()
				self.shieldAbsorbGlow:Hide()
			end
		else
			self.shieldAbsorbIndicator:Hide()
			self.shieldAbsorbGlow:Hide()
		end
	end

	--Health Events
		healthBarMetaFunctions.PLAYER_ENTERING_WORLD = function(self, ...)
			self:UpdateMaxHealth()
			self:UpdateHealth()
			self:UpdateHealPrediction()
		end

		healthBarMetaFunctions.UNIT_HEALTH = function(self, ...)
			self:UpdateHealth()
			self:UpdateHealPrediction()
		end

		healthBarMetaFunctions.UNIT_HEALTH_FREQUENT = function(self, ...)
			self:UpdateHealth()
			self:UpdateHealPrediction()
		end

		healthBarMetaFunctions.UNIT_MAXHEALTH = function(self, ...)
			self:UpdateMaxHealth()
			self:UpdateHealth()
			self:UpdateHealPrediction()
		end


		healthBarMetaFunctions.UNIT_HEAL_PREDICTION = function(self, ...)
			self:UpdateMaxHealth()
			self:UpdateHealth()
			self:UpdateHealPrediction()
		end

		healthBarMetaFunctions.UNIT_ABSORB_AMOUNT_CHANGED = function(self, ...)
			self:UpdateMaxHealth()
			self:UpdateHealth()
			self:UpdateHealPrediction()
		end

		healthBarMetaFunctions.UNIT_HEAL_ABSORB_AMOUNT_CHANGED = function(self, ...)
			self:UpdateMaxHealth()
			self:UpdateHealth()
			self:UpdateHealPrediction()
		end


-- ~healthbar
function detailsFramework:CreateHealthBar (parent, name, settingsOverride)

	assert(name or parent:GetName(), "DetailsFramework:CreateHealthBar parameter 'name' omitted and parent has no name.")

	local healthBar = CreateFrame("StatusBar", name or (parent:GetName() .. "HealthBar"), parent, "BackdropTemplate")
		do --layers
			--background
			healthBar.background = healthBar:CreateTexture(nil, "background")
			healthBar.background:SetDrawLayer("background", -6)

			--artwork
			--healing incoming
			healthBar.incomingHealIndicator = healthBar:CreateTexture(nil, "artwork")
			healthBar.incomingHealIndicator:SetDrawLayer("artwork", 4)
			--current shields on the unit
			healthBar.shieldAbsorbIndicator =  healthBar:CreateTexture(nil, "artwork")
			healthBar.shieldAbsorbIndicator:SetDrawLayer("artwork", 5)
			--debuff absorbing heal
			healthBar.healAbsorbIndicator = healthBar:CreateTexture(nil, "artwork")
			healthBar.healAbsorbIndicator:SetDrawLayer("artwork", 6)
			--the shield fills all the bar, show that cool glow
			healthBar.shieldAbsorbGlow = healthBar:CreateTexture(nil, "artwork")
			healthBar.shieldAbsorbGlow:SetDrawLayer("artwork", 7)
			--statusbar texture
			healthBar.barTexture = healthBar:CreateTexture(nil, "artwork")
			healthBar:SetStatusBarTexture(healthBar.barTexture)
		end

	--mixins
	detailsFramework:Mixin(healthBar, healthBarMetaFunctions)
	detailsFramework:Mixin(healthBar, detailsFramework.StatusBarFunctions)

	healthBar:CreateTextureMask()
	healthBar:SetTexture([[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]])

	--settings and hooks
	local settings = detailsFramework.table.copy({}, healthBarMetaFunctions.Settings)
	if (settingsOverride) then
		detailsFramework.table.copy(settings, settingsOverride)
	end
	healthBar.Settings = settings

	--hook list
	healthBar.HookList = detailsFramework.table.copy({}, healthBarMetaFunctions.HookList)

	--initialize the cast bar
	healthBar:Initialize()

	return healthBar
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--power bar frame

--[=[
	DF:CreatePowerBar (parent, name, settingsOverride)
	creates statusbar frame to show the unit power bar
	@parent = frame to pass for the CreateFrame function
	@name = absolute name of the frame, if omitted it uses the parent's name .. "PPowerBar"
	@settingsOverride = table with keys and values to replace the defaults from the framework
--]=]

detailsFramework.PowerFrameFunctions = {
	WidgetType = "powerBar",

	HookList = {
		OnHide = {},
		OnShow = {},
	},

	Settings = {
		--misc
		ShowAlternatePower = true, --if true it'll show alternate power over the regular power the unit uses
		ShowPercentText = true, --if true show a text with the current energy percent
		HideIfNoPower = true, --if true and the UnitMaxPower returns zero, it'll hide the power bar with self:Hide()
		CanTick = false, --if it calls the OnTick function every tick

		--appearance
		BackgroundColor = detailsFramework:CreateColorTable (.2, .2, .2, .8),
		Texture = [[Interface\RaidFrame\Raid-Bar-Resource-Fill]],

		--default size
		Width = 100,
		Height = 20,
	},

	PowerBarEvents = {
		{"PLAYER_ENTERING_WORLD"},
		{"UNIT_DISPLAYPOWER", true},
		{"UNIT_POWER_BAR_SHOW", true},
		{"UNIT_POWER_BAR_HIDE", true},
		{"UNIT_MAXPOWER", true},
		{"UNIT_POWER_UPDATE", true},
		{"UNIT_POWER_FREQUENT", true},
	},

	--setup the castbar to be used by another unit
	SetUnit = function(self, unit, displayedUnit)
		if (self.unit ~= unit or self.displayedUnit ~= displayedUnit or unit == nil) then
			self.unit = unit
			self.displayedUnit = displayedUnit or unit

			--register events
			if (unit) then
				for _, eventTable in ipairs(self.PowerBarEvents) do
					local event = eventTable [1]
					local isUnitEvent = eventTable [2]

					if (isUnitEvent) then
						self:RegisterUnitEvent (event, self.displayedUnit)
					else
						self:RegisterEvent(event)
					end
				end

				--set scripts
				self:SetScript("OnEvent", self.OnEvent)

				if (self.Settings.CanTick) then
					self:SetScript("OnUpdate", self.OnTick)
				end

				self:Show()
				self:UpdatePowerBar()
			else
				--remove all registered events
				for _, eventTable in ipairs(self.PowerBarEvents) do
					local event = eventTable [1]
					self:UnregisterEvent (event)
				end

				--remove scripts
				self:SetScript("OnEvent", nil)
				self:SetScript("OnUpdate", nil)
				self:Hide()
			end
		end
	end,

	Initialize = function(self)
		PixelUtil.SetWidth (self, self.Settings.Width)
		PixelUtil.SetHeight(self, self.Settings.Height)

		self:SetTexture(self.Settings.Texture)

		self.background:SetAllPoints()
		self.background:SetColorTexture(self.Settings.BackgroundColor:GetColor())

		if (self.Settings.ShowPercentText) then
			self.percentText:Show()
			PixelUtil.SetPoint(self.percentText, "center", self, "center", 0, 0)

			detailsFramework:SetFontSize(self.percentText, 9)
			detailsFramework:SetFontColor(self.percentText, "white")
			detailsFramework:SetFontOutline (self.percentText, "OUTLINE")
		else
			self.percentText:Hide()
		end

		self:SetUnit(nil)
	end,

	--call every tick
	OnTick = function(self, deltaTime) end, --if overrided, set 'CanTick' to true on the settings table

	--when an event happen for this unit, send it to the apropriate function
	OnEvent = function(self, event, ...)
		local eventFunc = self [event]
		if (eventFunc) then
			--the function doesn't receive which event was, only 'self' and the parameters
			eventFunc (self, ...)
		end
	end,

	UpdatePowerBar = function(self)
		self:UpdatePowerInfo()
		self:UpdateMaxPower()
		self:UpdatePower()
		self:UpdatePowerColor()
	end,

	--power update
	UpdateMaxPower = function(self)
		self.currentPowerMax = UnitPowerMax (self.displayedUnit, self.powerType)
		self:SetMinMaxValues(self.minPower, self.currentPowerMax)

		if (self.currentPowerMax == 0 and self.Settings.HideIfNoPower) then
			self:Hide()
		end
	end,
	UpdatePower = function(self)
		self.currentPower = UnitPower (self.displayedUnit, self.powerType)
		PixelUtil.SetStatusBarValue (self, self.currentPower)

		if (self.Settings.ShowPercentText) then
			self.percentText:SetText(floor(self.currentPower / self.currentPowerMax * 100) .. "%")
		end
	end,

	--when a event different from unit_power_update is triggered, update which type of power the unit should show
	UpdatePowerInfo = function(self)
		if (IS_WOW_PROJECT_MAINLINE and self.Settings.ShowAlternatePower) then -- not available in classic
			local barID = UnitPowerBarID(self.displayedUnit)
			local barInfo = GetUnitPowerBarInfoByID(barID)
			--local name, tooltip, cost = GetUnitPowerBarStringsByID(barID);
			--barInfo.barType,barInfo.minPower, barInfo.startInset, barInfo.endInset, barInfo.smooth, barInfo.hideFromOthers, barInfo.showOnRaid, barInfo.opaqueSpark, barInfo.opaqueFlash, barInfo.anchorTop, name, tooltip, cost, barInfo.ID, barInfo.forcePercentage, barInfo.sparkUnderFrame;
			if (barInfo and barInfo.showOnRaid and IsInGroup()) then
				self.powerType = ALTERNATE_POWER_INDEX
				self.minPower = barInfo.minPower
				return
			end
		end

		self.powerType = UnitPowerType (self.displayedUnit)
		self.minPower = 0
	end,

	--tint the bar with the color of the power, e.g. blue for a mana bar
	UpdatePowerColor = function(self)
		if (not UnitIsConnected (self.unit)) then
			self:SetStatusBarColor(.5, .5, .5)
			return
		end

		if (self.powerType == ALTERNATE_POWER_INDEX) then
			--don't change this, keep the same color as the game tints on CompactUnitFrame.lua
			self:SetStatusBarColor(0.7, 0.7, 0.6)
			return
		end

		local powerColor = PowerBarColor [self.powerType] --don't appear to be, but PowerBarColor is a global table with all power colors /run Details:Dump (PowerBarColor)
		if (powerColor) then
			self:SetStatusBarColor(powerColor.r, powerColor.g, powerColor.b)
			return
		end

		local _, _, r, g, b = UnitPowerType (self.displayedUnit)
		if (r) then
			self:SetStatusBarColor(r, g, b)
			return
		end

		--if everything else fails, tint as rogue energy
		powerColor = PowerBarColor ["ENERGY"]
		self:SetStatusBarColor(powerColor.r, powerColor.g, powerColor.b)
	end,

	--events
	PLAYER_ENTERING_WORLD = function(self, ...)
		self:UpdatePowerBar()
	end,
	UNIT_DISPLAYPOWER  = function(self, ...)
		self:UpdatePowerBar()
	end,
	UNIT_POWER_BAR_SHOW = function(self, ...)
		self:UpdatePowerBar()
	end,
	UNIT_POWER_BAR_HIDE = function(self, ...)
		self:UpdatePowerBar()
	end,

	UNIT_MAXPOWER = function(self, ...)
		self:UpdateMaxPower()
		self:UpdatePower()
	end,
	UNIT_POWER_UPDATE = function(self, ...)
		self:UpdatePower()
	end,
	UNIT_POWER_FREQUENT = function(self, ...)
		self:UpdatePower()
	end,
}

detailsFramework:Mixin(detailsFramework.PowerFrameFunctions, detailsFramework.ScriptHookMixin)

-- ~powerbar
function detailsFramework:CreatePowerBar(parent, name, settingsOverride)
	assert(name or parent:GetName(), "DetailsFramework:CreatePowerBar parameter 'name' omitted and parent has no name.")

	local powerBar = CreateFrame("StatusBar", name or (parent:GetName() .. "PowerBar"), parent, "BackdropTemplate")
		do --layers
			--background
			powerBar.background = powerBar:CreateTexture(nil, "background")
			powerBar.background:SetDrawLayer("background", -6)

			--artwork
			powerBar.barTexture = powerBar:CreateTexture(nil, "artwork")
			powerBar:SetStatusBarTexture(powerBar.barTexture)

			--overlay
			powerBar.percentText = powerBar:CreateFontString(nil, "overlay", "GameFontNormal")
		end

	--mixins
	detailsFramework:Mixin(powerBar, detailsFramework.PowerFrameFunctions)
	detailsFramework:Mixin(powerBar, detailsFramework.StatusBarFunctions)

	powerBar:CreateTextureMask()
	powerBar:SetTexture([[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]])

	--settings and hooks
	local settings = detailsFramework.table.copy({}, detailsFramework.PowerFrameFunctions.Settings)
	if (settingsOverride) then
		detailsFramework.table.copy(settings, settingsOverride)
	end
	powerBar.Settings = settings

	local hookList = detailsFramework.table.copy({}, detailsFramework.PowerFrameFunctions.HookList)
	powerBar.HookList = hookList

	--initialize the cast bar
	powerBar:Initialize()

	return powerBar
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--cast bar frame

--[=[
	DF:CreateCastBar (parent, name, settingsOverride)
	creates a cast bar to show an unit cast
	@parent = frame to pass for the CreateFrame function
	@name = absolute name of the frame, if omitted it uses the parent's name .. "CastBar"
	@settingsOverride = table with keys and values to replace the defaults from the framework
--]=]

detailsFramework.CastFrameFunctions = {
	WidgetType = "castBar",

	HookList = {
		OnHide = {},
		OnShow = {},

		--can be regular cast or channel
		OnCastStart = {},
	},

	CastBarEvents = {
		{"UNIT_SPELLCAST_INTERRUPTED"},
		{"UNIT_SPELLCAST_DELAYED"},
		{"UNIT_SPELLCAST_CHANNEL_START"},
		{"UNIT_SPELLCAST_CHANNEL_UPDATE"},
		{"UNIT_SPELLCAST_CHANNEL_STOP"},
		{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_EMPOWER_START"},
		{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_EMPOWER_UPDATE"},
		{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_EMPOWER_STOP"},
		{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_INTERRUPTIBLE"},
		{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_NOT_INTERRUPTIBLE"},
		{"PLAYER_ENTERING_WORLD"},
		{"UNIT_SPELLCAST_START", true},
		{"UNIT_SPELLCAST_STOP", true},
		{"UNIT_SPELLCAST_FAILED", true},
	},

	Settings = {
		NoFadeEffects = false, --if true it won't play fade effects when a cast if finished
		ShowTradeSkills = false, --if true, it shows cast for trade skills, e.g. creating an icon with blacksmith
		ShowShield = true, --if true, shows the shield above the spell icon for non interruptible casts
		CanTick = true, --if true it will run its OnTick function every tick.
		ShowCastTime = true, --if true, show the remaining time to finish the cast, lazy tick must be enabled
		FadeInTime = 0.1, --amount of time in seconds to go from zero to 100% alpha when starting to cast
		FadeOutTime = 0.5, --amount of time in seconds to go from 100% to zero alpha when the cast finishes
		CanLazyTick = true, --if true, it'll execute the lazy tick function, it ticks in a much slower pace comparece with the regular tick
		LazyUpdateCooldown = 0.2, --amount of time to wait for the next lazy update, this updates non critical things like the cast timer

		ShowEmpoweredDuration = true, --full hold time for empowered spells

		FillOnInterrupt = true,
		HideSparkOnInterrupt = true,

		--default size
		Width = 100,
		Height = 20,

		--colour the castbar statusbar by the type of the cast
		Colors = {
			Casting = detailsFramework:CreateColorTable (1, 0.73, .1, 1),
			Channeling = detailsFramework:CreateColorTable (1, 0.73, .1, 1),
			Finished = detailsFramework:CreateColorTable (0, 1, 0, 1),
			NonInterruptible = detailsFramework:CreateColorTable (.7, .7, .7, 1),
			Failed = detailsFramework:CreateColorTable (.4, .4, .4, 1),
			Interrupted = detailsFramework:CreateColorTable (.965, .754, .154, 1),
		},

		--appearance
		BackgroundColor = detailsFramework:CreateColorTable (.2, .2, .2, .8),
		Texture = [[Interface\TargetingFrame\UI-StatusBar]],
		BorderShieldWidth = 10,
		BorderShieldHeight = 12,
		BorderShieldCoords = {0.26171875, 0.31640625, 0.53125, 0.65625},
		BorderShieldTexture = 1300837,
		SpellIconWidth = 10,
		SpellIconHeight = 10,
		ShieldIndicatorTexture = [[Interface\RaidFrame\Shield-Fill]],
		ShieldGlowTexture = [[Interface\RaidFrame\Shield-Overshield]],
		SparkTexture = [[Interface\CastingBar\UI-CastingBar-Spark]],
		SparkWidth = 16,
		SparkHeight = 16,
		SparkOffset = 0,
	},

	Initialize = function(self)
		self.unit = "unutilized unit"
		self.lazyUpdateCooldown = self.Settings.LazyUpdateCooldown
		self.Colors = self.Settings.Colors

		self:SetUnit(nil)
		PixelUtil.SetWidth (self, self.Settings.Width)
		PixelUtil.SetHeight(self, self.Settings.Height)

		self.background:SetColorTexture(self.Settings.BackgroundColor:GetColor())
		self.background:SetAllPoints()
		self.extraBackground:SetColorTexture(0, 0, 0, 1)
		self.extraBackground:SetVertexColor(self.Settings.BackgroundColor:GetColor())
		self.extraBackground:SetAllPoints()

		self:SetTexture(self.Settings.Texture)

		self.BorderShield:SetPoint("center", self, "left", 0, 0)
		self.BorderShield:SetTexture(self.Settings.BorderShieldTexture)
		self.BorderShield:SetTexCoord(unpack(self.Settings.BorderShieldCoords))
		self.BorderShield:SetSize(self.Settings.BorderShieldWidth, self.Settings.BorderShieldHeight)

		self.Icon:SetPoint("center", self, "left", 2, 0)
		self.Icon:SetSize(self.Settings.SpellIconWidth, self.Settings.SpellIconHeight)

		self.Spark:SetTexture(self.Settings.SparkTexture)
		self.Spark:SetSize(self.Settings.SparkWidth, self.Settings.SparkHeight)

		self.percentText:SetPoint("right", self, "right", -2, 0)
		self.percentText:SetJustifyH("right")

		self.fadeOutAnimation.alpha1:SetDuration(self.Settings.FadeOutTime)
		self.fadeInAnimation.alpha1:SetDuration(self.Settings.FadeInTime)
	end,

	SetDefaultColor = function(self, colorType, r, g, b, a)
		assert(type(colorType) == "string", "DetailsFramework: CastBar:SetDefaultColor require a string in the first argument.")
		self.Colors [colorType]:SetColor (r, g, b, a)
	end,

	--this get a color suggestion based on the type of cast being shown in the cast bar
	GetCastColor = function(self)
		if (not self.canInterrupt) then
			return self.Colors.NonInterruptible

		elseif (self.channeling) then
			return self.Colors.Channeling

		elseif (self.failed) then
			return self.Colors.Failed

		elseif (self.interrupted) then
			return self.Colors.Interrupted

		elseif (self.finished) then
			return self.Colors.Finished

		else
			return self.Colors.Casting
		end
	end,

	--update all colors of the cast bar
	UpdateCastColor = function(self)
		local castColor = self:GetCastColor()
		self:SetColor (castColor) --SetColor handles with ParseColors()
	end,

	--initial checks to know if this is a valid cast and should show the cast bar, if this fails the cast bar won't show
	IsValid = function(self, unit, castName, isTradeSkill, ignoreVisibility)
		if (not ignoreVisibility and not self:IsShown()) then
			return false
		end

		if (not self.Settings.ShowTradeSkills) then
			if (isTradeSkill) then
				return false
			end
		end

		if (not castName) then
			return false
		end

		return true
	end,

	--handle the interrupt state of the cast
	--this does not change the cast bar color because this function is called inside the start cast where is already handles the cast color
	UpdateInterruptState = function(self)
		if (self.Settings.ShowShield and not self.canInterrupt) then
			self.BorderShield:Show()
		else
			self.BorderShield:Hide()
		end
	end,

	--this check if the cast did reach 100% in the statusbar, mostly called from OnTick
	CheckCastIsDone = function(self, event, isFinished)

		--check max value
		if (not isFinished and not self.finished) then
			if (self.casting) then
				if (self.value >= self.maxValue) then
					isFinished = true
				end

			elseif (self.channeling) then
				if (self.value > self.maxValue or self.value <= 0) then
					isFinished = true
				end
			end

			--check if passed an event (not begin used at the moment)
			if (event) then
				if (event == UNIT_SPELLCAST_STOP or event == UNIT_SPELLCAST_CHANNEL_STOP) then
					isFinished = true
				end
			end
		end

		--the cast is finished
		if (isFinished) then
			if (self.casting) then
				self.UNIT_SPELLCAST_STOP (self, self.unit, self.unit, self.castID, self.spellID)

			elseif (self.channeling) then
				self.UNIT_SPELLCAST_CHANNEL_STOP (self, self.unit, self.unit, self.castID, self.spellID)
			end

			return true
		end
	end,

	--setup the castbar to be used by another unit
	SetUnit = function(self, unit, displayedUnit)
		if (self.unit ~= unit or self.displayedUnit ~= displayedUnit or unit == nil) then
			self.unit = unit
			self.displayedUnit = displayedUnit or unit

			--reset the cast bar
			self.casting = nil
			self.channeling = nil
			self.caninterrupt = nil

			--register events
			if (unit) then
				for _, eventTable in ipairs(self.CastBarEvents) do
					local event = eventTable [1]
					local isUnitEvent = eventTable [2]

					if event then
						if (isUnitEvent) then
							self:RegisterUnitEvent (event, unit)
						else
							self:RegisterEvent(event)
						end
					end
				end

				--set scripts
				self:SetScript("OnEvent", self.OnEvent)
				self:SetScript("OnShow", self.OnShow)
				self:SetScript("OnHide", self.OnHide)

				if (self.Settings.CanTick) then
					self:SetScript("OnUpdate", self.OnTick)
				end

				--check is can show the cast time text
				if (self.Settings.ShowCastTime and self.Settings.CanLazyTick) then
					self.percentText:Show()
				else
					self.percentText:Hide()
				end

				--setup animtions
				self:CancelScheduleToHide()

				--self:PLAYER_ENTERING_WORLD (unit, unit)
				self:OnEvent ("PLAYER_ENTERING_WORLD", unit, unit)

			else
				for _, eventTable in ipairs(self.CastBarEvents) do
					local event = eventTable [1]
					if event then
						self:UnregisterEvent (event)
					end
				end

				--register main events
				self:SetScript("OnUpdate", nil)
				self:SetScript("OnEvent", nil)
				self:SetScript("OnShow", nil)
				self:SetScript("OnHide", nil)

				self:Hide()
			end
		end
	end,

	--executed after a scheduled to hide timer is done
	DoScheduledHide = function(timerObject)
		timerObject.castBar.scheduledHideTime = nil

		--just to make sure it isn't casting
		if (not timerObject.castBar.casting and not timerObject.castBar.channeling) then
			if (not timerObject.castBar.Settings.NoFadeEffects) then
				timerObject.castBar:Animation_FadeOut()
			else
				timerObject.castBar:Hide()
			end
		end
	end,

	HasScheduledHide = function(self)
		return self.scheduledHideTime and not self.scheduledHideTime:IsCancelled()
	end,

	CancelScheduleToHide = function(self)
		if (self:HasScheduledHide()) then
			self.scheduledHideTime:Cancel()
		end
	end,

	--after an interrupt, do not immediately hide the cast bar, let it up for short amount of time to give feedback to the player
	ScheduleToHide = function(self, delay)
		if (not delay) then
			if (self.scheduledHideTime and not self.scheduledHideTime:IsCancelled()) then
				self.scheduledHideTime:Cancel()
			end

			self.scheduledHideTime = nil
			return
		end

		--already have a scheduled timer?
		if (self.scheduledHideTime and not self.scheduledHideTime:IsCancelled()) then
			self.scheduledHideTime:Cancel()
		end

		self.scheduledHideTime = C_Timer.NewTimer(delay, self.DoScheduledHide)
		self.scheduledHideTime.castBar = self
	end,

	OnHide = function(self)
		--just in case some other effects made it have a different alpha since SetUnit won't load if the unit is the same.
		self:SetAlpha(1)
		--cancel any timer to hide scheduled
		self:CancelScheduleToHide()
	end,

	--just update the current value if a spell is being cast since it wasn't running its tick function during the hide state
	--everything else should be in the correct state
	OnShow = function(self)
		self.flashTexture:Hide()

		if (self.unit) then
			if (self.casting) then
				local name, text, texture, startTime = UnitCastingInfo (self.unit)
				if (name) then
					--[[if not self.spellStartTime then
						self:UpdateCastingInfo(self.unit)
					end]]--
					self.value = GetTime() - self.spellStartTime
				end

				self:RunHooksForWidget("OnShow", self, self.unit)

			elseif (self.channeling) then
				local name, text, texture, endTime = UnitChannelInfo (self.unit)
				if (name) then
					--[[if not self.spellEndTime then
						self:UpdateChannelInfo(self.unit)
					end]]--
					self.value = self.spellEndTime - GetTime()
				end

				self:RunHooksForWidget("OnShow", self, self.unit)
			end
		end
	end,

	--it's triggering several events since it's not registered for the unit with RegisterUnitEvent
	OnEvent = function(self, event, ...)
		local arg1 = ...
		local unit = self.unit

		if (event == "PLAYER_ENTERING_WORLD") then
			local newEvent = self.PLAYER_ENTERING_WORLD (self, unit, ...)
			if (newEvent) then
				self.OnEvent (self, newEvent, unit)
				return
			end

		elseif (arg1 ~= unit) then
			return
		end

		local eventFunc = self [event]
		if (eventFunc) then
			eventFunc (self, unit, ...)
		end
	end,

	OnTick_LazyTick = function(self)
		--run the lazy tick if allowed
		if (self.Settings.CanLazyTick) then
			--update the cast time
			if (self.Settings.ShowCastTime) then
				if (self.casting) then
					self.percentText:SetText(format("%.1f", abs(self.value - self.maxValue)))

				elseif (self.channeling) then
					local remainingTime = abs(self.value)
					if (remainingTime > 999) then
						self.percentText:SetText("")
					else
						self.percentText:SetText(format("%.1f", remainingTime))
					end
				else
					self.percentText:SetText("")
				end
			end

			return true
		else
			return false
		end
	end,

	--tick function for regular casts
	OnTick_Casting = function(self, deltaTime)
		self.value = self.value + deltaTime

		if (self:CheckCastIsDone()) then
			return
		else
			self:SetValue(self.value)
		end

		--update spark position
		local sparkPosition = self.value / self.maxValue * self:GetWidth()
		self.Spark:SetPoint("center", self, "left", sparkPosition + self.Settings.SparkOffset, 0)

		--in order to allow the lazy tick run, it must return true, it tell that the cast didn't finished
		return true
	end,

	--tick function for channeling casts
	OnTick_Channeling = function(self, deltaTime)
		self.value = self.value - deltaTime

		if (self:CheckCastIsDone()) then
			return
		else
			self:SetValue(self.value)
		end

		--update spark position
		local sparkPosition = self.value / self.maxValue * self:GetWidth()
		self.Spark:SetPoint("center", self, "left", sparkPosition + self.Settings.SparkOffset, 0)

		return true
	end,

	OnTick = function(self, deltaTime)
		if (self.casting) then
			if (not self:OnTick_Casting (deltaTime)) then
				return
			end

			--lazy tick
			self.lazyUpdateCooldown = self.lazyUpdateCooldown - deltaTime
			if (self.lazyUpdateCooldown < 0) then
				self:OnTick_LazyTick()
				self.lazyUpdateCooldown = self.Settings.LazyUpdateCooldown
			end

		elseif (self.channeling) then
			if (not self:OnTick_Channeling (deltaTime)) then
				return
			end

			--lazy tick
			self.lazyUpdateCooldown = self.lazyUpdateCooldown - deltaTime
			if (self.lazyUpdateCooldown < 0) then
				self:OnTick_LazyTick()
				self.lazyUpdateCooldown = self.Settings.LazyUpdateCooldown
			end
		end
	end,

	--animation start script
	Animation_FadeOutStarted = function(self)

	end,

	--animation finished script
	Animation_FadeOutFinished = function(self)
		local castBar = self:GetParent()
		castBar:SetAlpha(1)
		castBar:Hide()
	end,

	--animation start script
	Animation_FadeInStarted = function(self)

	end,

	--animation finished script
	Animation_FadeInFinished = function(self)
		local castBar = self:GetParent()
		castBar:Show()
		castBar:SetAlpha(1)
	end,

	--animation calls
	Animation_FadeOut = function(self)
		self:ScheduleToHide (false)

		if (self.fadeInAnimation:IsPlaying()) then
			self.fadeInAnimation:Stop()
		end

		if (not self.fadeOutAnimation:IsPlaying()) then
			self.fadeOutAnimation:Play()
		end
	end,

	Animation_FadeIn = function(self)
		self:ScheduleToHide (false)

		if (self.fadeOutAnimation:IsPlaying()) then
			self.fadeOutAnimation:Stop()
		end

		if (not self.fadeInAnimation:IsPlaying()) then
			self.fadeInAnimation:Play()
		end
	end,

	Animation_Flash = function(self)
		if (not self.flashAnimation:IsPlaying()) then
			self.flashAnimation:Play()
		end
	end,

	Animation_StopAllAnimations = function(self)
		if (self.flashAnimation:IsPlaying()) then
			self.flashAnimation:Stop()
		end

		if (self.fadeOutAnimation:IsPlaying()) then
			self.fadeOutAnimation:Stop()
		end

		if (self.fadeInAnimation:IsPlaying()) then
			self.fadeInAnimation:Stop()
		end
	end,

	PLAYER_ENTERING_WORLD = function(self, unit, arg1)
		local isChannel = UnitChannelInfo (unit)
		local isRegularCast = UnitCastingInfo (unit)

		if (isChannel) then
			self.channeling = true
			self:UpdateChannelInfo(unit)
			return self.unit == arg1 and "UNIT_SPELLCAST_CHANNEL_START"

		elseif (isRegularCast) then
			self.casting = true
			self:UpdateCastingInfo(unit)
			return self.unit == arg1 and "UNIT_SPELLCAST_START"

		else
			self.casting = nil
			self.channeling = nil
			self.failed = nil
			self.finished = nil
			self.interrupted = nil
			self.Spark:Hide()
			self:Hide()
		end
	end,

	UpdateCastingInfo = function(self, unit)
		local name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible, spellID = UnitCastingInfo (unit)

		--is valid?
		if (not self:IsValid (unit, name, isTradeSkill, true)) then
			return
		end

		--setup cast
			self.casting = true
			self.channeling = nil
			self.interrupted = nil
			self.failed = nil
			self.finished = nil
			self.canInterrupt = not notInterruptible
			self.spellID = spellID
			self.castID = castID
			self.spellName = name
			self.spellTexture = texture
			self.spellStartTime = startTime / 1000
			self.spellEndTime = endTime / 1000
			self.value = GetTime() - self.spellStartTime
			self.maxValue = self.spellEndTime - self.spellStartTime

			self:SetMinMaxValues(0, self.maxValue)
			self:SetValue(self.value)
			self:SetAlpha(1)
			self.Icon:SetTexture(texture)
			self.Icon:Show()
			self.Text:SetText(text or name)

			if (self.Settings.ShowCastTime and self.Settings.CanLazyTick) then
				self.percentText:Show()
			end

			self.flashTexture:Hide()
			self:Animation_StopAllAnimations()

			self:SetAlpha(1)

			--set the statusbar color
			self:UpdateCastColor()

			if (not self:IsShown()) then
				self:Animation_FadeIn()
			end

			self.Spark:Show()
			self:Show()

		--update the interrupt cast border
		self:UpdateInterruptState()

	end,

	UNIT_SPELLCAST_START = function(self, unit)

		self:UpdateCastingInfo(unit)

		self:RunHooksForWidget("OnCastStart", self, self.unit, "UNIT_SPELLCAST_START")
	end,

	UpdateChannelInfo = function(self, unit, ...)
		local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, spellID, _, numStages = UnitChannelInfo (unit)

		--is valid?
		if (not self:IsValid (unit, name, isTradeSkill, true)) then
			return
		end
		
		--empowered?
			self.empStages = {}
			if numStages and numStages > 0 then
				self.holdAtMaxTime = GetUnitEmpowerHoldAtMaxTime(unit)
				self.empowered = true

				local lastStageEndTime = 0
				for i = 1, numStages do
					self.empStages[i] = {
						start = lastStageEndTime,
						finish = lastStageEndTime - GetUnitEmpowerStageDuration(unit, i - 1) / 1000,
					}
					lastStageEndTime = self.empStages[i].finish
					
					if startTime / 1000 + lastStageEndTime <= GetTime() then
						self.curStage = i
					end
				end
				
				if (self.Settings.ShowEmpoweredDuration) then
					endTime = endTime + self.holdAtMaxTime
				end
			else
				self.holdAtMaxTime = 0
				self.empowered = false
				self.curStage = 0
			end

		--setup cast
			self.casting = nil
			self.channeling = true
			self.interrupted = nil
			self.failed = nil
			self.finished = nil
			self.canInterrupt = not notInterruptible
			self.spellID = spellID
			self.castID = castID
			self.spellName = name
			self.spellTexture = texture
			self.spellStartTime = startTime / 1000
			self.spellEndTime = endTime / 1000
			self.value = self.spellEndTime - GetTime()
			self.maxValue = self.spellEndTime - self.spellStartTime
			self.numStages = numStages

			self:SetMinMaxValues(0, self.maxValue)
			self:SetValue(self.value)

			self:SetAlpha(1)
			self.Icon:SetTexture(texture)
			self.Icon:Show()
			self.Text:SetText(text)

			if (self.Settings.ShowCastTime and self.Settings.CanLazyTick) then
				self.percentText:Show()
			end

			self.flashTexture:Hide()
			self:Animation_StopAllAnimations()

			self:SetAlpha(1)

			--set the statusbar color
			self:UpdateCastColor()

			if (not self:IsShown()) then
				self:Animation_FadeIn()
			end

			self.Spark:Show()
			self:Show()

		--update the interrupt cast border
		self:UpdateInterruptState()

	end,

	UNIT_SPELLCAST_CHANNEL_START = function(self, unit, ...)

		self:UpdateChannelInfo(unit, ...)

		self:RunHooksForWidget("OnCastStart", self, self.unit, "UNIT_SPELLCAST_CHANNEL_START")
	end,

	UNIT_SPELLCAST_STOP = function(self, unit, ...)
		local unitID, castID, spellID = ...
		if (self.castID == castID) then
			if (self.interrupted) then
				if (self.Settings.HideSparkOnInterrupt) then
					self.Spark:Hide()
				end
			else
				self.Spark:Hide()
			end

			self.percentText:Hide()

			local value = self:GetValue()
			local _, maxValue = self:GetMinMaxValues()

			if (self.interrupted) then
				if (self.Settings.FillOnInterrupt) then
					self:SetValue(self.maxValue or maxValue or 1)
				end
			else
				self:SetValue(self.maxValue or maxValue or 1)
			end

			self.casting = nil
			self.finished = true

			if (not self:HasScheduledHide()) then
				--check if settings has no fade option or if its parents are not visible
				if (not self:IsVisible()) then
					self:Hide()

				elseif (self.Settings.NoFadeEffects) then
					self:ScheduleToHide (0.3)

				else
					self:Animation_Flash()
					self:Animation_FadeOut()
				end
			end

			self:UpdateCastColor()
		end
	end,

	UNIT_SPELLCAST_CHANNEL_STOP = function(self, unit, ...)
		local unitID, castID, spellID = ...

		if (self.channeling and castID == self.castID) then
			self.Spark:Hide()
			self.percentText:Hide()

			local value = self:GetValue()
			local _, maxValue = self:GetMinMaxValues()
			self:SetValue(self.maxValue or maxValue or 1)

			self.channeling = nil
			self.finished = true

			if (not self:HasScheduledHide()) then
				--check if settings has no fade option or if its parents are not visible
				if (not self:IsVisible()) then
					self:Hide()

				elseif (self.Settings.NoFadeEffects) then
					self:ScheduleToHide (0.3)

				else
					self:Animation_Flash()
					self:Animation_FadeOut()
				end
			end

			self:UpdateCastColor()
		end
	end,
	
	UNIT_SPELLCAST_EMPOWER_START = function(self, unit, ...)
		self:UNIT_SPELLCAST_CHANNEL_START(unit, ...)
	end,
	
	UNIT_SPELLCAST_EMPOWER_UPDATE = function(self, unit, ...)
		self:UNIT_SPELLCAST_CHANNEL_UPDATE(unit, ...)
	end,
	
	UNIT_SPELLCAST_EMPOWER_STOP = function(self, unit, ...)
		self:UNIT_SPELLCAST_CHANNEL_STOP(unit, ...)
	end,

	UNIT_SPELLCAST_FAILED = function(self, unit, ...)
		local unitID, castID, spellID = ...

		if ((self.casting or self.channeling) and castID == self.castID and not self.fadeOut) then
			self.casting = nil
			self.channeling = nil
			self.failed = true
			self.finished = true
			self:SetValue(self.maxValue or select(2, self:GetMinMaxValues()) or 1)

			--set the statusbar color
			self:UpdateCastColor()

			self.Spark:Hide()
			self.percentText:Hide()
			self.Text:SetText(FAILED) --auto locale within the global namespace

			self:ScheduleToHide (1)
		end
	end,

	UNIT_SPELLCAST_INTERRUPTED = function(self, unit, ...)
		local unitID, castID, spellID = ...

		if ((self.casting or self.channeling) and castID == self.castID and not self.fadeOut) then
			self.casting = nil
			self.channeling = nil
			self.interrupted = true
			self.finished = true

			if (self.Settings.FillOnInterrupt) then
				self:SetValue(self.maxValue or select(2, self:GetMinMaxValues()) or 1)
			end

			if (self.Settings.HideSparkOnInterrupt) then
				self.Spark:Hide()
			end

			local castColor = self:GetCastColor()
			self:SetColor (castColor) --SetColor handles with ParseColors()

			self.percentText:Hide()
			self.Text:SetText(INTERRUPTED) --auto locale within the global namespace

			self:ScheduleToHide (1)
		end
	end,

	UNIT_SPELLCAST_DELAYED = function(self, unit, ...)
		local name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo (unit)

		if (not self:IsValid (unit, name, isTradeSkill)) then
			return
		end

		--update the cast time
		self.spellStartTime = startTime / 1000
		self.spellEndTime = endTime / 1000
		self.value = GetTime() - self.spellStartTime
		self.maxValue = self.spellEndTime - self.spellStartTime
		self:SetMinMaxValues(0, self.maxValue)
	end,

	UNIT_SPELLCAST_CHANNEL_UPDATE = function(self, unit, ...)
		local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, spellID, _, numStages = UnitChannelInfo (unit)

		if (not self:IsValid (unit, name, isTradeSkill)) then
			return
		end

		--update the cast time
		self.spellStartTime = startTime / 1000
		self.spellEndTime = endTime / 1000
		self.value = self.spellEndTime - GetTime()

		if (self.value < 0) then
			self.value = 0
		end

		self.maxValue = self.spellEndTime - self.spellStartTime
		self:SetMinMaxValues(0, self.maxValue)
		self:SetValue(self.value)
	end,

	--cast changed its state to interruptable
	UNIT_SPELLCAST_INTERRUPTIBLE = function(self, unit, ...)
		self.canInterrupt = true
		self:UpdateCastColor()
		self:UpdateInterruptState()
	end,

	--cast changed its state to non interruptable
	UNIT_SPELLCAST_NOT_INTERRUPTIBLE = function(self, unit, ...)
		self.canInterrupt = false
		self:UpdateCastColor()
		self:UpdateInterruptState()
	end,

}

detailsFramework:Mixin(detailsFramework.CastFrameFunctions, detailsFramework.ScriptHookMixin)

-- for classic era use LibClassicCasterino:
local LibCC = LibStub("LibClassicCasterino", true)
if IS_WOW_PROJECT_CLASSIC_ERA and LibCC then
	local fCast = CreateFrame("frame")

	local getCastBar = function(unitId)
		local plateFrame = C_NamePlate.GetNamePlateForUnit (unitId)
		if (not plateFrame) then
			return
		end

		local castBar = plateFrame.unitFrame and plateFrame.unitFrame.castBar
		if (not castBar) then
			return
		end

		return castBar
	end

	local triggerCastEvent = function(castBar, event, unitId, ...)
		if (castBar and castBar.OnEvent) then
			castBar.OnEvent (castBar, event, unitId)
		end
	end

	local funcCast = function(event, unitId, ...)
		local castBar = getCastBar (unitId)
		if (castBar) then
			triggerCastEvent (castBar, event, unitId)
		end
	end

	fCast.UNIT_SPELLCAST_START = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	fCast.UNIT_SPELLCAST_STOP = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	fCast.UNIT_SPELLCAST_DELAYED = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	fCast.UNIT_SPELLCAST_FAILED = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	fCast.UNIT_SPELLCAST_INTERRUPTED = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	fCast.UNIT_SPELLCAST_CHANNEL_START = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	fCast.UNIT_SPELLCAST_CHANNEL_UPDATE = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	fCast.UNIT_SPELLCAST_CHANNEL_STOP = function(self, event, unitId, ...)
		triggerCastEvent (getCastBar (unitId), event, unitId)
	end

	if LibCC then
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_START", funcCast)
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_DELAYED", funcCast) -- only for player
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_STOP", funcCast)
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_FAILED", funcCast)
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_INTERRUPTED", funcCast)
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_CHANNEL_START", funcCast)
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_CHANNEL_UPDATE", funcCast) -- only for player
		LibCC.RegisterCallback(fCast,"UNIT_SPELLCAST_CHANNEL_STOP", funcCast)

		UnitCastingInfo = function(unit)
			return LibCC:UnitCastingInfo (unit)
		end

		UnitChannelInfo = function(unit)
			return LibCC:UnitChannelInfo (unit)
		end
	end
end -- end classic era

-- ~castbar

function detailsFramework:CreateCastBar(parent, name, settingsOverride)
	assert(name or parent:GetName(), "DetailsFramework:CreateCastBar parameter 'name' omitted and parent has no name.")

	local castBar = CreateFrame("StatusBar", name or (parent:GetName() .. "CastBar"), parent, "BackdropTemplate")

		do --layers

			--these widgets was been made with back compatibility in mind
			--they are using the same names as the retail game uses on the nameplate castbar
			--this should make Plater core and Plater scripts made by users compatible with the new unit frame made on the framework

			--background
			castBar.background = castBar:CreateTexture(nil, "background", nil, -6)
			castBar.extraBackground = castBar:CreateTexture(nil, "background", nil, -5)

			--overlay
			castBar.Text = castBar:CreateFontString(nil, "overlay", "SystemFont_Shadow_Small")
			castBar.Text:SetDrawLayer("overlay", 1)
			castBar.Text:SetPoint("center", 0, 0)

			castBar.BorderShield = castBar:CreateTexture(nil, "overlay", nil, 5)
			castBar.BorderShield:Hide()

			castBar.Icon = castBar:CreateTexture(nil, "overlay", nil, 4)
			castBar.Icon:Hide()

			castBar.Spark = castBar:CreateTexture(nil, "overlay", nil, 3)
			castBar.Spark:SetBlendMode("ADD")

			--time left on the cast
			castBar.percentText = castBar:CreateFontString(nil, "overlay", "SystemFont_Shadow_Small")
			castBar.percentText:SetDrawLayer("overlay", 7)

			--statusbar texture
			castBar.barTexture = castBar:CreateTexture(nil, "artwork", nil, -6)
			castBar:SetStatusBarTexture(castBar.barTexture)

			--animations fade in and out
			local fadeOutAnimationHub = detailsFramework:CreateAnimationHub (castBar, detailsFramework.CastFrameFunctions.Animation_FadeOutStarted, detailsFramework.CastFrameFunctions.Animation_FadeOutFinished)
			fadeOutAnimationHub.alpha1 = detailsFramework:CreateAnimation(fadeOutAnimationHub, "ALPHA", 1, 1, 1, 0)
			castBar.fadeOutAnimation = fadeOutAnimationHub

			local fadeInAnimationHub = detailsFramework:CreateAnimationHub (castBar, detailsFramework.CastFrameFunctions.Animation_FadeInStarted, detailsFramework.CastFrameFunctions.Animation_FadeInFinished)
			fadeInAnimationHub.alpha1 = detailsFramework:CreateAnimation(fadeInAnimationHub, "ALPHA", 1, 0.150, 0, 1)
			castBar.fadeInAnimation = fadeInAnimationHub

			--animatios flash
			local flashTexture = castBar:CreateTexture(nil, "overlay", nil, 7)
			flashTexture:SetColorTexture(1, 1, 1, 1)
			flashTexture:SetAllPoints()
			flashTexture:SetAlpha(0)
			flashTexture:Hide()
			flashTexture:SetBlendMode("ADD")
			castBar.flashTexture = flashTexture

			local flashAnimationHub = detailsFramework:CreateAnimationHub (flashTexture, function() flashTexture:Show() end, function() flashTexture:Hide() end)
			detailsFramework:CreateAnimation(flashAnimationHub, "ALPHA", 1, 0.2, 0, 0.8)
			detailsFramework:CreateAnimation(flashAnimationHub, "ALPHA", 2, 0.2, 1, 0)
			castBar.flashAnimation = flashAnimationHub
		end

	--mixins
	detailsFramework:Mixin(castBar, detailsFramework.CastFrameFunctions)
	detailsFramework:Mixin(castBar, detailsFramework.StatusBarFunctions)

	castBar:CreateTextureMask()
	castBar:AddMaskTexture(castBar.flashTexture)
	castBar:AddMaskTexture(castBar.background)
	castBar:AddMaskTexture(castBar.extraBackground)

	castBar:SetTexture([[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]])

	--settings and hooks
	local settings = detailsFramework.table.copy({}, detailsFramework.CastFrameFunctions.Settings)
	if (settingsOverride) then
		detailsFramework.table.copy(settings, settingsOverride)
	end
	castBar.Settings = settings

	local hookList = detailsFramework.table.copy({}, detailsFramework.CastFrameFunctions.HookList)
	castBar.HookList = hookList

	--initialize the cast bar
	castBar:Initialize()

	return castBar
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--border frame

--[=[
	DF:CreateBorderFrame (parent, name)
	creates a frame with 4 child textures attached to each one of the 4 sides of a frame
	@parent = parent frame to pass to CreateFrame function
	@name = name of the frame, if omitted a random name is created
--]=]

detailsFramework.BorderFunctions = {
	SetBorderColor = function(self, r, g, b, a)
		r, g, b, a = detailsFramework:ParseColors(r, g, b, a)
		for _, texture in ipairs(self.allTextures) do
			texture:SetVertexColor(r, g, b, a)
		end
	end,

	SetBorderThickness = function(self, newThickness)
		PixelUtil.SetWidth (self.leftBorder, newThickness, newThickness)
		PixelUtil.SetWidth (self.rightBorder, newThickness, newThickness)
		PixelUtil.SetHeight(self.topBorder, newThickness, newThickness)
		PixelUtil.SetHeight(self.bottomBorder, newThickness, newThickness)
	end,

	WidgetType = "border",
}

-- ~borderframe
function detailsFramework:CreateBorderFrame (parent, name)
	local parentName = name or "DetailsFrameworkBorderFrame" .. tostring(math.random(1, 100000000))

	local f = CreateFrame("frame", parentName, parent, "BackdropTemplate")
	f:SetFrameLevel(f:GetFrameLevel()+1)
	f:SetAllPoints()

	detailsFramework:Mixin(f, detailsFramework.BorderFunctions)

	f.allTextures = {}

	--create left border
		local leftBorder = f:CreateTexture(nil, "overlay")
		leftBorder:SetDrawLayer("overlay", 7)
		leftBorder:SetColorTexture(1, 1, 1, 1)
		tinsert(f.allTextures, leftBorder)
		f.leftBorder = leftBorder
		PixelUtil.SetPoint(leftBorder, "topright", f, "topleft", 0, 1, 0, 1)
		PixelUtil.SetPoint(leftBorder, "bottomright", f, "bottomleft", 0, -1, 0, -1)
		PixelUtil.SetWidth (leftBorder, 1, 1)

	--create right border
		local rightBorder = f:CreateTexture(nil, "overlay")
		rightBorder:SetDrawLayer("overlay", 7)
		rightBorder:SetColorTexture(1, 1, 1, 1)
		tinsert(f.allTextures, rightBorder)
		f.rightBorder = rightBorder
		PixelUtil.SetPoint(rightBorder, "topleft", f, "topright", 0, 1, 0, 1)
		PixelUtil.SetPoint(rightBorder, "bottomleft", f, "bottomright", 0, -1, 0, -1)
		PixelUtil.SetWidth (rightBorder, 1, 1)

	--create top border
		local topBorder = f:CreateTexture(nil, "overlay")
		topBorder:SetDrawLayer("overlay", 7)
		topBorder:SetColorTexture(1, 1, 1, 1)
		tinsert(f.allTextures, topBorder)
		f.topBorder = topBorder
		PixelUtil.SetPoint(topBorder, "bottomleft", f, "topleft", 0, 0, 0, 0)
		PixelUtil.SetPoint(topBorder, "bottomright", f, "topright", 0, 0, 0, 0)
		PixelUtil.SetHeight(topBorder, 1, 1)

	--create  border
		local bottomBorder = f:CreateTexture(nil, "overlay")
		bottomBorder:SetDrawLayer("overlay", 7)
		bottomBorder:SetColorTexture(1, 1, 1, 1)
		tinsert(f.allTextures, bottomBorder)
		f.bottomBorder = bottomBorder
		PixelUtil.SetPoint(bottomBorder, "topleft", f, "bottomleft", 0, 0, 0, 0)
		PixelUtil.SetPoint(bottomBorder, "topright", f, "bottomright", 0, 0, 0, 0)
		PixelUtil.SetHeight(bottomBorder, 1, 1)

	return f
end



------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--unit frame

--[=[
	DF:CreateUnitFrame(parent, name, settingsOverride)
	creates a very basic unit frame with a healthbar, castbar and power bar
	each unit frame has a .Settings table which isn't shared among other unit frames created with this method
	all members names are the same as the unit frame from the retail game

	@parent = frame to pass for the CreateFrame function
	@name = absolute name of the frame, if omitted a random name is created
	@settingsOverride = table with keys and values to replace the defaults from the framework

--]=]

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--unit frame

	--return true if the unit has been claimed by another player (health bar is gray)
	local isUnitTapDenied = function(unit)
		return unit and not UnitPlayerControlled(unit) and UnitIsTapDenied(unit)
	end

	detailsFramework.UnitFrameFunctions = {
		WidgetType = "unitFrame",

		Settings = {
			--unit frames
			ClearUnitOnHide = true, --if tue it'll set the unit to nil when the unit frame is set to hide
			ShowCastBar = true, --if this is false, the cast bar for the unit won't be shown
			ShowPowerBar = true, --if true it'll show the power bar for the unit, e.g. the mana bar
			ShowUnitName = true, --if false, the unit name won't show
			ShowBorder = true, --if false won't show the border frame

			--health bar color
			CanModifyHealhBarColor = true, --if false it won't change the color of the health bar
			ColorByAggro = false, --if true it'll color the healthbar with red color when the unit has aggro on player
			FixedHealthColor = false, --color override with a table {r=1, g=1, b=1}
			UseFriendlyClassColor = true, --make the healthbar class color for friendly players
			UseEnemyClassColor = true, --make the healthbar class color for enemy players

			--misc
			ShowTargetOverlay = true, --shows a highlighht for the player current target
			BorderColor = detailsFramework:CreateColorTable (0, 0, 0, 1), --border color, set to alpha zero for no border
			CanTick = false, --if true it'll run the OnTick event

			--size
			Width = 100,
			Height = 20,
			PowerBarHeight = 4,
			CastBarHeight = 8,
		},

		UnitFrameEvents = {
			--run for all units
			{"PLAYER_ENTERING_WORLD"},
			{"PARTY_MEMBER_DISABLE"},
			{"PARTY_MEMBER_ENABLE"},
			{"PLAYER_TARGET_CHANGED"},

			--run for one unit
			{"UNIT_NAME_UPDATE", true},
			{"UNIT_CONNECTION", true},
			{"UNIT_ENTERED_VEHICLE", true},
			{"UNIT_EXITED_VEHICLE", true},
			{"UNIT_PET", true},
			{"UNIT_THREAT_LIST_UPDATE", true},
		},

		--used when a event is triggered to quickly check if is a unit event
		IsUnitEvent = {
			["UNIT_NAME_UPDATE"] = true,
			["UNIT_CONNECTION"] = true,
			["UNIT_ENTERED_VEHICLE"] = true,
			["UNIT_EXITED_VEHICLE"] = true,
			["UNIT_PET"] = true,
			["UNIT_THREAT_LIST_UPDATE"] = true,
		},

		Initialize = function(self)
			self.border:SetBorderColor (self.Settings.BorderColor)

			PixelUtil.SetWidth (self, self.Settings.Width, 1)
			PixelUtil.SetHeight(self, self.Settings.Height, 1)

			PixelUtil.SetPoint(self.powerBar, "bottomleft", self, "bottomleft", 0, 0, 1, 1)
			PixelUtil.SetPoint(self.powerBar, "bottomright", self, "bottomright", 0, 0, 1, 1)
			PixelUtil.SetHeight(self.powerBar, self.Settings.PowerBarHeight, 1)

			--make the castbar overlap the powerbar
			PixelUtil.SetPoint(self.castBar, "bottomleft", self, "bottomleft", 0, 0, 1, 1)
			PixelUtil.SetPoint(self.castBar, "bottomright", self, "bottomright", 0, 0, 1, 1)
			PixelUtil.SetHeight(self.castBar, self.Settings.CastBarHeight, 1)
		end,

		SetHealthBarColor = function(self, r, g, b, a)
			self.healthBar:SetColor (r, g, b, a)
		end,

		--register all events which will be used by the unit frame
		RegisterEvents = function(self)
			--register events
			for index, eventTable in ipairs(self.UnitFrameEvents) do
				local event, isUnitEvent = unpack(eventTable)
				if (not isUnitEvent) then
					self:RegisterEvent(event)
				else
					self:RegisterUnitEvent (event, self.unit, self.displayedUnit ~= unit and self.displayedUnit or nil)
				end
			end

			--check settings and unregister events for disabled features
			if (not self.Settings.ColorByAggro) then
				self:UnregisterEvent ("UNIT_THREAT_LIST_UPDATE")
			end

			--set scripts
			self:SetScript("OnEvent", self.OnEvent)
			self:SetScript("OnHide", self.OnHide)

			if (self.Settings.CanTick) then
				self:SetScript("OnUpdate", self.OnTick)
			end
		end,

		--unregister events, called when this unit frame losses its unit
		UnregisterEvents = function(self)
			for index, eventTable in ipairs(self.UnitFrameEvents) do
				local event, firstUnit, secondUnit = unpack(eventTable)
				self:UnregisterEvent (event)
			end

			self:SetScript("OnEvent", nil)
			self:SetScript("OnUpdate", nil)
			self:SetScript("OnHide", nil)
		end,

		--call every tick
		OnTick = function(self, deltaTime) end, --if overrided, set 'CanTick' to true on the settings table

		--when an event happen for this unit, send it to the apropriate function
		OnEvent = function(self, event, ...)
			--run the function for this event
			local eventFunc = self [event]
			if (eventFunc) then
				--is this event an unit event?
				if (self.IsUnitEvent [event]) then
					local unit = ...
					--check if is for this unit (even if the event is registered only for the unit)
					if (unit == self.unit or unit == self.displayedUnit) then
						eventFunc (self, ...)
					end
				else
					eventFunc (self, ...)
				end
			end
		end,

		OnHide = function(self)
			if (self.Settings.ClearUnitOnHide) then
				self:SetUnit(nil)
			end
		end,

		--run if the unit currently shown is different than the new one
		SetUnit = function(self, unit)
			if (unit ~= self.unit or unit == nil) then
				self.unit = unit --absolute unit
				self.displayedUnit = unit --~todo rename to 'displayedUnit' for back compatibility with older scripts in Plater
				self.unitInVehicle = nil --true when the unit is in a vehicle

				if (unit) then
					self:RegisterEvents()

					self.healthBar:SetUnit(unit, self.displayedUnit)

					--is using castbars?
					if (self.Settings.ShowCastBar) then
						self.castBar:SetUnit(unit, self.displayedUnit)
					else
						self.castBar:SetUnit(nil)
					end

					--is using powerbars?
					if (self.Settings.ShowPowerBar) then
						self.powerBar:SetUnit(unit, self.displayedUnit)
					else
						self.powerBar:SetUnit(nil)
					end

					--is using the border?
					if (self.Settings.ShowBorder) then
						self.border:Show()
					else
						self.border:Hide()
					end

					if (not self.Settings.ShowUnitName) then
						self.unitName:Hide()
					end
				else
					self:UnregisterEvents()
					self.healthBar:SetUnit(nil)
					self.castBar:SetUnit(nil)
					self.powerBar:SetUnit(nil)
				end

				self:UpdateUnitFrame()
			end
		end,

		--if the unit is controlling a vehicle, need to show the vehicle instead
		--.unit and .displayedUnit is always the same execept when the unit is controlling a vehicle, then .displayedUnit is the unitID for the vehicle
		--todo: see what 'UnitTargetsVehicleInRaidUI' is, there's a call for this in the CompactUnitFrame.lua but zero documentation
		CheckVehiclePossession = function(self)
			--this unit is possessing a vehicle?
			local unitPossessVehicle = (IS_WOW_PROJECT_MAINLINE) and UnitHasVehicleUI(self.unit)	or false
			if (unitPossessVehicle) then
				if (not self.unitInVehicle) then
					if (UnitIsUnit("player", self.unit)) then
						self.displayedUnit = "vehicle"
						self.unitInVehicle = true
						self:RegisterEvents()
						self:UpdateAllWidgets()
						return true
					end

					local prefix, id, suffix = string.match(self.unit, "([^%d]+)([%d]*)(.*)") --CompactUnitFrame.lua
					local vehicleUnitID = prefix .. "pet" .. id .. suffix
					if (UnitExists(vehicleUnitID)) then
						self.displayedUnit = vehicleUnitID
						self.unitInVehicle = true
						self:RegisterEvents()
						self:UpdateAllWidgets()
						return true
					end
				end
			end

			if (self.unitInVehicle) then
				self.displayedUnit = self.unit
				self.unitInVehicle = nil
				self:RegisterEvents()
				self:UpdateAllWidgets()
			end
		end,

		--find a color for the health bar, if a color has been passed in the arguments use it instead, 'CanModifyHealhBarColor' must be true for this function run
		UpdateHealthColor = function(self, r, g, b)
			--check if color changes is disabled
			if (not self.Settings.CanModifyHealhBarColor) then
				return
			end

			local unit = self.displayedUnit

			--check if a color has been passed within the parameters
			if (r) then
				--check if passed a special color
				if (type(r) ~= "number") then
					r, g, b = detailsFramework:ParseColors(r)
				end

				self:SetHealthBarColor (r, g, b)
				return
			end

			--check if there is a color override in the settings
			if (self.Settings.FixedHealthColor) then
				local FixedHealthColor = self.Settings.FixedHealthColor
				r, g, b = FixedHealthColor.r, FixedHealthColor.g, FixedHealthColor.b
				self:SetHealthBarColor (r, g, b)
				return
			end

			--check if the unit is a player
			if (UnitIsPlayer (unit)) then
				--check if the unit is disconnected (in case it is a player
				if (not UnitIsConnected (unit)) then
					self:SetHealthBarColor (.5, .5, .5)
					return
				end

				--is a friendly or enemy player?
				if (UnitIsFriend ("player", unit)) then
					if (self.Settings.UseFriendlyClassColor) then
						local _, className = UnitClass(unit)
						if (className) then
							local classColor = RAID_CLASS_COLORS [className]
							if (classColor) then
								self:SetHealthBarColor (classColor.r, classColor.g, classColor.b)
								return
							end
						end
					else
						self:SetHealthBarColor (0, 1, 0)
						return
					end
				else
					if (self.Settings.UseEnemyClassColor) then
						local _, className = UnitClass(unit)
						if (className) then
							local classColor = RAID_CLASS_COLORS [className]
							if (classColor) then
								self:SetHealthBarColor (classColor.r, classColor.g, classColor.b)
								return
							end
						end
					else
						self:SetHealthBarColor (1, 0, 0)
						return
					end
				end
			end

			--is tapped?
			if (isUnitTapDenied (unit)) then
				self:SetHealthBarColor (.6, .6, .6)
				return
			end

			--is this is a npc attacking the player?
			if (self.Settings.ColorByAggro) then
				local _, threatStatus = UnitDetailedThreatSituation ("player", unit)
				if (threatStatus) then
					self:SetHealthBarColor (1, 0, 0)
					return
				end
			end

			-- get the regular color by selection
			r, g, b = UnitSelectionColor (unit)
			self:SetHealthBarColor (r, g, b)
		end,

		--misc
		UpdateName = function(self)
			if (not self.Settings.ShowUnitName) then
				return
			end

			--unit name without realm names by default
			local name = UnitName (self.unit)
			self.unitName:SetText(name)
			self.unitName:Show()
		end,

		--this runs when the player it self changes its target, need to update the current target overlay
		--todo: add focus overlay
		UpdateTargetOverlay = function(self)
			if (not self.Settings.ShowTargetOverlay) then
				self.targetOverlay:Hide()
				return
			end

			if (UnitIsUnit(self.displayedUnit, "target")) then
				self.targetOverlay:Show()
			else
				self.targetOverlay:Hide()
			end
		end,

		UpdateAllWidgets = function(self)
			if (UnitExists(self.displayedUnit)) then
				local unit = self.unit
				local displayedUnit = self.displayedUnit

				self:SetUnit(unit, displayedUnit)

				--is using castbars?
				if (self.Settings.ShowCastBar) then
					self.castBar:SetUnit(unit, displayedUnit)
				end

				--is using powerbars?
				if (self.Settings.ShowPowerBar) then
					self.powerBar:SetUnit(unit, displayedUnit)
				end

				self:UpdateName()
				self:UpdateTargetOverlay()
				self:UpdateHealthColor()
			end
		end,

		--update the unit frame and its widgets
		UpdateUnitFrame = function(self)
			local unitInVehicle = self:CheckVehiclePossession()

			--if the unit is inside a vehicle, the vehicle possession function will call an update on all widgets
			if (not unitInVehicle) then
				self:UpdateAllWidgets()
			end
		end,

		--event handles
		PLAYER_ENTERING_WORLD = function(self, ...)
			self:UpdateUnitFrame()
		end,

		--update overlays when the player changes its target
		PLAYER_TARGET_CHANGED = function(self, ...)
			self:UpdateTargetOverlay()
		end,

		--unit received a name update
		UNIT_NAME_UPDATE = function(self, ...)
			self:UpdateName()
		end,

		--this is registered only if .settings.ColorByAggro is true
		UNIT_THREAT_LIST_UPDATE = function(self, ...)
			if (self.Settings.ColorByAggro) then
				self:UpdateHealthColor()
			end
		end,

		--vehicle
		UNIT_ENTERED_VEHICLE = function(self, ...)
			self:UpdateUnitFrame()
		end,
		UNIT_EXITED_VEHICLE = function(self, ...)
			self:UpdateUnitFrame()
		end,

		--pet
		UNIT_PET = function(self, ...)
			self:UpdateUnitFrame()
		end,

		--player connection
		UNIT_CONNECTION = function(self, ...)
			if (UnitIsConnected (self.unit)) then
				self:UpdateUnitFrame()
			end
		end,
		PARTY_MEMBER_ENABLE = function(self, ...)
			if (UnitIsConnected (self.unit)) then
				self:UpdateName()
			end
		end,
	}

-- ~unitframe
local globalBaseFrameLevel = 1 -- to be increased + used across each new plate
function detailsFramework:CreateUnitFrame(parent, name, unitFrameSettingsOverride, healthBarSettingsOverride, castBarSettingsOverride, powerBarSettingsOverride)
	local parentName = name or ("DetailsFrameworkUnitFrame" .. tostring(math.random(1, 100000000)))

	--create the main unit frame
	local mewUnitFrame = CreateFrame("button", parentName, parent, "BackdropTemplate")

	--base level
	--local baseFrameLevel = f:GetFrameLevel()
	local baseFrameLevel = globalBaseFrameLevel
	globalBaseFrameLevel = globalBaseFrameLevel + 50

	mewUnitFrame:SetFrameLevel(baseFrameLevel)

	--create the healthBar
	local healthBar = detailsFramework:CreateHealthBar(mewUnitFrame, false, healthBarSettingsOverride)
	healthBar:SetFrameLevel(baseFrameLevel + 1)
	mewUnitFrame.healthBar = healthBar

	--create the power bar
	local powerBar = detailsFramework:CreatePowerBar(mewUnitFrame, false, powerBarSettingsOverride)
	powerBar:SetFrameLevel(baseFrameLevel + 2)
	mewUnitFrame.powerBar = powerBar

	--create the castBar
	local castBar = detailsFramework:CreateCastBar(mewUnitFrame, false, castBarSettingsOverride)
	castBar:SetFrameLevel(baseFrameLevel + 3)
	mewUnitFrame.castBar = castBar

	--border frame
	local borderFrame = detailsFramework:CreateBorderFrame(mewUnitFrame, mewUnitFrame:GetName() .. "Border")
	borderFrame:SetFrameLevel(mewUnitFrame:GetFrameLevel() + 5)
	mewUnitFrame.border = borderFrame

	--overlay frame (widgets that need to stay above the unit frame)
	local overlayFrame = CreateFrame("frame", "$parentOverlayFrame", mewUnitFrame, "BackdropTemplate")
	overlayFrame:SetFrameLevel(mewUnitFrame:GetFrameLevel() + 6)
	mewUnitFrame.overlayFrame = overlayFrame

	--unit frame layers
		do
			--artwork
			mewUnitFrame.unitName = mewUnitFrame:CreateFontString(nil, "artwork", "GameFontHighlightSmall")
			PixelUtil.SetPoint(mewUnitFrame.unitName, "topleft", healthBar, "topleft", 2, -2, 1, 1)

			--target overlay - it's parented in the healthbar so other widgets won't get the overlay
			mewUnitFrame.targetOverlay = overlayFrame:CreateTexture(nil, "artwork")
			mewUnitFrame.targetOverlay:SetTexture(healthBar:GetTexture())
			mewUnitFrame.targetOverlay:SetBlendMode("ADD")
			mewUnitFrame.targetOverlay:SetAlpha(.5)
			mewUnitFrame.targetOverlay:SetAllPoints(healthBar)
		end

	--mixins
		--inject mixins
		detailsFramework:Mixin(mewUnitFrame, detailsFramework.UnitFrameFunctions)

		--create the settings table and copy the overrides into it, the table is set into the frame after the mixin
		local unitFrameSettings = detailsFramework.table.copy({}, detailsFramework.UnitFrameFunctions.Settings)
		if (unitFrameSettingsOverride) then
			unitFrameSettings = detailsFramework.table.copy(unitFrameSettings, unitFrameSettingsOverride)
		end
		mewUnitFrame.Settings = unitFrameSettings

	--initialize scripts
		--unitframe
		mewUnitFrame:Initialize()

	return mewUnitFrame
end


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--horizontal scroll frame

local timeline_options = {
	width = 400,
	height = 700,
	line_height = 20,
	line_padding = 1,

	show_elapsed_timeline = true,
	elapsed_timeline_height = 20,

	--space to put the player/spell name and icons
	header_width = 150,

	--how many pixels will be use to represent 1 second
	pixels_per_second = 20,

	scale_min = 0.15,
	scale_max = 1,

	backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	backdrop_color = {0, 0, 0, 0.2},
	backdrop_color_highlight = {.2, .2, .2, 0.4},
	backdrop_border_color = {0.1, 0.1, 0.1, .2},

	slider_backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	slider_backdrop_color = {0, 0, 0, 0.2},
	slider_backdrop_border_color = {0.1, 0.1, 0.1, .2},

	title_template = "ORANGE_FONT_TEMPLATE",
	text_tempate = "OPTIONS_FONT_TEMPLATE",

	on_enter = function(self)
		self:SetBackdropColor(unpack(self.backdrop_color_highlight))
	end,
	on_leave = function(self)
		self:SetBackdropColor(unpack(self.backdrop_color))
	end,

	block_on_enter = function(self)

	end,
	block_on_leave = function(self)

	end,
}

local elapsedtime_frame_options = {
	backdrop = {bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	backdrop_color = {.3, .3, .3, .7},

	text_color = {1, 1, 1, 1},
	text_size = 12,
	text_font = "Arial Narrow",
	text_outline = "NONE",

	height = 20,

	distance = 200, --distance in pixels between each label informing the time
	distance_min = 50, --minimum distance in pixels
	draw_line = true, --if true it'll draw a vertical line to represent a segment
	draw_line_color = {1, 1, 1, 0.2},
	draw_line_thickness = 1,
}

detailsFramework.TimeLineElapsedTimeFunctions = {
	--get a label and update its appearance
	GetLabel = function(self, index)
		local label = self.labels [index]

		if (not label) then
			label = self:CreateFontString(nil, "artwork", "GameFontNormal")
			label.line = self:CreateTexture(nil, "artwork")
			label.line:SetColorTexture(1, 1, 1)
			label.line:SetPoint("topleft", label, "bottomleft", 0, -2)
			self.labels [index] = label
		end

		detailsFramework:SetFontColor(label, self.options.text_color)
		detailsFramework:SetFontSize(label, self.options.text_size)
		detailsFramework:SetFontFace (label, self.options.text_font)
		detailsFramework:SetFontOutline (label, self.options.text_outline)

		if (self.options.draw_line) then
			label.line:SetVertexColor(unpack(self.options.draw_line_color))
			label.line:SetWidth(self.options.draw_line_thickness)
			label.line:Show()
		else
			label.line:Hide()
		end

		return label
	end,

	Reset = function(self)
		for i = 1, #self.labels do
			self.labels [i]:Hide()
		end
	end,

	Refresh = function(self, elapsedTime, scale)
		local parent = self:GetParent()

		self:SetHeight(self.options.height)
		local effectiveArea = self:GetWidth() --already scaled down width
		local pixelPerSecond = elapsedTime / effectiveArea --how much 1 pixels correlate to time

		local distance = self.options.distance --pixels between each segment
		local minDistance = self.options.distance_min --min pixels between each segment

		--scale the distance between each label showing the time with the parent's scale
		distance = distance * scale
		distance = max(distance, minDistance)

		local amountSegments = ceil (effectiveArea / distance)

		for i = 1, amountSegments do
			local label = self:GetLabel (i)
			local xOffset = distance * (i - 1)
			label:SetPoint("left", self, "left", xOffset, 0)

			local secondsOfTime = pixelPerSecond * xOffset

			label:SetText(detailsFramework:IntegerToTimer(floor(secondsOfTime)))

			if (label.line:IsShown()) then
				label.line:SetHeight(parent:GetParent():GetHeight())
			end

			label:Show()
		end
	end,
}

--creates a frame to show the elapsed time in a row
function detailsFramework:CreateElapsedTimeFrame(parent, name, options)
	local elapsedTimeFrame = CreateFrame("frame", name, parent, "BackdropTemplate")

	detailsFramework:Mixin(elapsedTimeFrame, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(elapsedTimeFrame, detailsFramework.LayoutFrame)
	detailsFramework:Mixin(elapsedTimeFrame, detailsFramework.TimeLineElapsedTimeFunctions)

	elapsedTimeFrame:BuildOptionsTable(elapsedtime_frame_options, options)

	elapsedTimeFrame:SetBackdrop(elapsedTimeFrame.options.backdrop)
	elapsedTimeFrame:SetBackdropColor(unpack(elapsedTimeFrame.options.backdrop_color))

	elapsedTimeFrame.labels = {}

	return elapsedTimeFrame
end


detailsFramework.TimeLineBlockFunctions = {
	--self is the line
	SetBlock = function(self, index, blockInfo)
		--get the block information
		--see what is the current scale
		--adjust the block position

		local block = self:GetBlock (index)

		--need:
			--the total time of the timeline
			--the current scale of the timeline
			--the elapsed time of this block
			--icon of the block
			--text
			--background color

	end,

	SetBlocksFromData = function(self)
		local parent = self:GetParent():GetParent()
		local data = parent.data
		local defaultColor = parent.defaultColor --guarantee to have a value

		self:Show()

		--none of these values are scaled, need to calculate
		local pixelPerSecond = parent.pixelPerSecond
		local totalLength = parent.totalLength
		local scale = parent.currentScale

		pixelPerSecond = pixelPerSecond * scale

		local headerWidth = parent.headerWidth

		--dataIndex stores which line index from the data this line will use
		--lineData store members: .text .icon .timeline
		local lineData = data.lines[self.dataIndex]

		self.spellId = lineData.spellId

		--if there's an icon, anchor the text at the right side of the icon
		--this is the title and icon of the title
		if (lineData.icon) then
			self.icon:SetTexture(lineData.icon)
			if (lineData.coords) then
				self.icon:SetTexCoord(unpack(lineData.coords))
			else
				self.icon:SetTexCoord(.1, .9, .1, .9)
			end
			self.text:SetText(lineData.text or "")
			self.text:SetPoint("left", self.icon.widget, "right", 2, 0)
		else
			self.icon:SetTexture(nil)
			self.text:SetText(lineData.text or "")
			self.text:SetPoint("left", self, "left", 2, 0)
		end

		if (self.dataIndex % 2 == 1) then
			self:SetBackdropColor(0, 0, 0, 0)
		else
			local r, g, b, a = unpack(self.backdrop_color)
			self:SetBackdropColor(r, g, b, a)
		end

		self:SetWidth(5000)

		local timelineData = lineData.timeline
		local spellId = lineData.spellId
		local useIconOnBlock = data.useIconOnBlocks

		local baseFrameLevel = parent:GetFrameLevel() + 10

		for i = 1, #timelineData do
			local blockInfo = timelineData[i]

			local timeInSeconds = blockInfo[1]
			local length = blockInfo[2]
			local isAura = blockInfo[3]
			local auraDuration = blockInfo[4]
			local blockSpellId = blockInfo[5]

			local payload = blockInfo.payload

			local xOffset = pixelPerSecond * timeInSeconds
			local width = pixelPerSecond * length

			if (timeInSeconds < -0.2) then
				xOffset = xOffset / 2.5
			end

			local block = self:GetBlock(i)
			block:Show()
			block:SetFrameLevel(baseFrameLevel + i)

			PixelUtil.SetPoint(block, "left", self, "left", xOffset + headerWidth, 0)

			block.info.spellId = blockSpellId or spellId
			block.info.time = timeInSeconds
			block.info.duration = auraDuration
			block.info.payload = payload

			if (useIconOnBlock) then
				local iconTexture = lineData.icon
				if (blockSpellId) then
					iconTexture = GetSpellTexture(blockSpellId)
				end

				block.icon:SetTexture(iconTexture)
				block.icon:SetTexCoord(.1, .9, .1, .9)
				block.icon:SetAlpha(.834)
				block.icon:SetSize(self:GetHeight(), self:GetHeight())

				if (timeInSeconds < -0.2) then
					block.icon:SetDesaturated(true)
				else
					block.icon:SetDesaturated(false)
				end

				PixelUtil.SetSize(block, self:GetHeight(), self:GetHeight())

				if (isAura) then
					block.auraLength:Show()
					block.auraLength:SetWidth(pixelPerSecond * isAura)
					block:SetWidth(max(pixelPerSecond * isAura, 16))
				else
					block.auraLength:Hide()
				end

				block.background:SetVertexColor(0, 0, 0, 0)
			else
				block.background:SetVertexColor(0, 0, 0, 0)
				PixelUtil.SetSize(block, max(width, 16), self:GetHeight())
				block.auraLength:Hide()
			end
		end
	end,

	GetBlock = function(self, index)
		local block = self.blocks [index]
		if (not block) then
			block = CreateFrame("frame", nil, self, "BackdropTemplate")
			self.blocks [index] = block

			local background = block:CreateTexture(nil, "background")
			background:SetColorTexture(1, 1, 1, 1)
			local icon = block:CreateTexture(nil, "artwork")
			local text = block:CreateFontString(nil, "artwork")
			local auraLength = block:CreateTexture(nil, "border")

			background:SetAllPoints()
			icon:SetPoint("left")
			text:SetPoint("left", icon, "left", 2, 0)
			auraLength:SetPoint("topleft", icon, "topleft", 0, 0)
			auraLength:SetPoint("bottomleft", icon, "bottomleft", 0, 0)
			auraLength:SetColorTexture(1, 1, 1, 1)
			auraLength:SetVertexColor(1, 1, 1, 0.1)

			block.icon = icon
			block.text = text
			block.background = background
			block.auraLength = auraLength

			block:SetScript("OnEnter", self:GetParent():GetParent().options.block_on_enter)
			block:SetScript("OnLeave", self:GetParent():GetParent().options.block_on_leave)

			block:SetMouseClickEnabled(false)
			block.info = {}
		end

		return block
	end,

	Reset = function(self)
		--attention, it doesn't reset icon texture, text and background color
		for i = 1, #self.blocks do
			self.blocks [i]:Hide()
		end
		self:Hide()
	end,
}

detailsFramework.TimeLineFunctions = {
	GetLine = function(self, index)
		local line = self.lines [index]
		if (not line) then
			--create a new line
			line = CreateFrame("frame", "$parentLine" .. index, self.body, "BackdropTemplate")
			detailsFramework:Mixin(line, detailsFramework.TimeLineBlockFunctions)
			self.lines [index] = line

			local lineHeader = CreateFrame("frame", nil, line, "BackdropTemplate")
			lineHeader:SetPoint("topleft", line, "topleft", 0, 0)
			lineHeader:SetPoint("bottomleft", line, "bottomleft", 0, 0)
			lineHeader:SetScript("OnEnter", self.options.header_on_enter)
			lineHeader:SetScript("OnLeave", self.options.header_on_leave)

			line.lineHeader = lineHeader

			--store the individual textures that shows the timeline information
			line.blocks = {}
			line.SetBlock = detailsFramework.TimeLineBlockFunctions.SetBlock
			line.GetBlock = detailsFramework.TimeLineBlockFunctions.GetBlock

			--set its parameters

			if (self.options.show_elapsed_timeline) then
				line:SetPoint("topleft", self.body, "topleft", 1, -((index-1) * (self.options.line_height + 1)) - 2 - self.options.elapsed_timeline_height)
			else
				line:SetPoint("topleft", self.body, "topleft", 1, -((index-1) * (self.options.line_height + 1)) - 1)
			end
			line:SetSize(1, self.options.line_height) --width is set when updating the frame

			line:SetScript("OnEnter", self.options.on_enter)
			line:SetScript("OnLeave", self.options.on_leave)
			line:SetMouseClickEnabled(false)

			line:SetBackdrop(self.options.backdrop)
			line:SetBackdropColor(unpack(self.options.backdrop_color))
			line:SetBackdropBorderColor(unpack(self.options.backdrop_border_color))

			local icon = detailsFramework:CreateImage(line, "", self.options.line_height, self.options.line_height)
			icon:SetPoint("left", line, "left", 2, 0)
			line.icon = icon

			local text = detailsFramework:CreateLabel(line, "", detailsFramework:GetTemplate("font", self.options.title_template))
			text:SetPoint("left", icon.widget, "right", 2, 0)
			line.text = text

			line.backdrop_color = self.options.backdrop_color or {.1, .1, .1, .3}
			line.backdrop_color_highlight = self.options.backdrop_color_highlight or {.3, .3, .3, .5}
		end

		return line
	end,

	ResetAllLines = function(self)
		for i = 1, #self.lines do
			self.lines[i]:Reset()
		end
	end,

	AdjustScale = function(self, index)

	end,

	--todo
	--make the on enter and leave tooltips
	--set icons and texts
	--skin the sliders

	RefreshTimeLine = function(self)
		--debug
		--self.currentScale = 1

		--calculate the total width
		local pixelPerSecond = self.options.pixels_per_second
		local totalLength = self.data.length or 1
		local currentScale = self.currentScale

		self.scaleSlider:Enable()

		--how many pixels represent 1 second
		local bodyWidth = totalLength * pixelPerSecond * currentScale
		self.body:SetWidth(bodyWidth + self.options.header_width)
		self.body.effectiveWidth = bodyWidth

		--reduce the default canvas size from the body with and don't allow the max value be negative
		local newMaxValue = max(bodyWidth - (self:GetWidth() - self.options.header_width), 0)

		--adjust the scale slider range
		local oldMin, oldMax = self.horizontalSlider:GetMinMaxValues()
		self.horizontalSlider:SetMinMaxValues(0, newMaxValue)
		self.horizontalSlider:SetValue(detailsFramework:MapRangeClamped(oldMin, oldMax, 0, newMaxValue, self.horizontalSlider:GetValue()))

		local defaultColor = self.data.defaultColor or {1, 1, 1, 1}

		--cache values
		self.pixelPerSecond = pixelPerSecond
		self.totalLength = totalLength
		self.defaultColor = defaultColor
		self.headerWidth = self.options.header_width

		--calculate the total height
		local lineHeight = self.options.line_height
		local linePadding = self.options.line_padding

		local bodyHeight = (lineHeight + linePadding) * #self.data.lines
		self.body:SetHeight(bodyHeight)
		self.verticalSlider:SetMinMaxValues(0, max(bodyHeight - self:GetHeight(), 0))
		self.verticalSlider:SetValue(0)

		--refresh lines
		self:ResetAllLines()
		for i = 1, #self.data.lines do
			local line = self:GetLine(i)
			line.dataIndex = i --this index is used inside the line update function to know which data to get
			line.lineHeader:SetWidth(self.options.header_width)
			line:SetBlocksFromData() --the function to update runs within the line object
		end

		--refresh elapsed time frame
		--the elapsed frame must have a width before the refresh function is called
		self.elapsedTimeFrame:ClearAllPoints()
		self.elapsedTimeFrame:SetPoint("topleft", self.body, "topleft", self.options.header_width, 0)
		self.elapsedTimeFrame:SetPoint("topright", self.body, "topright", 0, 0)
		self.elapsedTimeFrame:Reset()

		self.elapsedTimeFrame:Refresh(self.data.length, self.currentScale)
	end,

	SetData = function(self, data)
		self.data = data
		self:RefreshTimeLine()
	end,
}

--creates a regular scroll in horizontal position
function detailsFramework:CreateTimeLineFrame(parent, name, options, timelineOptions)
	local width = options and options.width or timeline_options.width
	local height = options and options.height or timeline_options.height
	local scrollWidth = 800 --placeholder until the timeline receives data
	local scrollHeight = 800 --placeholder until the timeline receives data

	local frameCanvas = CreateFrame("scrollframe", name, parent, "BackdropTemplate")

	detailsFramework:Mixin(frameCanvas, detailsFramework.TimeLineFunctions)
	detailsFramework:Mixin(frameCanvas, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(frameCanvas, detailsFramework.LayoutFrame)

	frameCanvas.data = {}
	frameCanvas.lines = {}
	frameCanvas.currentScale = 0.5
	frameCanvas:SetSize(width, height)

	detailsFramework:ApplyStandardBackdrop(frameCanvas)

	local frameBody = CreateFrame("frame", nil, frameCanvas, "BackdropTemplate")
	frameBody:SetSize(scrollWidth, scrollHeight)

	frameCanvas:SetScrollChild(frameBody)
	frameCanvas.body = frameBody

	frameCanvas:BuildOptionsTable(timeline_options, options)

	--create elapsed time frame
	frameCanvas.elapsedTimeFrame = detailsFramework:CreateElapsedTimeFrame(frameBody, frameCanvas:GetName() and frameCanvas:GetName() .. "ElapsedTimeFrame", timelineOptions)

	--create horizontal slider
		local horizontalSlider = CreateFrame("slider", frameCanvas:GetName() .. "HorizontalSlider", parent, "BackdropTemplate")
		horizontalSlider.bg = horizontalSlider:CreateTexture(nil, "background")
		horizontalSlider.bg:SetAllPoints(true)
		horizontalSlider.bg:SetTexture(0, 0, 0, 0.5)
		frameCanvas.horizontalSlider = horizontalSlider

		horizontalSlider:SetBackdrop(frameCanvas.options.slider_backdrop)
		horizontalSlider:SetBackdropColor(unpack(frameCanvas.options.slider_backdrop_color))
		horizontalSlider:SetBackdropBorderColor(unpack(frameCanvas.options.slider_backdrop_border_color))

		horizontalSlider.thumb = horizontalSlider:CreateTexture(nil, "OVERLAY")
		horizontalSlider.thumb:SetTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
		horizontalSlider.thumb:SetSize(24, 24)
		horizontalSlider.thumb:SetVertexColor(0.6, 0.6, 0.6, 0.95)
		horizontalSlider:SetThumbTexture(horizontalSlider.thumb)

		horizontalSlider:SetOrientation("horizontal")
		horizontalSlider:SetSize(width + 20, 20)
		horizontalSlider:SetPoint("topleft", frameCanvas, "bottomleft")
		horizontalSlider:SetMinMaxValues(0, scrollWidth)
		horizontalSlider:SetValue(0)
		horizontalSlider:SetScript("OnValueChanged", function(self)
			local _, maxValue = horizontalSlider:GetMinMaxValues()
			local stepValue = ceil(ceil(self:GetValue() * maxValue) / max(maxValue, SMALL_FLOAT))
			if (stepValue ~= horizontalSlider.currentValue) then
				horizontalSlider.currentValue = stepValue
				frameCanvas:SetHorizontalScroll(stepValue)
			end
		end)

	--create scale slider
		local scaleSlider = CreateFrame("slider", frameCanvas:GetName() .. "ScaleSlider", parent, "BackdropTemplate")
		scaleSlider.bg = scaleSlider:CreateTexture(nil, "background")
		scaleSlider.bg:SetAllPoints(true)
		scaleSlider.bg:SetTexture(0, 0, 0, 0.5)
		scaleSlider:Disable()
		frameCanvas.scaleSlider = scaleSlider

		scaleSlider:SetBackdrop(frameCanvas.options.slider_backdrop)
		scaleSlider:SetBackdropColor(unpack(frameCanvas.options.slider_backdrop_color))
		scaleSlider:SetBackdropBorderColor(unpack(frameCanvas.options.slider_backdrop_border_color))

		scaleSlider.thumb = scaleSlider:CreateTexture(nil, "OVERLAY")
		scaleSlider.thumb:SetTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
		scaleSlider.thumb:SetSize(24, 24)
		scaleSlider.thumb:SetVertexColor(0.6, 0.6, 0.6, 0.95)
		scaleSlider:SetThumbTexture(scaleSlider.thumb)

		scaleSlider:SetOrientation("horizontal")
		scaleSlider:SetSize(width + 20, 20)
		scaleSlider:SetPoint("topleft", horizontalSlider, "bottomleft", 0, -2)
		scaleSlider:SetMinMaxValues(frameCanvas.options.scale_min, frameCanvas.options.scale_max)
		scaleSlider:SetValue(detailsFramework:GetRangeValue(frameCanvas.options.scale_min, frameCanvas.options.scale_max, 0.5))

		scaleSlider:SetScript("OnValueChanged", function(self)
			local stepValue = ceil(self:GetValue() * 100) / 100
			if (stepValue ~= frameCanvas.currentScale) then
				local current = stepValue
				frameCanvas.currentScale = stepValue
				frameCanvas:RefreshTimeLine()
			end
		end)

	--create vertical slider
		local verticalSlider = CreateFrame("slider", frameCanvas:GetName() .. "VerticalSlider", parent, "BackdropTemplate")
		verticalSlider.bg = verticalSlider:CreateTexture(nil, "background")
		verticalSlider.bg:SetAllPoints(true)
		verticalSlider.bg:SetTexture(0, 0, 0, 0.5)
		frameCanvas.verticalSlider = verticalSlider

		verticalSlider:SetBackdrop(frameCanvas.options.slider_backdrop)
		verticalSlider:SetBackdropColor(unpack(frameCanvas.options.slider_backdrop_color))
		verticalSlider:SetBackdropBorderColor(unpack(frameCanvas.options.slider_backdrop_border_color))

		verticalSlider.thumb = verticalSlider:CreateTexture(nil, "OVERLAY")
		verticalSlider.thumb:SetTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
		verticalSlider.thumb:SetSize(24, 24)
		verticalSlider.thumb:SetVertexColor(0.6, 0.6, 0.6, 0.95)
		verticalSlider:SetThumbTexture(verticalSlider.thumb)

		verticalSlider:SetOrientation("vertical")
		verticalSlider:SetSize(20, height - 2)
		verticalSlider:SetPoint("topleft", frameCanvas, "topright", 0, 0)
		verticalSlider:SetMinMaxValues(0, scrollHeight)
		verticalSlider:SetValue(0)
		verticalSlider:SetScript("OnValueChanged", function(self)
		    frameCanvas:SetVerticalScroll(self:GetValue())
		end)

	--mouse scroll
		frameCanvas:EnableMouseWheel(true)
		frameCanvas:SetScript("OnMouseWheel", function(self, delta)
			local minValue, maxValue = horizontalSlider:GetMinMaxValues()
			local currentHorizontal = horizontalSlider:GetValue()

			if (IsShiftKeyDown() and delta < 0) then
				local amountToScroll = frameBody:GetHeight() / 20
				verticalSlider:SetValue(verticalSlider:GetValue() + amountToScroll)

			elseif (IsShiftKeyDown() and delta > 0) then
				local amountToScroll = frameBody:GetHeight() / 20
				verticalSlider:SetValue(verticalSlider:GetValue() - amountToScroll)

			elseif (IsControlKeyDown() and delta > 0) then
				scaleSlider:SetValue(min(scaleSlider:GetValue() + 0.1, 1))

			elseif (IsControlKeyDown() and delta < 0) then
				scaleSlider:SetValue(max(scaleSlider:GetValue() - 0.1, 0.15))

			elseif (delta < 0 and currentHorizontal < maxValue) then
				local amountToScroll = frameBody:GetWidth() / 20
				horizontalSlider:SetValue(currentHorizontal + amountToScroll)

			elseif (delta > 0 and maxValue > 1) then
				local amountToScroll = frameBody:GetWidth() / 20
				horizontalSlider:SetValue(currentHorizontal - amountToScroll)

			end
		end)

	--mouse drag
	frameBody:SetScript("OnMouseDown", function(self, button)
		local x = GetCursorPosition()
		self.MouseX = x

		frameBody:SetScript("OnUpdate", function(self, deltaTime)
			local x = GetCursorPosition()
			local deltaX = self.MouseX - x
			local current = horizontalSlider:GetValue()
			horizontalSlider:SetValue(current +(deltaX * 1.2) *((IsShiftKeyDown() and 2) or(IsAltKeyDown() and 0.5) or 1))
			self.MouseX = x
		end)
	end)

	frameBody:SetScript("OnMouseUp", function(self, button)
		frameBody:SetScript("OnUpdate", nil)
	end)

	return frameCanvas
end


--[=[
local f = CreateFrame("frame", "TestFrame", UIParent)
f:SetPoint("center")
f:SetSize(900, 420)
f:SetBackdrop({bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16,	insets = {left = 1, right = 1, top = 0, bottom = 1}})

local scroll = DF:CreateTimeLineFrame (f, "$parentTimeLine", {width = 880, height = 400})
scroll:SetPoint("topleft", f, "topleft", 0, 0)

--need fake data to test fills
scroll:SetData ({
	length = 360,
	defaultColor = {1, 1, 1, 1},
	lines = {
			{text = "player 1", icon = "", timeline = {
				--each table here is a block shown in the line
				--is an indexed table with: [1] time [2] length [3] color (if false, use the default) [4] text [5] icon [6] tooltip: if number = spellID tooltip, if table is text lines
				{1, 10}, {13, 11}, {25, 7}, {36, 5}, {55, 18}, {76, 30}, {105, 20}, {130, 11}, {155, 11}, {169, 7}, {199, 16}, {220, 18}, {260, 10}, {290, 23}, {310, 30}, {350, 10}
			}
		}, --end of line 1
	},
})


f:Hide()

--scroll.body:SetScale(0.5)

--]=]

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--error message box

function detailsFramework:ShowErrorMessage (errorMessage, titleText)

	if (not detailsFramework.ErrorMessagePanel) then
		local f = CreateFrame("frame", "DetailsFrameworkErrorMessagePanel", UIParent, "BackdropTemplate")
		f:SetSize(400, 120)
		f:SetFrameStrata("FULLSCREEN")
		f:SetPoint("center", UIParent, "center", 0, 100)
		f:EnableMouse(true)
		f:SetMovable(true)
		f:RegisterForDrag ("LeftButton")
		f:SetScript("OnDragStart", function() f:StartMoving() end)
		f:SetScript("OnDragStop", function() f:StopMovingOrSizing() end)
		f:SetScript("OnMouseDown", function(self, button) if (button == "RightButton") then f:Hide() end end)
		tinsert(UISpecialFrames, "DetailsFrameworkErrorMessagePanel")
		detailsFramework.ErrorMessagePanel = f

		detailsFramework:CreateTitleBar (f, "Details! Framework Error!")
		detailsFramework:ApplyStandardBackdrop(f)

		local errorLabel = f:CreateFontString(nil, "overlay", "GameFontNormal")
		errorLabel:SetPoint("top", f, "top", 0, -25)
		errorLabel:SetJustifyH("center")
		errorLabel:SetSize(360, 66)
		f.errorLabel = errorLabel

		local button_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")
		local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")

		local closeButton = detailsFramework:CreateButton(f, nil, 60, 20, "close", nil, nil, nil, nil, nil, nil, options_dropdown_template)
		closeButton:SetPoint("bottom", f, "bottom", 0, 5)
		f.closeButton = closeButton

		closeButton:SetClickFunction(function()
			f:Hide()
		end)

		f.ShowAnimation = detailsFramework:CreateAnimationHub (f, function()
			f:SetBackdropBorderColor(0, 0, 0, 0)
			f.TitleBar:SetBackdropBorderColor(0, 0, 0, 0)
		end, function()
			f:SetBackdropBorderColor(0, 0, 0, 1)
			f.TitleBar:SetBackdropBorderColor(0, 0, 0, 1)
		end)
		detailsFramework:CreateAnimation(f.ShowAnimation, "scale", 1, .075, .2, .2, 1.1, 1.1, "center", 0, 0)
		detailsFramework:CreateAnimation(f.ShowAnimation, "scale", 2, .075, 1, 1, .90, .90, "center", 0, 0)

		f.FlashTexture = f:CreateTexture(nil, "overlay")
		f.FlashTexture:SetColorTexture(1, 1, 1, 1)
		f.FlashTexture:SetAllPoints()

		f.FlashAnimation = detailsFramework:CreateAnimationHub (f.FlashTexture, function() f.FlashTexture:Show() end, function() f.FlashTexture:Hide() end)
		detailsFramework:CreateAnimation(f.FlashAnimation, "alpha", 1, .075, 0, .05)
		detailsFramework:CreateAnimation(f.FlashAnimation, "alpha", 2, .075, .1, 0)

		f:Hide()
	end

	detailsFramework.ErrorMessagePanel:Show()
	detailsFramework.ErrorMessagePanel.errorLabel:SetText(errorMessage)
	detailsFramework.ErrorMessagePanel.TitleLabel:SetText(titleText)
	detailsFramework.ErrorMessagePanel.ShowAnimation:Play()
	detailsFramework.ErrorMessagePanel.FlashAnimation:Play()
end

--[[
	DF:SetPointOffsets(frame, xOffset, yOffset)

	Set an offset into the already existing offset of the frame
	If passed xOffset:1 and yOffset:1 and the frame has 1 -1,  the new offset will be 2 -2
	This function is great to create a 1 knob for distance

	@frame: a frame to have the offsets changed
	@xOffset: the amount to apply into the x offset
	@yOffset: the amount to apply into the y offset
--]]
function detailsFramework:SetPointOffsets(frame, xOffset, yOffset)
	for i = 1, frame:GetNumPoints() do
		local anchor1, anchorTo, anchor2, x, y = frame:GetPoint(i)
		x = x or 0
		y = y or 0

		if (x >= 0) then
			xOffset = x + xOffset

		elseif (x < 0) then
			xOffset = x - xOffset
		end

		if (y >= 0) then
			yOffset = y + yOffset

		elseif (y < 0) then
			yOffset = y - yOffset
		end

		frame:SetPoint(anchor1, anchorTo, anchor2, xOffset, yOffset)
	end
end

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--list box

detailsFramework.ListboxFunctions = {
	scrollRefresh = function(self, data, offset, totalLines)
		for i = 1, totalLines do
			local index = i + offset
			local lineData = data[index] --what is shown in the textentries, array

			if (lineData) then
				local line = self:GetLine(i)
				line.dataIndex = index
				line.deleteButton:SetClickFunction(detailsFramework.ListboxFunctions.deleteEntry, data, index)
				line.indexText:SetText(index)

				local amountEntries = #lineData
				for o = 1, amountEntries do
					--data
					local textEntry = line.widgets[o]
					textEntry.dataTable = lineData
					textEntry.dataTableIndex = o
					local text = lineData[o]
					textEntry:SetText(text)
				end
			end
		end
	end,

	addEntry = function(self)
		local frameCanvas = self:GetParent()
		local data = frameCanvas.data
		local newEntry = {}
		for i = 1, frameCanvas.headerLength do
			tinsert(newEntry, "")
		end
		tinsert(data, newEntry)
		frameCanvas.scrollBox:Refresh()
	end,

	deleteEntry = function(self, button, data, index)
		tremove(data, index)
		--get the line, get the scrollframe
		self:GetParent():GetParent():Refresh()
	end,

	createScrollLine = function(self, index)
		local listBox = self:GetParent()
		local line = CreateFrame("frame", self:GetName().. "line_" .. index, self, "BackdropTemplate")

		line:SetPoint("topleft", self, "topleft", 1, -((index-1)*(self.lineHeight+1)) - 1)
		line:SetSize(self:GetWidth() - 28, self.lineHeight) -- -28 space for the scrollbar

		local options = listBox.options
		line:SetBackdrop(options.line_backdrop)
		line:SetBackdropColor(unpack(options.line_backdrop_color))
		line:SetBackdropBorderColor(unpack(options.line_backdrop_border_color))

		detailsFramework:Mixin(line, detailsFramework.HeaderFunctions)

		line.widgets = {}

		for i = 1, (listBox.headerLength+2) do --+2 to add the delete button and index
			local headerColumn = listBox.headerTable[i]

			if (headerColumn.isDelete) then
				local deleteButton = detailsFramework:CreateButton(line, detailsFramework.ListboxFunctions.deleteEntry, 20, self.lineHeight, "X", listBox.data, index, nil, nil, nil, nil, detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
				line.deleteButton = deleteButton
				line:AddFrameToHeaderAlignment(deleteButton)

			elseif (headerColumn.isIndex) then
				local indexText = detailsFramework:CreateLabel(line)
				line.indexText = indexText
				line:AddFrameToHeaderAlignment(indexText)

			elseif (headerColumn.text) then
				local template = detailsFramework.table.copy({}, detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE"))
				template.backdropcolor = {.1, .1, .1, .7}
				template.backdropbordercolor = {.2, .2, .2, .6}

				local textEntry = detailsFramework:CreateTextEntry(line, function()end, headerColumn.width, self.lineHeight, nil, nil, nil, template)
				textEntry:SetHook("OnEditFocusGained", function() textEntry:HighlightText(0) end)
				textEntry:SetHook("OnEditFocusLost", function()
					textEntry:HighlightText(0, 0)
					local text = textEntry.text
					local dataTable = textEntry.dataTable
					dataTable[textEntry.dataTableIndex] = text
				end)
				tinsert(line.widgets, textEntry)
				line:AddFrameToHeaderAlignment(textEntry)
			end
		end

		line:AlignWithHeader(listBox.header, "left")
		return line
	end,

	SetData = function(frameCanvas, newData)
		if (type(newData) ~= "table") then
			error("ListBox:SetData received an invalid newData on parameter 2.")
			return
		end

		frameCanvas.data = newData
		frameCanvas.scrollBox:SetData(newData)
		frameCanvas.scrollBox:Refresh()
	end,
}

local listbox_options = {
	width = 800,
	height = 600,
	auto_width = true,
	line_height = 16,
	line_backdrop = {bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true},
	line_backdrop_color = {.1, .1, .1, .6},
	line_backdrop_border_color = {0, 0, 0, .5},
}

--@parent: parent frame
--@name: name of the frame to be created
--@data: table with current data to fill the column, this table are also used for values changed or added
--@options: table with options to overwrite the default setting from 'listbox_options'
--@header: a table to create a header widget
--@header_options: a table with options to overwrite the default header options
function detailsFramework:CreateListBox(parent, name, data, options, headerTable, headerOptions)

	options = options or {}
	name = name or "ListboxUnamed_" .. (math.random(100000, 1000000))

	--canvas
	local frameCanvas = CreateFrame("scrollframe", name, parent, "BackdropTemplate")
	detailsFramework:Mixin(frameCanvas, detailsFramework.ListboxFunctions)
	detailsFramework:Mixin(frameCanvas, detailsFramework.OptionsFunctions)
	detailsFramework:Mixin(frameCanvas, detailsFramework.LayoutFrame)
	frameCanvas.headerTable = headerTable

	if (not data or type(data) ~= "table") then
		error("CreateListBox() parameter 3 'data' must be a table.")
	end

	frameCanvas.data = data
	frameCanvas.lines = {}
	detailsFramework:ApplyStandardBackdrop(frameCanvas)
	frameCanvas:BuildOptionsTable(listbox_options, options)

	--header
		--check for default values in the header
		headerTable = headerTable or {
			{text = "Spell Id", width = 70},
			{text = "Spell Name", width = 70},
		}
		headerOptions = headerOptions or {
			padding = 2,
		}

		--each header is an entry in the data, if the header has 4 indexes the data has sub tables with 4 indexes as well
		frameCanvas.headerLength = #headerTable

		--add the detele line column into the header frame
		tinsert(headerTable, 1, {text = "#", width = 20, isIndex = true}) --isDelete signals the createScrollLine() to make the delete button for the line
		tinsert(headerTable, {text = "Delete", width = 50, isDelete = true}) --isDelete signals the createScrollLine() to make the delete button for the line

		local header = detailsFramework:CreateHeader(frameCanvas, headerTable, headerOptions)
		--set the header point
		header:SetPoint("topleft", frameCanvas, "topleft", 5, -5)
		frameCanvas.header = header

	--auto size
		if (frameCanvas.options.auto_width) then
			local width = 10 --padding 5 on each side
			width = width + 20 --scrollbar reserved space
			local headerPadding = headerOptions.padding or 0

			for _, header in pairs(headerTable) do
				if (header.width) then
					width = width + header.width + headerPadding
				end
			end

			frameCanvas.options.width = width
			frameCanvas:SetWidth(width)
		end

		local width = frameCanvas.options.width
		local height = frameCanvas.options.height

		frameCanvas:SetSize(frameCanvas.options.width, height)

	--scroll frame
		local lineHeight = frameCanvas.options.line_height
		--calc the size of the space occupied by the add button, header etc
		local lineAmount = floor((height - 60) / lineHeight)

		-- -12 is padding: 5 on top, 7 bottom, 2 header scrollbar blank space | -24 to leave space to the add button
		local scrollBox = detailsFramework:CreateScrollBox(frameCanvas, "$parentScrollbox", frameCanvas.scrollRefresh, data, width-4, height - header:GetHeight() - 12 - 24, lineAmount, lineHeight)
		scrollBox:SetPoint("topleft", header, "bottomleft", 0, -2)
		scrollBox:SetPoint("topright", header, "bottomright", 0, -2) -- -20 for the scrollbar
		detailsFramework:ReskinSlider(scrollBox)
		scrollBox.lineHeight = lineHeight
		scrollBox.lineAmount = lineAmount
		frameCanvas.scrollBox = scrollBox

		for i = 1, lineAmount do
			scrollBox:CreateLine(frameCanvas.createScrollLine)
		end

		scrollBox:Refresh()

	--add line button
		local addLineButton = detailsFramework:CreateButton(frameCanvas, detailsFramework.ListboxFunctions.addEntry, 80, 20, "Add", nil, nil, nil, nil, nil, nil, detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), detailsFramework:GetTemplate("font", "ORANGE_FONT_TEMPLATE"))
		addLineButton:SetPoint("topleft", scrollBox, "bottomleft", 0, -4)

	return frameCanvas
end

--[=[ -- test case

    local pframe = ListBoxTest or CreateFrame("frame", "ListBoxTest", UIParent)
    pframe:SetSize(900, 700)
    pframe:SetPoint("left")

    local data = {{254154, "spell name 1", 45}, {299154, "spell name 2", 05}, {354154, "spell name 3", 99}}
    local headerTable = {
        {text = "spell id", width = 120},
        {text = "spell name", width = 180},
        {text = "number", width = 90},
    }

    local listbox = DetailsFramework:CreateListBox(pframe, "$parentlistbox", data, nil, headerTable, nil)
    listbox:SetPoint("topleft", pframe, "topleft", 10, -10)

--]=]

