local settings_prefix = "/" .. THEME:GetRealThemeDisplayName() .. "_settings/"
local ETTERNA_PURPLE = "#614080" -- originally "Main Highlight" color
local defaultConfig = {
	main = {
		PrimaryBackground = "#111111",
		SecondaryBackground = "#111111", -- darker version of PrimaryBackground
		PrimaryText = "#ffffff",
		SecondaryText = "#ffffff", -- probably shouldnt be far from PrimaryText
		IconColor = "#ffffff",
		Visualizer = "#ffffff",
		SeparationDivider = "#ffffff", -- also some accents like slider markers and text cursors
	},
	leaderboard = {
		Background = "#111111",
		Border = "#000111",
		Text = "#9654fd",
	},
	musicWheel = {
		HeaderBackground = "#111111",
		FolderBackground = "#111111",
		SongBackground = "#111111",
		ItemDivider = "#999999",
		GraphLine = "#ffffff",
		Favorite = "#ffff00",
		Permamirror = "#1111bb",
		HighlightColor = "#ffffff",
	},
	generalBox = {
		SliderBackground = "#000000",
		RequiredTag = "#8888ff",
		AssignedTag = "#ff8888",
		FilteredTag = "#88ff88",
		GoalBackground = "#ffffff",
		GoalAchieved = "#9654fd",
		GoalDefault = "#111111",
	},
	chartPreview = {
		Background = "#000000",
		GraphBackground = "#ffffff",
		GraphNPSLine = "#666699",
		GraphNPSText = "#666699",
		GraphSeekBar = "#666699",
		GraphProgress = "#00ff00",
		GraphLowestDensityBar = "#ffffff",
		GraphHighestDensityBar = "#000000",
	},
	downloader = {
		ProgressBarFill = "#ffffff",
		ProgressBarBackground = "#ffffff",
		InstalledIcon = "#00ff00",
		NotInstalledIcon = "#ffffff",
		Bundle1Easiest = "#66ccff",
		Bundle2Easy = "#099948",
		Bundle3Medium = "#ddaa00",
		Bundle4Hard = "#ff6666",
		Bundle5Hardest = "#c97bff",
	},
	options = {
		Cursor = "#ffffff",
		Arrows = "#ffffff",
		KeybindButtonEdge = "#ffffff", -- the bg bg, white edges
		KeybindButtonBackground = "#111111", -- the bg
	},
	evaluation = {
		LifeGraphTint = "#ffffff",
		LifeGraphBackground = "#555555",
		ComboGraphTint = "#ffffff",
		ComboGraphBackground = "#111111",
		MaxComboText = "#999999",
		NormalComboText = "#ffffff",
	},
	assetSettings = {
		HoveredItem = "#ffffff",
		SavedItem = "#999999",	
	},
	offsetPlot = {
		Background = "#000000",
		Text = "#ffffff",
		HoverLine = "#aa2222",
		MineHit = "#ff0000",
		HoldDrop = "#ff0000",
		RollDrop = "#ff00ff",
	},
	title = {
		PrimaryText = "#ffffff",
		SecondaryText = "#ffffff",
		ProfileBackground = "#000000",
		Separator = "#ffffff",
		UnderlayBackground = "#333333",
	},
	gameplay = {
		ErrorBarCenter = ETTERNA_PURPLE,
		ErrorBarEWMABar = "#4cbb17",
		FullProgressBar = ETTERNA_PURPLE,
		FullProgressBarBG = "#666666",
		MiniProgressBar = ETTERNA_PURPLE,
		MiniProgressBarBG = "#666666",
		MiniProgressBarEnd = "#555555",
		TargetGoalAhead = "#9654fd",
		TargetGoalBehind = "#ff9999",
		LaneCover = "#333333",
		LaneCoverBPM = "#4cbb17",
		LaneCoverHeight = "#ffffff",
		NPSGraph = "#ffffff",
		NoteFieldBG = "#000000",
		PracticeBookmark = "#1080ff",
		PracticeRegion = "#c010c0",
	},
	combo = {
		MarvFullCombo = "#00aeef",
		PerfFullCombo = "#fff568",
		FullCombo = "#a4ff00",
		RegularCombo = "#ffffff",
		ComboLabel = "#00aeef",
	},
	clearType = {
		MFC = "#66ccff",
		WF = "#dddddd",
		SDP = "#cc8800",
		PFC = "#eeaa00",
		BF = "#999999",
		SDG = "#448844",
		FC = "#66cc66",
		MF = "#cc6666",
		SDCB = "#33bbff",
		Clear = "#33aaff",
		Failed = "#e61e25",
		Invalid = "#e61e25",
		NoPlay = "#666666",
		None = "#666666",
	},
	difficulty = {
		Difficulty_Beginner = "#66ccff", -- light blue
		Difficulty_Easy = "#099948", -- green
		Difficulty_Medium = "#ddaa00", -- yellow
		Difficulty_Hard = "#ff6666", -- red
		Difficulty_Challenge = "#c97bff", -- light blue
		Difficulty_Edit = "#666666", -- gray
		Beginner = "#66ccff",
		Easy = "#099948", -- green
		Medium = "#ddaa00", -- yellow
		Hard = "#ff6666", -- red
		Challenge = "#c97bff", -- Purple
		Edit = "#666666", -- gray
		Difficulty_Crazy = "#cc66ff",
		Difficulty_Freestyle = "#666666",
		Difficulty_Nightmare = "#666666",
		Crazy = "#cc66ff",
		Freestyle = "#666666",
		Nightmare = "#666666",
	},
	grades = {
		Grade_Tier01 = "#ffffff", -- AAAAA
		Grade_Tier02 = "#66ccff", -- AAAA:
		Grade_Tier03 = "#66ccff", -- AAAA.
		Grade_Tier04 = "#66ccff", -- AAAA
		Grade_Tier05 = "#eebb00", -- AAA:
		Grade_Tier06 = "#eebb00", -- AAA.
		Grade_Tier07 = "#eebb00", -- AAA
		Grade_Tier08 = "#66cc66", -- AA:
		Grade_Tier09 = "#66cc66", -- AA.
		Grade_Tier10 = "#66cc66", -- AA
		Grade_Tier11 = "#da5757", -- A:
		Grade_Tier12 = "#da5757", -- A.
		Grade_Tier13 = "#da5757", -- A
		Grade_Tier14 = "#5b78bb", -- B
		Grade_Tier15 = "#c97bff", -- C
		Grade_Tier16 = "#8c6239", -- D
		Grade_Failed = "#cdcdcd", -- F
		Grade_None = "#666666" -- no play
	},
	judgment = {
		TapNoteScore_W1 = "#99ccff",
		TapNoteScore_W2 = "#f2cb30",
		TapNoteScore_W3 = "#14cc8f",
		TapNoteScore_W4 = "#1ab2ff",
		TapNoteScore_W5 = "#ff1ab3",
		TapNoteScore_Miss = "#cc2929",
		HoldNoteScore_Held = "#f2cb30",
		HoldNoteScore_LetGo = "#cc2929",
	},
}

