local _, env, db, L = ...; db, L = env.db, env.L;
local PanelMixin, Widgets = {}, env.Widgets;
local SHORTCUT_WIDTH, GENERAL_WIDTH, FIXED_OFFSET = 284, 700, 8;

---------------------------------------------------------------
-- Console variable fields
---------------------------------------------------------------
local Cvar, Mapper = env.CvarMixin, db.Mapper;
local Setting = CreateFromMixins(Cvar)

function Setting:OnLoad()
	Cvar.OnLoad(self)
	self:SetWidth(GENERAL_WIDTH - 32)
end

function Setting:GetData()
	return self:GetMetadata().data;
end

function Setting:SetRaw(variableID, ...)
	local data = self:GetData()
	if data then
		return Mapper:SetValue(self.variableID, ...)
	end
	return Cvar.SetRaw(self, variableID, ...)
end

function Setting:GetRaw(...)
	local data = self:GetData()
	if data then
		return Mapper:GetValue(self.variableID, data:Get())
	end
	return Cvar.GetRaw(self, ...)
end

function Setting:GetRawBool(...)
	local data = self:GetData()
	if data then
		return Mapper:GetValue(self.variableID, data:Get()) -- TODO
	end
	return Cvar.GetRawBool(self, ...)
end

function Setting:OnMapperConfigLoaded()
	self:OnValueChanged(self:GetRaw())
end

----------------------------------------------------------------
-- Device selection
----------------------------------------------------------------
-- Renders real devices in a list for individual mapping.
local function GetRealDevices()
	local realDevices = {{
		name = DEFAULT;
		productID = 0x0;
		vendorID  = 0x0;
	}};
	for i, deviceID in ipairs(C_GamePad.GetAllDeviceIDs()) do
		local device = C_GamePad.GetDeviceRawState(deviceID)
		if device then
			tinsert(realDevices, device)
		end
	end
	return realDevices;
end

local function GetRealDeviceIDs()
	local realDeviceIDs = {0x0};
	for i, deviceID in ipairs(C_GamePad.GetAllDeviceIDs()) do
		local device = C_GamePad.GetDeviceRawState(deviceID)
		if device then
			tinsert(realDeviceIDs, deviceID)
		end
	end
	return realDeviceIDs;
end

