-- Library of common/shared methods
local Grid2 = Grid2
local Grid2Options = Grid2Options

local L = Grid2Options.L

-- Default values for new or morphed indicators
Grid2Options.indicatorDefaultValues = {
	icon = {size = 16, fontSize = 8},
	square = {size = 5},
	shape = {size = 5},
	text = {duration = true, stack = false, textlength = 12, fontSize = 8, font = "Friz Quadrata TT"}
}

-- Grid2Options:MakeIndicatorCurrentStatusOptions()
-- Grid2Options:MakeIndicatorStatusOptions()
-- Grid2Options:MakeStatusIndicatorOptions()
do
	local function GetIndexOfValue(map, status)
		for i, s in ipairs(map) do
			if s == status then
				return i
			end
		end
	end

	local function GetIndicatorNewPriority(indicator)
		local priority = 50
		local map = Grid2:DbGetValue("statusMap", indicator.name)
		if map then
			for _, p in pairs(map) do
				p = tonumber(p)
				if p and p >= priority then
					priority = p + 1
				end
			end
		end
		return priority
	end

	local function RegisterIndicatorStatus(indicator, status, value)
		if value then
			local priority = GetIndicatorNewPriority(indicator)
			Grid2:DbSetMap(indicator.name, status.name, priority)
			indicator:RegisterStatus(status, priority)
			status:Refresh()
		else
			Grid2:DbSetMap(indicator.name, status.name, nil)
			indicator:UnregisterStatus(status)
		end
		Grid2Options:RefreshIndicator(indicator, "Layout")
	end

	local function RefreshIndicatorCurrentStatusOptions(info)
		wipe(info.arg.options)
		Grid2Options:MakeIndicatorCurrentStatusOptions(info.arg.indicator, info.arg.options)
	end

	local function SetIndicatorStatus(info, statusKey, value)
		for key, status in Grid2:IterateStatuses() do
			if key == statusKey then
				RegisterIndicatorStatus(info.arg.indicator, status, value)
				RefreshIndicatorCurrentStatusOptions(info)
				return
			end
		end
	end

	local function SetIndicatorStatusCurrent(info, value)
		SetIndicatorStatus(info, info[#info], value)
	end

	local function SetStatusPriority(info, map, indicator, status, priority, index)
		Grid2:DbSetMap(indicator.name, status.name, priority)
		indicator:SetStatusPriority(status, priority)
		map[index], map[status] = status, priority
		local key, opt = status.name, info.arg.options
		opt[key].order = 500 - priority
		opt[key .. "U"].order = 500.1 - priority
		opt[key .. "D"].order = 500.2 - priority
		opt[key .. "S"].order = 500.3 - priority
	end

	local function StatusSwapPriorities(info, map, indicator, index1, index2)
		local status1 = map[index1]
		local status2 = map[index2]
		local priority1 = map[status1]
		local priority2 = map[status2]
		SetStatusPriority(info, map, indicator, status1, priority2, index2)
		SetStatusPriority(info, map, indicator, status2, priority1, index1)
	end

	local function StatusShiftUp(info, map, indicator, lowerStatus)
		local index = GetIndexOfValue(map, lowerStatus)
		if index then
			local newIndex = index > 1 and index - 1 or #map
			StatusSwapPriorities(info, map, indicator, index, newIndex)
			Grid2Options:RefreshIndicator(indicator, "Layout")
		end
	end

	local function StatusShiftDown(info, map, indicator, higherStatus)
		local index = GetIndexOfValue(map, higherStatus)
		if index then
			local newIndex = index < #map and index + 1 or 1
			StatusSwapPriorities(info, map, indicator, index, newIndex)
			Grid2Options:RefreshIndicator(indicator, "Layout")
		end
	end

	local function LoadStatusMap(indicator)
		local map = {}
		local dbx = Grid2:DbGetValue("statusMap", indicator.name)
		if dbx then
			for statusKey, priority in pairs(dbx) do
				local status = Grid2:GetStatusByName(statusKey)
				if status then
					map[#map + 1] = status
					map[status] = priority
				end
			end
		end
		table.sort(
			map,
			function(a, b)
				return map[a] > map[b]
			end
		)
		return map
	end

	-- Grid2Options:MakeIndicatorCurrentStatusOptions(indicator, options)
	function Grid2Options:MakeIndicatorCurrentStatusOptions(indicator, options)
		if indicator.statuses then
			local map = LoadStatusMap(indicator)
			local hide = #map <= 1 or nil
			local arg = {indicator = indicator, options = options}
			for _, status in ipairs(map) do
				local priority = map[status]
				options[status.name] = {
					type = "toggle",
					order = 500 - map[status],
					width = "double",
					name = Grid2Options.LocalizeStatus(status),
					desc = L["Select statuses to display with the indicator"],
					get = function()
						return true
					end,
					set = SetIndicatorStatusCurrent,
					arg = arg
				}
				options[status.name .. "U"] = {
					type = "execute",
					order = 500.1 - map[status],
					width = "half",
					image = "Interface\\Addons\\Grid2Options\\media\\arrow-up",
					imageWidth = 16,
					imageHeight = 14,
					name = "",
					desc = L["Move the status higher in priority"],
					func = function(info)
						StatusShiftUp(info, map, indicator, status)
					end,
					arg = arg,
					hidden = hide
				}
				options[status.name .. "D"] = {
					type = "execute",
					order = 500.2 - map[status],
					width = "half",
					image = "Interface\\Addons\\Grid2Options\\media\\arrow-down",
					imageWidth = 16,
					imageHeight = 14,
					name = "",
					desc = L["Move the status lower in priority"],
					func = function(info)
						StatusShiftDown(info, map, indicator, status)
					end,
					arg = arg,
					hidden = hide
				}
				options[status.name .. "S"] = {
					type = "description",
					name = "",
					order = 500.3 - map[status],
					hidden = hide
				}
			end
		end
	end

	-- Grid2Options:MakeIndicatorStatusOptions()
	function Grid2Options:MakeIndicatorStatusOptions(indicator, options)
		local curOptions = {}
		self:MakeIndicatorCurrentStatusOptions(indicator, curOptions)
		options.statusesCurrent = {
			type = "group",
			order = 100,
			inline = true,
			name = L["Current Statuses"],
			desc = L["Current statuses in order of priority"],
			args = curOptions
		}
		options.statusesAvailable = {
			type = "multiselect",
			order = 200,
			name = L["Available Statuses"],
			desc = L["Available statuses you may add"],
			values = function()
				return self:GetAvailableStatusValues(indicator)
			end,
			get = false,
			set = SetIndicatorStatus,
			arg = {indicator = indicator, options = curOptions}
		}
	end

	-- Grid2Options:MakeStatusIndicatorOptions()
	function Grid2Options:MakeStatusIndicatorsOptions(status, options)
		options.indicators = {
			type = "multiselect",
			order = 10,
			name = L["Assigned indicators"],
			values = function()
				return self:GetAvailableIndicatorValues(status)
			end,
			get = function(info, key)
				local dbx = Grid2:DbGetValue("statusMap", key)
				return dbx and dbx[status.name] ~= nil
			end,
			set = function(info, key, value)
				local indicator = Grid2.indicators[key]
				if indicator.dbx.type ~= "multibar" then
					RegisterIndicatorStatus(indicator, status, value)
					self:RefreshIndicatorOptions(indicator)
				end
			end,
			confirm = function(info, key)
				return Grid2.indicators[key].dbx.type == "multibar" and L["This indicator cannot be changed from here: go to indicators section to assign/unassign statuses to this indicator."]
			end,
			disabled = function() return status:IsSuspended() end
		}
		return options
	end
end

-- Grid2Options:MakeIndicatorDeleteOptions()
do
	local function DeleteIndicator(info)
		local indicator = info.arg
		Grid2Frame:WithAllFrames(indicator, "Disable")
		Grid2:DbSetIndicator(indicator.name, nil)
		if indicator.dbx.sideKick then
			Grid2:DbSetIndicator(indicator.dbx.sideKick.name, nil)
		end
		Grid2:UnregisterIndicator(indicator)
		Grid2Frame:UpdateIndicators()
		Grid2Options:DeleteIndicatorOptions(indicator)
	end
	local function Disabled(info)
		local indicator = info.arg
		return #indicator.statuses > 0 or (indicator.sideKick and #indicator.sideKick.statuses > 0)
	end
	function Grid2Options:MakeIndicatorDeleteOptions(indicator, options)
		self:MakeHeaderOptions(options, "Delete")
		options.delete = {
			type = "execute",
			order = 255,
			width = "half",
			name = L["Delete"],
			desc = L["Delete this element"],
			confirm = function() return L["Are you sure you want to delete this indicator?"] end,
			func = DeleteIndicator,
			arg = indicator,
			disabled = Disabled
		}
		options.deletemsg = {
			type = "description",
			name = L["There are status linked to this indicator."],
			fontSize = "small",
			order = 265,
			width = "double",
			hidden = function() return next(indicator.statuses) == nil end
		}
	end
end

-- Grid2Options:MakeIndicatorSizeOptions()
function Grid2Options:MakeIndicatorSizeOptions(indicator, options, optionParams)
	options.size = {
		type = "range",
		order = 10,
		name = L["Size"],
		desc = L["Adjust the size of the indicator."],
		min = 5,
		max = 50,
		step = 1,
		get = function()
			return indicator.dbx.size
		end,
		set = function(_, v)
			indicator.dbx.size = v
			self:RefreshIndicator(indicator, "Layout")
		end
	}
end

-- Grid2Options:MakeIndicatorBorderSizeOptions()
function Grid2Options:MakeIndicatorBorderSizeOptions(indicator, options, optionParams)
	options.borderSize = {
		type = "range",
		order = 20,
		name = L["Border Size"],
		desc = L["Adjust the border size of the indicator."],
		min = 0,
		max = 20,
		step = 1,
		get = function()
			return indicator.dbx.borderSize or 0
		end,
		set = function(_, v)
			if v == 0 then
				v = nil
			end
			indicator.dbx.borderSize = v
			self:RefreshIndicator(indicator, "Layout", "Update")
		end
	}
end

-- Grid2Options:MakeIndicatorTextureOptions()
function Grid2Options:MakeIndicatorTextureOptions(indicator, options, optionParams)
	options.texture = {
		type = "select",
		dialogControl = "LSM30_Statusbar",
		order = 11,
		name = L["Frame Texture"],
		desc = L["Adjust the texture of the indicator."],
		get = function(info)
			return indicator.dbx.texture or "Grid2 Flat"
		end,
		set = function(info, v)
			indicator.dbx.texture = v
			self:RefreshIndicator(indicator, "Layout")
		end,
		values = AceGUIWidgetLSMlists.statusbar
	}
end

-- Grid2Options:MakeIndicatorBorderOptions()
function Grid2Options:MakeIndicatorBorderOptions(indicator, options, optionParams)
	optionParams = optionParams or {}
	optionParams.color1 = L["Border Color"]
	optionParams.colorDesc1 = L["Adjust border color and alpha."]
	self:MakeHeaderOptions(options, "Border")
	self:MakeIndicatorColorOptions(indicator, options, optionParams)
	self:MakeIndicatorBorderSizeOptions(indicator, options, optionParams)
end

-- Grid2Options:MakeIndicatorColorOptions()
do
	local function GetIndicatorColor(info)
		local indicator = info.arg.indicator
		local colorKey = "color" .. info.arg.colorIndex
		local c = indicator.dbx[colorKey]
		if c then
			return c.r, c.g, c.b, c.a
		end
		return 0, 0, 0, 0
	end
	local function SetIndicatorColor(info, r, g, b, a)
		local colorKey = "color" .. info.arg.colorIndex
		local indicator = info.arg.indicator
		local dbx = indicator.dbx
		local c = dbx[colorKey]
		if not c then
			c = {}
			dbx[colorKey] = c
		end
		c.r, c.g, c.b, c.a = r, g, b, a
		if indicator.UpdateDB then
			indicator:UpdateDB()
		end
		Grid2Frame:UpdateIndicators()
	end
	function Grid2Options:MakeIndicatorColorOptions(indicator, options, optionParams)
		local colorCount = indicator.dbx.colorCount or 1
		local name = L["Color"]
		local desc = L["Color for %s."]:format(indicator.name)
		for i = 1, colorCount, 1 do
			local colorKey = "color" .. i
			if (optionParams and optionParams[colorKey]) then
				name = optionParams[colorKey]
			elseif (colorCount > 1) then
				name = L["Color %d"]:format(i)
			end
			local colorDescKey = "colorDesc" .. i
			if (optionParams and optionParams[colorDescKey]) then
				desc = optionParams[colorDescKey]
			elseif (colorCount > 1) then
				desc = name
			end
			options[colorKey] = {
				type = "color",
				order = (20 + i),
				name = name,
				desc = desc,
				get = GetIndicatorColor,
				set = SetIndicatorColor,
				hasAlpha = true,
				arg = {indicator = indicator, colorIndex = i}
			}
		end
	end
end

-- Grid2Options:MakeIndicatorTypeOptions()
do
	local typeMorphValue = {}
	local typeMorphValues = {icon = L["icon"], square = L["square"], text = L["text"]}

	local function RegisterIndicatorStatusesFromDatabase(indicator)
		if indicator then
			local map = Grid2:DbGetValue("statusMap", indicator.name)
			if map then
				for statusKey, priority in pairs(map) do
					local status = Grid2.statuses[statusKey]
					if (status and tonumber(priority)) then
						indicator:RegisterStatus(status, priority)
					end
				end
			end
		end
	end

	local function GetIndicatorTypeValues(info)
		local typeKey = info.arg.dbx.type
		if not typeMorphValues[typeKey] then
			wipe(typeMorphValue)
			typeMorphValue[typeKey] = L[typeKey]
			return typeMorphValue
		end
		return typeMorphValues
	end

	local function GetIndicatorTypeDisabled(info)
		return not typeMorphValues[info.arg.dbx.type]
	end

	local function GetIndicatorType(info)
		return info.arg.dbx.type
	end

	local function SetIndicatorType(info, value)
		local indicator = info.arg
		local baseKey = indicator.name
		local dbx = indicator.dbx
		local colorKey = baseKey .. "-color"
		local oldType = dbx.type

		if dbx.type == value then
			return
		end

		-- Set new fields width defaults values
		dbx.type = value
		for k, v in pairs(Grid2Options.indicatorDefaultValues[value]) do
			if (not dbx[k]) then
				indicator.dbx[k] = v
				dbx[k] = v
			end
		end
		-- Remove old indicator
		Grid2:UnregisterIndicator(indicator)
		-- Create new indicator
		local setupFunc = Grid2.setupFunc[dbx.type]
		local newIndicator = setupFunc(baseKey, dbx)
		-- Remove incompatible statuses from database
		local map = Grid2:DbGetValue("statusMap", baseKey)
		if map then
			for statusKey, priority in pairs(map) do
				local status = Grid2.statuses[statusKey]
				if (not status) or (not Grid2Options:IsCompatiblePair(newIndicator, status)) then
					map[statusKey] = nil
				end
			end
		end
		-- Register indicator statuses from database
		RegisterIndicatorStatusesFromDatabase(newIndicator)
		RegisterIndicatorStatusesFromDatabase(newIndicator.sideKick)
		-- Recreate indicators in frame units
		Grid2Frame:WithAllFrames(function(f)
			newIndicator:Create(f)
			newIndicator:Layout(f)
		end)
		-- Delete or Create associated text-color indicator in database
		if oldType == "text" then
			Grid2:DbSetIndicator(colorKey, nil)
		elseif value == "text" then
			Grid2:DbSetIndicator(colorKey, {type = "text-color"})
		end
		-- Update unit frames
		Grid2Frame:UpdateIndicators()
		-- Create new indicator options
		Grid2Options:MakeIndicatorOptions(newIndicator)
	end

	function Grid2Options:MakeIndicatorTypeOptions(indicator, options, optionParams)
		options.indicatorType = {
			type = "select",
			order = 1.91,
			name = L["Type of indicator"],
			desc = L["Change the indicator type"],
			values = GetIndicatorTypeValues,
			get = GetIndicatorType,
			set = SetIndicatorType,
			confirm = true,
			confirmText = L["Are you sure do you want to convert the indicator to the new selected type?"],
			arg = indicator
		}
	end
end

-- Grid2Options:MakeIndicatorLevelOptions()
do
	local levelValues = {1, 2, 3, 4, 5, 6, 7, 8, 9}
	function Grid2Options:MakeIndicatorLevelOptions(indicator, options)
		options.frameLevel = {
			type = "select",
			order = 6,
			name = L["Frame Level"],
			desc = L["Bars with higher numbers always show up on top of lower numbers."],
			get = function()
				return indicator.dbx.level or 1
			end,
			set = function(_, v)
				indicator.dbx.level = v
				self:RefreshIndicator(indicator, "Layout")
			end,
			values = levelValues
		}
	end
end

-- Grid2Options:MakeIndicatorTypeLevelOptions()
function Grid2Options:MakeIndicatorTypeLevelOptions(indicator, options)
	self:MakeHeaderOptions(options, "General")
	self:MakeIndicatorTypeOptions(indicator, options)
	self:MakeIndicatorLevelOptions(indicator, options)
end

-- Grid2Options:MakeIndicatorLocationOptions()
function Grid2Options:MakeIndicatorLocationOptions(indicator, options)
	local location = indicator.dbx.location
	self:MakeHeaderOptions(options, "Location")
	options.relPoint = {
		type = "select",
		order = 4,
		name = L["Location"],
		desc = L["Align my align point relative to"],
		values = self.pointValueList,
		get = function()
			return self.pointMap[location.relPoint]
		end,
		set = function(_, v)
			location.relPoint = self.pointMap[v]
			location.point = location.relPoint
			self:RefreshIndicator(indicator, "Layout")
		end
	}
	options.point = {
		type = "select",
		order = 5,
		name = L["Align Point"],
		desc = L["Align this point on the indicator"],
		values = self.pointValueList,
		get = function()
			return self.pointMap[location.point]
		end,
		set = function(_, v)
			location.point = self.pointMap[v]
			self:RefreshIndicator(indicator, "Layout")
		end
	}
	options.x = {
		type = "range",
		order = 7,
		name = L["X Offset"],
		desc = L["X - Horizontal Offset"],
		min = -50,
		max = 50,
		step = 1,
		bigStep = 1,
		get = function()
			return location.x
		end,
		set = function(_, v)
			location.x = v
			self:RefreshIndicator(indicator, "Layout")
		end
	}
	options.y = {
		type = "range",
		order = 8,
		name = L["Y Offset"],
		desc = L["Y - Vertical Offset"],
		min = -50,
		max = 50,
		step = 1,
		bigStep = 1,
		get = function()
			return location.y
		end,
		set = function(_, v)
			location.y = v
			self:RefreshIndicator(indicator, "Layout")
		end
	}
	self:MakeIndicatorLevelOptions(indicator, options)
end

-- Grid2Options:MakeIndicatorAnimationOptions()
function Grid2Options:MakeIndicatorAnimationOptions(indicator, options)
	self:MakeHeaderOptions(options, "Animation")
	options.animEnabled = {
		type = "toggle",
		order = 155,
		name = L["Enable animation"],
		desc = L["Turn on/off zoom animation of icons."],
		tristate = false,
		get = function()
			return indicator.dbx.animEnabled
		end,
		set = function(_, v)
			indicator.dbx.animEnabled = v or nil
			if not v then
				indicator.dbx.animScale = nil
				indicator.dbx.animDuration = nil
				indicator.dbx.animOrigin = nil
			end
			indicator:UpdateDB()
		end
	}
	options.animOnEnabled = {
		type = "toggle",
		order = 157,
		name = L["Only on Activation"],
		desc = L["Start the animation only when the indicator is activated, not on updates."],
		tristate = false,
		get = function()
			return indicator.dbx.animOnEnabled
		end,
		set = function(_, v)
			indicator.dbx.animOnEnabled = v or nil
			indicator:UpdateDB()
		end,
		hidden = function()
			return not indicator.dbx.animEnabled
		end
	}
	options.animDuration = {
		type = "range",
		order = 160,
		name = L["Duration"],
		desc = L["Sets the duration in seconds."],
		min = 0.1,
		max = 2,
		step = 0.1,
		get = function()
			return indicator.dbx.animDuration or 0.7
		end,
		set = function(_, v)
			indicator.dbx.animDuration = v
			Grid2Frame:WithAllFrames(function(f)
				local anim = indicator:GetBlinkFrame(f).scaleAnim
				if anim then
					anim.grow:SetDuration(v / 2)
					anim.shrink:SetDuration(v / 2)
				end
			end)
		end,
		hidden = function()
			return not indicator.dbx.animEnabled
		end
	}
	options.animScale = {
		type = "range",
		order = 164,
		name = L["Scale"],
		desc = L["Sets the zoom factor."],
		min = 1.1,
		max = 3,
		step = 0.1,
		get = function()
			return indicator.dbx.animScale or 1.5
		end,
		set = function(_, v)
			indicator.dbx.animScale = v
			Grid2Frame:WithAllFrames(function(f)
				local anim = indicator:GetBlinkFrame(f).scaleAnim
				if anim then
					anim.grow:SetScale(v, v)
					anim.shrink:SetScale(1 / v, 1 / v)
				end
			end)
		end,
		hidden = function()
			return not indicator.dbx.animEnabled
		end
	}
	options.animOrigin = {
		type = "select",
		order = 165,
		name = L["Origin"],
		desc = L["Zoom origin point"],
		values = self.pointValueList,
		get = function()
			return self.pointMap[indicator.dbx.animOrigin or "CENTER"]
		end,
		set = function(_, v)
			local point = self.pointMap[v]
			indicator.dbx.animOrigin = point ~= "CENTER" and point or nil
			Grid2Frame:WithAllFrames(function(f)
				local anim = indicator:GetBlinkFrame(f).scaleAnim
				if anim then
					anim.grow:SetOrigin(point, 0, 0)
					anim.shrink:SetOrigin(point, 0, 0)
				end
			end)
		end,
		hidden = function()
			return not indicator.dbx.animEnabled
		end
	}
end