local presetfolder = "Save" .. settings_prefix .. "color_presets/"
local defaultpresetname = "default"
local defaultpresetpath = presetfolder .. defaultpresetname .. ".lua"

local defaultGlobalConfig = {
	currentPreset = defaultpresetname,
}

function getDefaultColor(category, element)
	if defaultConfig[category] ~= nil then
		if defaultConfig[category][element] ~= nil then
			return color(defaultConfig[category][element])
		else
			print("Element "..element.." does not exist in the default color config.")
			return color("1,1,1,1")
		end
	else
		print("Category "..category.." does not exist in the default color config.")
		return color("1,1,1,1")
	end
end

-- the global color config table
-- serious big boy coding style would dictate that i put meta tables and stuff in this 
-- 	and a bunch of stuff would be private and whatever but nah im not doing that
-- all the loaded presets will be present here at once
-- hope you dont load 5gb of presets (this would be a feat tbh. someone try this)
COLORS = {}

-- contains info about color config preset and any other color config global settings
colorConfig = create_setting("colorConfig", "colorConfig.lua", defaultGlobalConfig, -1)

local function load_conf_file(fname)
	local file = RageFileUtil.CreateRageFile()
	local ret = {}
	if file:Open(fname, 1) then
		local data = loadstring(file:Read())
		setfenv(data, {})
		local success, data_ret = pcall(data)
		if success then
			ret = data_ret
		end
		file:Close()
	end
	file:destroy()
	return ret
end

local function writePreset(name)
	local presetData = COLORS.presets[name]
	local fname = presetfolder .. name .. ".lua"
	local f = RageFileUtil.CreateRageFile()
	if not f:Open(fname, 2) then
		print("Could not open '" .. fname .. "' to write.")
		return false
	else
		local o = "return " .. lua_table_to_string(presetData)
		f:Write(o)
		f:Close()
		f:destroy()
		return true
	end
