
-- if you change something here you probably want to change the glue version too

CONTROLTYPE_CHECKBOX = 1;
CONTROLTYPE_DROPDOWN = 2;
CONTROLTYPE_SLIDER = 3;


local ALT_KEY = "altkey";
local CONTROL_KEY = "controlkey";
local SHIFT_KEY = "shiftkey";
local NO_KEY = "none";

local next = next;
local function SecureNext(elements, key)
	return securecall(next, elements, key);
end

local tinsert = tinsert;
local tonumber = tonumber;
local tostring = tostring;
local gsub = gsub;


-- [[ Slider functions ]] --

function BlizzardOptionsPanel_Slider_Disable (slider)
	local name = slider:GetName();
	getmetatable(slider).__index.Disable(slider);
	_G[name.."Text"]:SetVertexColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b);
	_G[name.."Low"]:SetVertexColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b);
	_G[name.."High"]:SetVertexColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b);
end

function BlizzardOptionsPanel_Slider_Enable (slider)
	local name = slider:GetName();
	getmetatable(slider).__index.Enable(slider);
	_G[name.."Text"]:SetVertexColor(NORMAL_FONT_COLOR.r , NORMAL_FONT_COLOR.g , NORMAL_FONT_COLOR.b);
	_G[name.."Low"]:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
	_G[name.."High"]:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
end

function BlizzardOptionsPanel_Slider_Refresh (slider)
	local value;

	if ( slider.GetCurrentValue ) then
		value = slider:GetCurrentValue();
	elseif ( slider.cvar ) then
		value = BlizzardOptionsPanel_GetCVarSafe(slider.cvar);
	end

	if ( value ) then
		if ( slider.SetDisplayValue ) then
			slider:SetDisplayValue(value);
		else
			slider:SetValue(value);
		end
		slider.value = value;
	end
end

function BlizzardOptionsPanel_Slider_OnValueChanged (self, value)
	if(self.onvaluechanged) then
		self:onvaluechanged(value);
	else
		BlizzardOptionsPanel_SetCVarSafe(self.cvar, value);
	end
	self.value = value;
end

-- [[ CheckButton functions ]] --

function BlizzardOptionsPanel_CheckButton_Disable(checkBox)
	checkBox:Disable();
	local text = _G[checkBox:GetName().."Text"];
	if ( text ) then
		text:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b);
	end
end

function BlizzardOptionsPanel_CheckButton_Enable(checkBox, isWhite)
	checkBox:Enable();
	local text = _G[checkBox:GetName().."Text"];
	if ( not text ) then
		return;
	end
	if ( isWhite ) then
		text:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
	else
		text:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b);
	end
end

function BlizzardOptionsPanel_CheckButton_OnClick (checkButton)
	BlizzardOptionsPanel_CheckButton_SetNewValue(checkButton);

	local setting = "0";
	if ( checkButton:GetChecked() ) then
		if ( not checkButton.invert ) then
			setting = "1";
		end
	elseif ( checkButton.invert ) then
		setting = "1";
	end

	if ( checkButton.setFunc ) then
		checkButton.setFunc(setting);
	end
end

function BlizzardOptionsPanel_CheckButton_SetNewValue (checkButton)
	local setting = "0";
	if ( checkButton:GetChecked() ) then
		if ( not checkButton.invert ) then
			setting = "1";
		end
	elseif ( checkButton.invert ) then
		setting = "1";
	end

	if ( setting == checkButton.value ) then
		checkButton.newValue = nil;
	else
		checkButton.newValue = setting;
	end

	if ( checkButton.dependentControls ) then
		if ( checkButton:GetChecked() ) then
			for _, control in SecureNext, checkButton.dependentControls do
				control:Enable();
			end
		else
			for _, control in SecureNext, checkButton.dependentControls do
				control:Disable();
			end
		end
	end
end

function BlizzardOptionsPanel_CheckButton_Refresh (checkButton)
	local value;

	if ( checkButton.cvar ) then
		value = GetCVar(checkButton.cvar);
	elseif ( checkButton.GetValue ) then
		value = tostring(checkButton:GetValue());
	end

	if ( value ) then
		if ( checkButton.invert ) then
			checkButton:SetChecked(value == (checkButton.uncheckedValue or "0"));
		else
			checkButton:SetChecked(value == (checkButton.checkedValue or "1"));
		end

		if ( checkButton.dependentControls ) then
			if ( checkButton:GetChecked() ) then
				for _, depControl in SecureNext, checkButton.dependentControls do
					depControl:Enable();
				end
			else
				for _, depControl in SecureNext, checkButton.dependentControls do
					depControl:Disable();
				end
			end
		end

		checkButton.value = value;
	end
end

-- [[ DropDown functions ]] --

function BlizzardOptionsPanel_DropDown_Refresh (dropDown)
end


-- [[ BlizzardOptionsPanel functions ]] --

