local Grid2 = Grid2
local Grid2Frame = Grid2Frame

local min, max = math.min, math.max
local pairs, ipairs = pairs, ipairs
local CreateFrame = CreateFrame

local AlignPoints = Grid2.AlignPoints
local SetSizeMethods = {HORIZONTAL = "SetWidth", VERTICAL = "SetHeight"}
local GetSizeMethods = {HORIZONTAL = "GetWidth", VERTICAL = "GetHeight"}

local StatusBarPrototype = Grid2.StatusBarPrototype

local function Bar_CreateHH(self, parent)
	local bar = StatusBarPrototype(nil, parent)
	bar.myIndicator = self
	bar.myValues = {}
	bar:SetStatusBarColor(0, 0, 0, 0)
	bar:SetMinMaxValues(0, 1)
	bar:SetValue(0)
	parent[self.name] = bar
end

-- Warning Do not put bar:SetValue() methods inside this function because for some reason the bar is not updated&painted
-- on current frame (the bar update is delayed to the next frame), but extra bar textures are updated on current frame.
-- generating a graphic glitch because main bar & extra bars are displayed out of sync (in diferente frames)
-- In this case we are talking about screen frames like in frames per second.
local function Bar_OnFrameUpdate(bar)
	local self = bar.myIndicator
	local direction = self.direction
	local horizontal = self.horizontal
	local points = self.alignPoints
	local barSize = bar[self.GetSizeMethod](bar)
	local myTextures = bar.myTextures
	local myValues = bar.myValues
	local valueTo = myValues[1] or 0
	local valueMax = valueTo
	local maxIndex = 0
	if self.reverse then
		valueMax, valueTo = 0, -valueTo
	end
	for i = 2, bar.myMaxIndex do
		local texture = myTextures[i]
		local value = myValues[i] or 0
		if texture and value > 0 then
			local size, offset
			maxIndex = i
			if texture.myReverse then
				size = min(value, valueTo)
				offset = valueTo - size
				valueTo = valueTo - value
			elseif texture.myNoOverlap then
				size = min(value, 1 - valueMax)
				offset = valueMax
				valueTo = valueMax + value
				valueMax = valueTo
			else
				offset = max(valueTo, 0)
				valueTo = valueTo + value
				size = min(valueTo, 1) - offset
				valueMax = max(valueMax, valueTo)
			end
			if size > 0 then
				if horizontal then
					texture:SetPoint(points[1], bar, points[1], direction * offset * barSize, 0)
				else
					texture:SetPoint(points[1], bar, points[1], 0, direction * offset * barSize)
				end
				texture:mySetSize(size * barSize)
				texture:Show()
			else
				texture:Hide()
			end
		elseif texture then
			texture:Hide()
		end
	end
	bar.myMaxIndex = maxIndex
	if self.backAnchor then
		local texture = myTextures[#myTextures]
		local size = (self.backAnchor == 1) and myValues[1] or valueMax
		if size < 1 then
			texture:SetPoint(points[2], bar, points[2], 0, 0)
			texture:mySetSize((1 - size) * barSize)
			texture:Show()
		else
			texture:Hide()
		end
	end
end

-- Optimization: Updating modified bars only on next frame repaint
local updates = {}
local EnableDelayedUpdates
local lastUpdate = 0
EnableDelayedUpdates = function()
	CreateFrame("Frame", nil, Grid2LayoutFrame):SetScript("OnUpdate", function(self, elapsed)
		lastUpdate = lastUpdate + elapsed
		if lastUpdate > 0.005 then
			for bar in pairs(updates) do
				Bar_OnFrameUpdate(bar)
			end
			lastUpdate = 0
			wipe(updates)
		end
	end)
	EnableDelayedUpdates = Grid2.Dummy
end

-- Warning: This is an overrided indicator:Update() NOT the standard indicator:OnUpdate()
-- We are calling bar:SetValue()/bar:SetMainBarValue() here instead of inside Bar_OnFrameUpdate() because the
-- StatusBar texture is not updated inmmediatly like the additional bars textures, generating a graphic glitch.
local function Bar_Update(self, parent, unit, status)
	if unit then
		local bar = parent[self.name]
		local values = bar.myValues
		if status then
			local index = self.priorities[status]
			local value = status:GetPercent(unit) or 0
			values[index] = value
			if value > 0 and index > bar.myMaxIndex then
				bar.myMaxIndex = index -- Optimization to avoid updating bars with zero value
			end
			if index == 1 then
				bar:SetMainBarValue(value)
			end
			if self.backAnchor or bar.myMaxIndex > 1 then
				updates[bar] = true
			end
		else
			for i, st in ipairs(self.statuses) do
				values[i] = st:GetPercent(unit) or 0
			end
			bar.myMaxIndex = #self.statuses
			bar:SetMainBarValue(values[1] or 0)
			updates[bar] = true
		end
	end
end

local function Bar_Layout(self, parent)
	local bar = parent[self.name]
	-- main bar
	local width = self.width or parent.container:GetWidth()
	local height = self.height or parent.container:GetHeight()
	bar:SetParent(parent)
	bar:SetOrientation(self.orientation)
	bar:SetReverseFill(self.reverseFill)
	bar:SetFrameLevel(parent:GetFrameLevel() + self.frameLevel)
	bar:SetStatusBarTexture(self.texture)
	local barTexture = bar:GetStatusBarTexture()
	barTexture:SetDrawLayer("ARTWORK", -7)
	local color = self.foreColor
	if color then
		bar:SetStatusBarColor(color.r, color.g, color.b, self.opacity)
	end
	bar:SetSize(width, height)
	bar:ClearAllPoints()
	bar:SetPoint(self.anchor, parent.container, self.anchorRel, self.offsetx, self.offsety)
	bar:SetValue(0)
	bar.SetMainBarValue = self.reverse and Grid2.Dummy or bar.SetValue
	-- extra bars
	local ctextures
	local barCount = #self.bars
	local textures = bar.myTextures or {barTexture}
	for i = 1, barCount do
		local setup = self.bars[i]
		local f = CreateFrame("Frame", nil, bar)
		f:SetFrameLevel(bar:GetFrameLevel() + i)
		f:SetAllPoints()
		local texture = textures[i + 1] or f:CreateTexture()
		texture:Hide()
		texture:ClearAllPoints()
		texture.mySetSize = texture[self.SetSizeMethod]
		texture.myReverse = setup.reverse
		texture.myNoOverlap = setup.noOverlap
		texture.myOpacity = setup.opacity
		texture:SetTexture(setup.texture)
		texture:SetDrawLayer("ARTWORK", setup.sublayer)
		local c = setup.color
		if c then
			texture:SetVertexColor(c.r, c.g, c.b, setup.opacity)
		else
			ctextures = ctextures or {}
			ctextures[#ctextures + 1] = texture
		end
		if setup.background then
			texture:SetAllPoints()
			texture:Show()
		else
			texture:SetSize(width, height)
		end
		textures[i + 1] = texture
	end
	for i = barCount + 2, #textures do
		textures[i]:Hide()
	end
	bar.myTextures = textures
	bar.myCTextures = ctextures
	bar.myMaxIndex = #self.statuses
	bar:Show()
end

local function Bar_GetBlinkFrame(self, parent)
	return parent[self.name]
end

local function Bar_SetOrientation(self, orientation)
	self.dbx.orientation = orientation
	self.orientation = orientation or Grid2Frame.db.profile.orientation
end

local function Bar_Disable(self, parent)
	local bar = parent[self.name]
	local textures = bar.myTextures
	for i = 2, #textures do
		textures[i]:Hide()
	end
	bar:Hide()
	bar:SetParent(nil)
	bar:ClearAllPoints()
end

local function Bar_UpdateDB(self)
	local dbx = self.dbx
	local l = dbx.location
	local theme = Grid2Frame.db.profile
	local orientation = dbx.orientation or Grid2Frame.db.profile.orientation
	local backColor = dbx.backColor
	local texColor = dbx.textureColor and dbx.textureColor.r and dbx.textureColor
	self.foreColor = dbx.invertColor and backColor or texColor
	self.orientation = orientation
	self.SetSizeMethod = SetSizeMethods[orientation]
	self.GetSizeMethod = GetSizeMethods[orientation]
	self.alignPoints = AlignPoints[orientation .. (dbx.reverseFill and "_INVERSE" or "")]
	self.frameLevel = dbx.level or 1
	self.anchor = l.point
	self.anchorRel = l.relPoint
	self.offsetx = l.x
	self.offsety = l.y
	self.width = dbx.width
	self.height = dbx.height
	self.direction = dbx.reverseFill and -1 or 1
	self.horizontal = (orientation == "HORIZONTAL")
	self.reverseFill = dbx.reverseFill
	self.backAnchor = dbx.backAnchor
	self.reverse = dbx.reverseMainBar
	self.opacity = dbx.textureColor and dbx.textureColor.a
	self.texture = Grid2:MediaFetch("statusbar", dbx.texture or theme.barTexture, "Gradient")
	self.bars = {}
	for i, setup in ipairs(dbx) do
		self.bars[i] = {
			reverse = setup.reverse,
			noOverlap = setup.noOverlap,
			opacity = setup.color.a,
			color = setup.color.r and setup.color or self.foreColor,
			texture = setup.texture and Grid2:MediaFetch("statusbar", setup.texture) or self.texture,
			sublayer = i
		}
	end
	if backColor then
		self.bars[#self.bars + 1] = {
			texture = Grid2:MediaFetch("statusbar", dbx.backTexture or theme.barTexture, "Gradient") or self.texture,
			color = dbx.invertColor and texColor or backColor,
			opacity = backColor.a,
			background = not self.backAnchor,
			sublayer = -1
		}
	end
end

-- Bar Color indicator

local function BarColor_OnUpdate(self, parent, unit, status)
	if status then
		self:SetBarColor(parent, status:GetColor(unit))
	else
		self:SetBarColor(parent, 0, 0, 0, 0)
	end
end

local function BarColor_SetBarColor(self, parent, r, g, b, a)
	local bar = parent[self.parentName]
	bar:SetStatusBarColor(r, g, b, min(self.opacity or 0, a or 1))
	local textures = bar.myCTextures
	if textures then
		for i = #textures, 1, -1 do
			local tex = textures[i]
			tex:SetVertexColor(r, g, b, min(tex.myOpacity, a))
		end
	end
end

local function BarColor_SetBarColorInverted(self, parent, r, g, b, a)
	local textures = parent[self.parentName].myTextures
	textures[#textures]:SetVertexColor(r, g, b, a)
end

local function BarColor_UpdateDB(self)
	local dbx = self.dbx
	self.SetBarColor = dbx.invertColor and BarColor_SetBarColorInverted or BarColor_SetBarColor
	self.OnUpdate = dbx.textureColor and dbx.textureColor.r and Grid2.Dummy or BarColor_OnUpdate
	self.opacity = dbx.textureColor and dbx.textureColor.a
end

local function Create(indicatorKey, dbx)
	local Bar = Grid2.indicators[indicatorKey] or Grid2.indicatorPrototype:new(indicatorKey)
	Bar.dbx = dbx
	-- Hack to caculate status index fast: statuses[priorities[status]] == status
	Bar.sortStatuses = function(a, b)
		return Bar.priorities[a] < Bar.priorities[b]
	end
	Bar.Create = Bar_CreateHH
	Bar.GetBlinkFrame = Bar_GetBlinkFrame
	Bar.SetOrientation = Bar_SetOrientation
	Bar.Disable = Bar_Disable
	Bar.Layout = Bar_Layout
	Bar.Update = Bar_Update
	Bar.UpdateDB = Bar_UpdateDB
	Bar_UpdateDB(Bar)
	Grid2:RegisterIndicator(Bar, {"percent"})
	EnableDelayedUpdates()

	local colorKey = indicatorKey .. "-color"
	local BarColor = Grid2.indicators[colorKey] or Grid2.indicatorPrototype:new(colorKey)
	BarColor.dbx = dbx
	BarColor.parentName = indicatorKey
	BarColor.Create = Grid2.Dummy
	BarColor.Layout = Grid2.Dummy
	BarColor.UpdateDB = BarColor_UpdateDB
	BarColor_UpdateDB(BarColor)
	Grid2:RegisterIndicator(BarColor, {"color"})
	Bar.sideKick = BarColor

	return Bar, BarColor
end

Grid2.setupFunc["multibar"] = Create
Grid2.setupFunc["multibar-color"] = Grid2.Dummy