end

local function writeNamedDefaultPreset(name)
	local fname = presetfolder .. name .. ".lua"
	local f = RageFileUtil.CreateRageFile()
	if not f:Open(fname, 2) then
		print("Could not open '" .. fname .. "' to write.")
		return false
	else
		local o = "return " .. lua_table_to_string(defaultConfig)
		f:Write(o)
		f:Close()
		f:destroy()
		return true
	end
end

local function writeDefaultPreset()
	local file_handle = RageFileUtil.CreateRageFile()
	local fname = defaultpresetpath
	if not file_handle:Open(fname, 2) then
		print("Could not open '" .. fname .. "' to write.")
		return false
	else
		local output = "return " .. lua_table_to_string(defaultConfig)
		file_handle:Write(output)
		file_handle:Close()
		file_handle:destroy()
		return true
	end
end

-- works like the process of loading any config but loads it from a directory of configs instead
function COLORS.loadColorConfigPresets(self)
	print("Loading color config presets.")
	FILEMAN:FlushDirCache(presetfolder) -- the dir cache resets every 30 seconds. we need to be faster

	-- put the default in
	if not FILEMAN:DoesFileExist(defaultpresetpath) then
		writeDefaultPreset()
	end

	-- check the preset folder for more presets
	local tmp = FILEMAN:GetDirListing(presetfolder)
	local confignames = {}
	for _, name in ipairs(tmp) do
		if ActorUtil.GetFileType(presetfolder .. name) == "FileType_Lua" then
			confignames[#confignames+1] = name:gsub(".lua", "")
		end
	end

	if #confignames == 0 then
		print("No color config presets present even after writing the default one!!!")
		return
	end

	local count = 0
	-- load all the presets
	self.presets = {}
	for _, name in ipairs(confignames) do
		count = count + 1
		local pname = presetfolder .. name .. ".lua"
		local from_file = load_conf_file(pname)
		if type(from_file) == "table" then
			force_table_elements_to_match_type(from_file, defaultConfig, -1)
			self.presets[name] = from_file
		else
			self.presets[name] = DeepCopy(defaultConfig)
		end
	end
	print("Loaded "..count.." color config presets.")
end

-- return category names overall
function getColorConfigCategories()
	local o = {}
	for c, _ in pairs(defaultConfig) do
		o[#o+1] = c
	end
	table.sort(o, function(a,b) return a:lower()<b:lower() end)
	return o
end

-- return element names for a category
function getColorConfigElementsForCategory(cat)
	local o = {}
	for e, _ in pairs(defaultConfig[cat]) do
		o[#o+1] = e
	end
	table.sort(o, function(a,b) return a:lower()<b:lower() end)
	return o
end

-- return preset names
function getColorConfigPresets()
	-- we will load all presets for this action just in case
	COLORS:loadColorConfigPresets()
	local o = {}
	for p, _ in pairs(COLORS.presets) do
		o[#o+1] = p
	end
	table.sort(o, function(a,b) return a:lower()<b:lower() end)
	return o
end

-- return the current set color preset
function getColorPreset()
	if colorConfig ~= nil then
		return colorConfig:get_data().currentPreset
	else
		return nil
	end
end

function COLORS.loadColorPreset(self, preset)
	if self.presets[preset] == nil then
		self:loadColorConfigPresets()
	end
	if self.presets[preset] == nil then
		local pname = presetfolder .. preset .. ".lua"
		local from_file = load_conf_file(pname)
		if type(from_file) == "table" then
			force_table_elements_to_match_type(from_file, defaultConfig, -1)
			self.presets[preset] = from_file
		else
			self.presets[preset] = DeepCopy(defaultConfig)
		end
	end
	return true
end
function loadColorPreset(preset) return COLORS:loadColorPreset(preset) end

function changeCurrentColorPreset(preset)
	if colorConfig == nil then print("Color config does not exist???") return end
	colorConfig:get_data().currentPreset = preset
	colorConfig:set_dirty()
	colorConfig:save()

	COLORS:loadColorPreset(preset)
	MESSAGEMAN:Broadcast("ColorConfigUpdated")
end

function COLORS.saveColor(self, category, element, rawColor)
	self.presets[getColorPreset()][category][element] = rawColor
	MESSAGEMAN:Broadcast("ColorConfigUpdated")
end
function saveColor(category, element, rawColor) COLORS:saveColor(category, element, rawColor) end

function COLORS.saveColorPreset(self, preset) return writePreset(preset) end
function saveColorPreset(preset) return COLORS:saveColorPreset(preset) end
function COLORS.newColorPreset(self, name) return writeNamedDefaultPreset(name) end
function newColorPreset(name) return COLORS:newColorPreset(name) end

-- main color access function
-- uses the currently selected preset in COLORS
function COLORS.getColor(self, category, element)
	local preset = getColorPreset()
	if preset ~= nil then
		local presetconfig = self.presets[preset]
		if presetconfig ~= nil then
			local catdef = presetconfig[category]
			if catdef ~= nil then
				local result = catdef[element]
				if result ~= nil then
					return color(result)
				else
					print("The element "..element.." was not in category "..category.." in the color preset "..preset..". Loaded default in its place.")
					return getDefaultColor(category, element)
				end
			else
				print("The category "..category.." was not in the color preset "..preset..". Loaded default in its place.")
				return getDefaultColor(category, element)
			end
		else
			print("The color preset "..preset.." was not loaded. Loaded the default preset in its place.")
			return getDefaultColor(category, element)
		end
	else
		print("Current color preset is empty. Malformed colorConfig.lua? Loaded default color instead.")
		return getDefaultColor(category, element)
	end
end

function COLORS.getMainColor(self, element)
	return self:getColor("main", element)
end

function COLORS.getLeaderboardColor(self, element)
	return self:getColor("leaderboard", element)
end

function COLORS.getComboColor(self, element)
	return self:getColor("combo", element)
end

function COLORS.colorByClearType(self, type)
	return self:getColor("clearType", type)
end

function COLORS.getTitleColor(self, element)
	return self:getColor("title", element)
end
function getTitleColor(element) return COLORS:getTitleColor(element) end

function COLORS.getGameplayColor(self, element)
	return self:getColor("gameplay", element)
end
function getGameplayColor(element) return COLORS:getGameplayColor(element) end

function COLORS.getWheelColor(self, element)
	return self:getColor("musicWheel", element)
end
function getWheelColor(element) return COLORS:getWheelColor(element) end

function COLORS.getDownloaderColor(self, element)
	return self:getColor("downloader", element)
end
function getDownloaderColor(element) return COLORS:getDownloaderColor(element) end

function COLORS.colorByJudgment(self, judge)
	return self:getColor("judgment", judge)
end

function COLORS.colorByDifficulty(self, diff)
	return self:getColor("difficulty", diff)
end

function COLORS.colorByGrade(self, grade)
	return self:getColor("grades", grade)
end

-- expecting ms input (153, 13.321, etc) so convert to seconds to compare to judgment windows -mina
function COLORS.colorByTapOffset(self, offset, scale)
	local offset = math.abs(offset / 1000)
	if not scale then
		scale = PREFSMAN:GetPreference("TimingWindowScale")
	end
	if offset <= scale * PREFSMAN:GetPreference("TimingWindowSecondsW1") then
		return self:colorByJudgment("TapNoteScore_W1")
	elseif offset <= scale * PREFSMAN:GetPreference("TimingWindowSecondsW2") then
		return self:colorByJudgment("TapNoteScore_W2")
	elseif offset <= scale * PREFSMAN:GetPreference("TimingWindowSecondsW3") then
		return self:colorByJudgment("TapNoteScore_W3")
	elseif offset <= scale * PREFSMAN:GetPreference("TimingWindowSecondsW4") then
		return self:colorByJudgment("TapNoteScore_W4")
	elseif offset <= math.max(scale * PREFSMAN:GetPreference("TimingWindowSecondsW5"), 0.180) then
		return self:colorByJudgment("TapNoteScore_W5")
	else
		return self:colorByJudgment("TapNoteScore_Miss")
	end
end

function colorByMSD(x)
	if x then
		return HSV(math.max(95 - (x / 40) * 150, -50), 0.9, 0.9)
	end
	return HSV(0, 0.9, 0.9)
end

function colorByMusicLength(x)
	if x then
		x = math.min(x, 600)
		return HSV(math.max(95 - (x / 900) * 150, -50), 0.9, 0.9)
	end
	return HSV(0, 0.9, 0.9)
end

function colorByFileSize(x)
	if x then
		x = math.min(x, 600)
		return HSV(math.max(95 - (x / 1025) * 150, -50), 0.9, 0.9)
	end
	return HSV(0, 0.9, 0.9)
end

function colorByNPS(x)
	return color("#ffffff")
	-- if we want to make a gradient
	--[[
	if x then
		x = math.min(x, 60)
		return HSV(math.max(95 - (x / 50) * 150, -50), 0.9, 0.9)
	end
	return HSV(0, 0.9, 0.9)
	]]
end

-- aliases for smooth brains
function COLORS.getColorConfigElementsForCategory(self, cat) return getColorConfigElementsForCategory(cat) end
function COLORS.getColorConfigCategories(self) return getColorConfigCategories() end
function COLORS.colorByMSD(self, x) return colorByMSD(x) end
function COLORS.colorByMusicLength(self, x) return colorByMusicLength(x) end
function COLORS.colorByFileSize(self, x) return colorByFileSize(x) end
function COLORS.colorByNPS(self, x) return colorByNPS(x) end
function getMainColor(ele) return COLORS:getMainColor(ele) end
function getLeaderboardColor(ele) return COLORS:getLeaderboardColor(ele) end
function getComboColor(ele) return COLORS:getComboColor(ele) end
function colorByClearType(x) return COLORS:colorByClearType(x) end
function colorByJudgment(x) return COLORS:colorByJudgment(x) end
function colorByDifficulty(x) return COLORS:colorByDifficulty(x) end
function colorByGrade(x) return COLORS:colorByGrade(x) end
function colorByTapOffset(x, ts) return COLORS:colorByTapOffset(x, ts) end

---=======- UTIL
-- convert a given color = {r,g,b,a} to the 4 HSV+alpha values
function colorToHSVNums(color)
	local r = color[1]
	local g = color[2]
	local b = color[3]
	local cmax = math.max(r, g, b)
	local cmin = math.min(r, g, b)
	local dc = cmax - cmin -- delta c
	local h = 0
	if dc == 0 then
		h = 0
	elseif cmax == r then
		h = 60 * (((g-b)/dc) % 6)
	elseif cmax == g then
		h = 60 * (((b-r)/dc) + 2)
	elseif cmax == b then
		h = 60 * (((r-g)/dc) + 4)
	end
	local s = (cmax == 0 and 0 or dc / cmax)
	local v = cmax

	local alpha = (color[4] and color[4] or 1)

	return h, 1-s, 1-v, alpha
end

-- convert a given color = {r,g,b,a} to the 4 hex bytes RRGGBBAA
function colorToRGBNums(c)
	local r = c[1]
	local g = c[2]
	local b = c[3]
	local a = HasAlpha(c)
	local rX = scale(r, 0, 1, 0, 255)
	local gX = scale(g, 0, 1, 0, 255)
	local bX = scale(b, 0, 1, 0, 255)
	local aX = scale(a, 0, 1, 0, 255)
	return rX, gX, bX, aX
end

-- use this function and provide any actor to be colored automatically
-- allows providing a stroke brightness optionally
-- immediately updates the color and also listens for future updates
-- make sure not to overwrite ColorConfigUpdateMessage[Command]
-- however, this function will not be useful for complex items which
--	require coloring different ways based on conditions
--	for those situations, do not use this function but instead utilize the command
function registerActorToColorConfigElement(self, category, element, stroke)
	if stroke == nil then stroke = 0 end
	local cmd = function(self)
		local alphab4 = self:GetDiffuseAlpha()
		local clr = COLORS:getColor(category, element)
		self:diffuse(clr)
		self:diffusealpha(alphab4)
		if self.strokecolor and stroke ~= 0 then
			self:strokecolor(Brightness(clr, stroke))
		end
	end
	cmd(self)
	self:addcommand("ColorConfigUpdatedMessage", cmd)
end

-- same as the above but instead its for elements that use diffuseramp
function registerActorToColorConfigElementForDiffuseRamp(self, category, element, lowAlpha, hiAlpha)
	local cmd = function(self)
		local lowColor = COLORS:getColor(category, element)
		lowColor[4] = lowAlpha
		local hiColor = COLORS:getColor(category, element)
		hiColor[4] = hiAlpha
		self:effectcolor1(lowColor)
		self:effectcolor2(hiColor)
	end
	cmd(self)
	self:addcommand("ColorConfigUpdatedMessage", cmd)
end

-- run this stuff at init/load
COLORS:loadColorConfigPresets()