-- HACK: unfortunately, CVars have this funny quirk where they are returned as strings, even if they are numbers,
-- which makes things complicated for sliders...things get even more complicated when you have a mix of regular
-- Get/SetCVar calls with the following (typesafe) BlizzardOptionsPanel_Get/SetCVarSafe calls... so to avoid
-- comparing numbers to strings, we are going to convert anything that needs comparing into a number first!

function BlizzardOptionsPanel_SetCVarSafe (cvar, value, event)
	local oldValue = GetCVar(cvar);
	local oldValueNum = tonumber(oldValue);
	local valueNum = tonumber(value);
	if ( oldValueNum or valueNum ) then
		if ( oldValueNum ~= valueNum ) then
			SetCVar(cvar, value, event);
		end
	else
		if ( oldValue ~= value ) then
			SetCVar(cvar, value, event);
		end
	end
end

function BlizzardOptionsPanel_GetCVarSafe (cvar)
	local value = GetCVar(cvar);
	value = tonumber(value) or value;
	return value;
end

function BlizzardOptionsPanel_GetCVarDefaultSafe (cvar)
	local value = GetCVarDefault(cvar);
	value = tonumber(value) or value;
	return value;
end

function BlizzardOptionsPanel_OkayControl (control)
	if ( control.newValue ) then
		if ( control.value ~= control.newValue ) then
			control:SetValue(control.newValue);
			control.value = control.newValue;
			control.newValue = nil;
		end
	elseif ( control.value ) then
		if ( control:GetValue() ~= control.value ) then
			control:SetValue(control.value);
		end
	end
end

function BlizzardOptionsPanel_CancelControl (control)
	if ( control.newValue ) then
		if ( control.value and control.value ~= control.newValue ) then
			-- we need to force-set the value here just in case the control was doing dynamic updating
			control:SetValue(control.value);
			control.newValue = nil;
		end
	elseif ( control.value ) then
		if ( control:GetValue() ~= control.value ) then
			control:SetValue(control.value);
		end
	end
end

function BlizzardOptionsPanel_DefaultControl (control)
	if ( control.defaultValue and control.value ~= control.defaultValue ) then
		control:SetValue(control.defaultValue);
		control.value = control.defaultValue;
		control.newValue = nil;
	end
end

function BlizzardOptionsPanel_RefreshControl (control)
	if ( control.RefreshValue ) then
		control:RefreshValue();
	end
	if ( control.type == CONTROLTYPE_CHECKBOX ) then
		BlizzardOptionsPanel_CheckButton_Refresh(control);
	elseif ( control.type == CONTROLTYPE_DROPDOWN ) then
		BlizzardOptionsPanel_DropDown_Refresh(control);
	elseif ( control.type == CONTROLTYPE_SLIDER ) then
		BlizzardOptionsPanel_Slider_Refresh(control);
	end
end

function BlizzardOptionsPanel_Okay (self)
	for _, control in SecureNext, self.controls do
		securecall(BlizzardOptionsPanel_OkayControl, control);
	end
end

function BlizzardOptionsPanel_Cancel (self)
	for _, control in SecureNext, self.controls do
		securecall(BlizzardOptionsPanel_CancelControl, control);
	end
end

function BlizzardOptionsPanel_Default (self)
	for _, control in SecureNext, self.controls do
		securecall(BlizzardOptionsPanel_DefaultControl, control);
	end
end

function BlizzardOptionsPanel_Refresh (self)
	for _, control in SecureNext, self.controls do
		securecall(BlizzardOptionsPanel_RefreshControl, control);
	end
end

function BlizzardOptionsPanel_OnLoad (frame, okay, cancel, default, refresh)
	frame.okay = okay or BlizzardOptionsPanel_Okay;
	frame.cancel = cancel or BlizzardOptionsPanel_Cancel;
	frame.default = default or BlizzardOptionsPanel_Default;
	frame.refresh = refresh or BlizzardOptionsPanel_Refresh;

	if ( frame:IsEventRegistered("PLAYER_ENTERING_WORLD") ) then
		frame.keepPEWRegistered = true;
	else
		frame:RegisterEvent("PLAYER_ENTERING_WORLD");
	end
	if ( not frame:GetScript("OnEvent") ) then
		frame:SetScript("OnEvent", BlizzardOptionsPanel_OnEvent);
	end
end