local function ConvertToHex(number)
	local hex = string.format('%x', number)
	return ((#hex % 2 == 1) and '0'..hex or hex):upper();
end

local DeviceSelect = {};

function DeviceSelect:Construct()
	local options = self:GetRawOptions()
	self:SetDrawOutline(true)
	self:SetText(L'Device')
	self.Label:ClearAllPoints()
	self.Label:SetPoint('LEFT', 16, 0)
	self.Label:SetJustifyH('LEFT')
	self.Label:SetTextColor(1, 1, 1)
	Widgets.Select(self, 'DeviceID', nil, db.Data.Select(1, 1):SetRawOptions(options), L'Device Information')
	self.Popout:ClearAllPoints()
	self.Popout:SetPoint('TOP', self, 'BOTTOM', 0, 0)
	self.controller:SetCallback(function(value)
		self:OnValueChanged(value)
		self:Update()
	end)
	self:Update()
end

function DeviceSelect:GetRawOptions()
	local options = {};
	for i, device in ipairs(GetRealDevices()) do
		tinsert(options, device.name)
	end
	return options;
end

function DeviceSelect:Get()
	return self.controller:Get()
end

function DeviceSelect:GetCurrentDevice()
	return GetRealDevices()[self:Get()]
end

function DeviceSelect:GetCurrentDeviceID()
	return GetRealDeviceIDs()[self:Get()]
end

function DeviceSelect:GetHexSlug(vendorID, productID)
	if ( vendorID == 0x0 and productID == 0x0 ) then
		return '';
	end
	return ('<|cFF00FF00%s|r:|cFF00FF00%s|r>'):format(
		ConvertToHex(vendorID),
		ConvertToHex(productID)
	);
end

function DeviceSelect:Update()
	local device = self:GetCurrentDevice()
	if device then
		self:SetText(('%s %s'):format(device.name, self:GetHexSlug(device.vendorID, device.productID)))
		self.tooltipText = ('Name: %s\nVendor ID: |cFF00FFFF%s|r / |cFF00FF00%s|r\nProduct ID: |cFF00FFFF%s|r / |cFF00FF00%s|r'):format(
			device.name,
			device.vendorID, ConvertToHex(device.vendorID),
			device.productID, ConvertToHex(device.productID)
		);
	else
		self:SetText(L'Select a device from the list to continue.')
	end
	db:TriggerEvent('OnMapperDeviceChanged', device, self:GetCurrentDeviceID())
end

---------------------------------------------------------------
-- Console
---------------------------------------------------------------
local Console = CreateFromMixins(env.SettingListMixin)

function Console:OnVariableChanged(variable, value)
	-- dealing with emulation button overlap (don't trust the user)
	if (variable:match('Emulate') or variable:match('Click')) and not self.isMutexLocked then
		self.isMutexLocked = true;
		for cvar in self:EnumerateActive() do
			if (cvar:Get() == value) and (cvar.variableID ~= variable) then
				cvar:Set('none', true)
			end
		end
		self.isMutexLocked = false;
	end
end

function Console:DrawOptions(showAdvanced)
	self.headerPool:ReleaseAll()

	local prev;

	-- profile settings
	for group, set in db.table.spairs(db.Profile) do
		prev = self:CreateHeader(group, prev)

		for i, data in ipairs(set) do
			local widget, newObj = self:TryAcquireRegistered(group..':'..data.path)
			if newObj then
				widget:SetDrawOutline(true)
				widget:OnLoad()
				db:RegisterCallback('OnMapperConfigLoaded', widget.OnMapperConfigLoaded, widget)
			end
			widget:Construct(data, newObj, self)
			widget:SetPoint('TOP', prev, 'BOTTOM', 0, -FIXED_OFFSET)
			prev = widget;
		end
	end

	-- console variables
	for group, set in db.table.spairs(db.Console) do
		prev = self:CreateHeader(group, prev)

		for i, data in db.table.spairs(set) do
			local widget, newObj = self:TryAcquireRegistered(group..':'..data.cvar)
			if newObj then
				widget:SetDrawOutline(true)
				widget:OnLoad()
			end
			widget:Construct(data, newObj, self)
			widget:SetPoint('TOP', prev, 'BOTTOM', 0, -FIXED_OFFSET)
			prev = widget;
		end
	end
	self.Child:SetHeight(nil)
end

function Console:OnLoad()
	CPFocusPoolMixin.OnLoad(self)
	env.OpaqueMixin.OnLoad(self)
	self.headerPool = CreateFramePool('Frame', self.Child, 'CPConfigHeaderTemplate')
	self:CreateFramePool('IndexButton',
		'CPIndexButtonSettingTemplate', Setting, nil, self.Child)
	Mixin(self.Child, env.ScaleToContentMixin)
	self.Child:SetAllPoints()
	self.Child:SetMeasurementOrigin(self, self.Child, GENERAL_WIDTH, FIXED_OFFSET)
end


---------------------------------------------------------------
-- Panel
---------------------------------------------------------------
function PanelMixin:OnFirstShow()
	LibStub('Carpenter'):BuildFrame(self, {
		Select = {
			_Type  = 'IndexButton';
			_Setup = 'CPIndexButtonBindingHeaderTemplate';
			_Mixin = DeviceSelect;
			_Width = SHORTCUT_WIDTH - 16;
			_Point = {'TOPLEFT', 8, -32};
			{
				TopText = {
					_Type = 'FontString';
					_Point = {'BOTTOMLEFT', '$parent', 'TOPLEFT', 8, 8};
					_OnLoad = function(self)
						self:SetFontObject(GameFontNormal)
						self:SetText(L'Selected device profile:')
					end;
				};
			};
		};
	}, false, true)
	local shortcuts = self:CreateScrollableColumn('Shortcuts', {
		_Mixin = env.SettingShortcutsMixin;
		_Width = SHORTCUT_WIDTH;
		_Setup = {'CPSmoothScrollTemplate', 'BackdropTemplate'};
		_Backdrop = CPAPI.Backdrops.Opaque;
		_Points = {
			{'TOPLEFT', '$parent.Select', 'BOTTOMLEFT', -8, -40};
			{'BOTTOMLEFT', 0, 200};
		};
	})
	local cvars = self:CreateScrollableColumn('Console', {
		_Mixin = Console;
		_Width = GENERAL_WIDTH;
		_Setup = {'CPSmoothScrollTemplate', 'BackdropTemplate'};
		_Backdrop = CPAPI.Backdrops.Opaque;
		_Points = {
			{'TOPLEFT', '$parent.Select', 'TOPRIGHT', 8, 32};
			{'BOTTOMLEFT', '$parent.Shortcuts', 'BOTTOMRIGHT', 0, -200};
		};
	})
	self.Select:Construct()
	cvars.Shortcuts = shortcuts;
	shortcuts.List = cvars;
end

env.Console = ConsolePortConfig:CreatePanel({
	name  = SETTINGS;
	mixin = PanelMixin;
	scaleToParent = true;
	forbidRecursiveScale = true;
})