function BlizzardOptionsPanel_OnEvent (frame, event, ...)
	if ( event == "PLAYER_ENTERING_WORLD" ) then
		if ( frame.options and frame.controls ) then
			local entry;
			local minValue, maxValue;
			for i, control in SecureNext, frame.controls do
				entry = frame.options[(control.cvar or control.label)];
				if ( entry ) then
					if ( entry.text ) then
						control.tooltipText = (_G["OPTION_TOOLTIP_" .. gsub(entry.text, "_TEXT$", "")] or entry.tooltip);
						local text = _G[control:GetName() .. "Text"];
						if ( text ) then
							text:SetText(_G[entry.text] or entry.text);
						end
					end
					control.tooltipRequirement = entry.tooltipRequirement;

					control.gameRestart = entry.gameRestart;
					control.logout = entry.logout;

					control.event = entry.event or entry.text;

					if ( control.cvar ) then
						if ( control.type == CONTROLTYPE_CHECKBOX ) then
							control.defaultValue = control.defaultValue or GetCVarDefault(control.cvar);
						else
							control.defaultValue = BlizzardOptionsPanel_GetCVarDefaultSafe(control.cvar);
							minValue = entry.minValue;
							maxValue = entry.maxValue;
						end
					else
						control.defaultValue = control.defaultValue or entry.default;
						minValue = entry.minValue;
						maxValue = entry.maxValue;
					end

					if ( control.type == CONTROLTYPE_SLIDER ) then
						BlizzardOptionsPanel_Slider_Enable(control);
						control:SetMinMaxValues(minValue, maxValue);
						control:SetValueStep(entry.valueStep);
					end

					securecall(BlizzardOptionsPanel_SetupControl, control);
				end
			end
		end
		if ( not frame.keepPEWRegistered ) then
			frame:UnregisterEvent(event);
		end
	end
end

function BlizzardOptionsPanel_RegisterControl (control, parentFrame)
	if ( ( not parentFrame ) or ( not control ) ) then
		return;
	end

	parentFrame.controls = parentFrame.controls or {};

	tinsert(parentFrame.controls, control);

	-- Use the panel's OnEvent handler to wait and setup the control after game data is loaded
end

function BlizzardOptionsPanel_SetupControl (control)
	if ( control.type == CONTROLTYPE_CHECKBOX ) then			
		if ( control.cvar ) then
			local value = GetCVar(control.cvar);
			control.value = value;

			if ( control.uvar ) then
				_G[control.uvar] = value;
			end

			control.GetValue = function(self) return GetCVar(self.cvar); end
			control.SetValue = function(self, value) self.value = value; BlizzardOptionsPanel_SetCVarSafe(self.cvar, value, self.event); if ( self.uvar ) then _G[self.uvar] = value end end
			control.Disable = function (self) getmetatable(self).__index.Disable(self) _G[self:GetName().."Text"]:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b) end;
			control.Enable = function (self)
				getmetatable(self).__index.Enable(self);
				local text = _G[self:GetName().."Text"];
				local fontObject = text:GetFontObject();
				_G[self:GetName().."Text"]:SetTextColor(fontObject:GetTextColor());
			end
		elseif ( control.GetValue ) then
			if ( control.type == CONTROLTYPE_CHECKBOX ) then
				local value = control:GetValue();
				if ( value ) then
					control.value = tostring(value);
				else
					control.value = "0";
				end
				if ( control.uvar ) then
					_G[control.uvar] = value;
				end

				control.SetValue = function(self, value) self.value = value; if ( self.uvar ) then _G[self.uvar] = value; end end;
				control.Disable = function (self) getmetatable(self).__index.Disable(self) _G[self:GetName().."Text"]:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b) end;
				control.Enable = function (self)
					getmetatable(self).__index.Enable(self);
					local text = _G[self:GetName().."Text"];
					local fontObject = text:GetFontObject();
					_G[self:GetName().."Text"]:SetTextColor(fontObject:GetTextColor());
				end
			end
		end
	elseif ( control.type == CONTROLTYPE_SLIDER ) then
		local value;
		if ( control.GetCurrentValue ) then
			value = control:GetCurrentValue();
		elseif ( control.cvar ) then
			value = BlizzardOptionsPanel_GetCVarSafe(control.cvar);
		else
			value = control:GetValue();
		end

		if ( control.SetDisplayValue ) then
			control:SetDisplayValue(value);
		else
			control:SetValue(value);
		end
		-- set the value AFTER the set value function call so the current value matches the new value
		-- just in case an OnValueChange script changed the new value
		control.value = value;

		control.Disable = BlizzardOptionsPanel_Slider_Disable;
		control.Enable = BlizzardOptionsPanel_Slider_Enable;
	end
end

function BlizzardOptionsPanel_SetupDependentControl (dependency, control)
	if ( not dependency ) then
		return;
	end

	assert(control);

	dependency.dependentControls = dependency.dependentControls or {};
	tinsert(dependency.dependentControls, control);

	if ( control.type ~= CONTROLTYPE_DROPDOWN ) then
		control.Disable = function (self) getmetatable(self).__index.Disable(self) _G[self:GetName().."Text"]:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b) end;
		control.Enable = function (self) getmetatable(self).__index.Enable(self) _G[self:GetName().."Text"]:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) end;
	else
		control.Disable = function (self) UIDropDownMenu_DisableDropDown(self) end;
		control.Enable = function (self) UIDropDownMenu_EnableDropDown(self) end;
	end
end

