local _, db = ...;
local HELP_STRING, SLASH_FUNCTIONS = 'Usage: |cFFFFFFFF/consoleport|r |cFF00FFFF%s|r |cFF00FF00%s|r';
local DOCU_STRING = '  |cFF00FFFF%s|r |cFF00FF00%s|r \n- |cFFFFFFFF%s|r';
local CONFIG_ADDON_NAME = 'ConsolePort_Config';
local CURSOR_ADDON_NAME = 'ConsolePort_Cursor';
---------------------------------------------------------------
-- Process slash command
---------------------------------------------------------------
local function ProcessSlash(command, ...)
	local func = SLASH_FUNCTIONS[command];
	if func then
		func(...)
		return true
	end
end

local function Compile(...)
	local args = table.concat({...}, ', ')
	local func, errorMsg = loadstring(('return %s'):format(args))
	if not func then
		CPAPI.Log('Error while compiling:\n%s', errorMsg)
		return
	end
	return func(), args;
end

local function ProcessVarUpdate(var, ...)
	if (db(var) ~= nil) then
		local value, args = Compile(...)
		if (value == nil) then
			return true;
		end
		if (args == 'nil') then
			db('Settings/'..var, nil)
			CPAPI.Log('Variable |cFFFFFFFF%s|r reset to default.')
			return true;
		elseif (value ~= nil) then
			db('Settings/'..var, value)
			CPAPI.Log('Variable |cFFFFFFFF%s|r updated to:', var)
			DevTools_Dump(value)
			return true;
		end
	end
end

local function HandleSlashCommand(self, msg)
	if ProcessSlash((' '):split(msg or '')) then
		return
	elseif ProcessVarUpdate((' '):split(msg or '')) then
		return
	end
	if not CPAPI.IsAddOnLoaded(CONFIG_ADDON_NAME) then
		CPAPI.EnableAddOn(CONFIG_ADDON_NAME)
		CPAPI.LoadAddOn(CONFIG_ADDON_NAME)
	end
	ConsolePortConfig:SetShown(not ConsolePortConfig:IsShown())
end

local function Uninstall()
	for _, var in ipairs({
		-- Saved variables
		'ConsolePortSettings',
		'ConsolePortDevices',
		'ConsolePortUIStack',
		'ConsolePortShared',
		-- Saved variables per character
		'ConsolePortUtility',
		'ConsolePort_BarLayout',
		'ConsolePort_BarSetup', -- legacy
	}) do _G[var] = nil; end
	ReloadUI()
end

function ClearPadBindings()
	local function ClearPadBinding(binding, key, ...)
		if key then
			if IsBindingForGamePad(key) then
				SetBinding(key, nil)
			end
			ClearPadBinding(binding, ...)
		end
	end

	for i=1, GetNumBindings() do
		ClearPadBinding(GetBinding(i))
	end
end

---------------------------------------------------------------
-- Slash functions
---------------------------------------------------------------
SLASH_FUNCTIONS = {
	-----------------------------------------------------------
	-- Stack handling
	-----------------------------------------------------------
	addframe = function(owner, frame)
		if owner and frame then
			local loadable, reason = select(4, GetAddOnInfo(owner))
			if loadable or IsAddOnLoaded(owner) then
				EnableAddOn(CURSOR_ADDON_NAME)
				LoadAddOn(CURSOR_ADDON_NAME)
				return EventUtil.ContinueOnAddOnLoaded(CURSOR_ADDON_NAME, function()
					local stack = db.Stack;
					if stack:TryRegisterFrame(owner, frame, true) then
						stack:AddFrame(frame)
						stack:UpdateFrames()
						return CPAPI.Log('Frame %s was added under %s.', frame, owner)
					end
				end)
			end
			return CPAPI.Log('Addon %s is not eligble. Reason: %s', owner, _G['ADDON_'..reason])
		end
		CPAPI.Log(HELP_STRING, 'addframe', 'addonName frameName')
	end;
	removeframe = function(owner, frame)
		if owner and frame then
			local loadable, reason = select(4, GetAddOnInfo(owner))
			if loadable or IsAddOnLoaded(owner) then
				EnableAddOn(CURSOR_ADDON_NAME)
				LoadAddOn(CURSOR_ADDON_NAME)
				return EventUtil.ContinueOnAddOnLoaded(CURSOR_ADDON_NAME, function()
					local stack = db.Stack;
					if stack:TryRemoveFrame(owner, frame) then
						stack:RemoveFrame(_G[frame])
						stack:UpdateFrames()
						return CPAPI.Log('Frame %s was removed from %s.', frame, owner)
					end
				end)
			end
			return CPAPI.Log('Addon is not eligble. Reason: %s', reason)
		end
		CPAPI.Log(HELP_STRING, 'removeframe', 'addonName frameName')
	end;
	-----------------------------------------------------------
	-- Config
	-----------------------------------------------------------
	applyconfig = function(useBluetooth)
		local bluetooth = useBluetooth and Compile(useBluetooth)
		local device = db('Gamepad/Active')
		if device then
			device:ApplyConfig(bluetooth)
			CPAPI.Log('Config was applied for your %s device.', device.Name)
		end
	end;
	bluetooth = function(state)
		if state then
			local bluetooth = state and Compile(state)
			local device = db('Gamepad/Active')
			if device then
				device:ApplyConfig(bluetooth)
				return CPAPI.Log('Bluetooth set to %s for %s.', state, device.Name)
			end
		end
		CPAPI.Log(HELP_STRING, 'bluetooth', 'true/false')
	end;
	config = function(path, value)
		if not path and not value then
			return CPAPI.Log(HELP_STRING, 'config', 'path/to/key [value]')
		end

		local config = db('Gamepad/Active/Config')
		if not config then return CPAPI.Log('No active config found.') end

		if not value then
			value = db('Gamepad/Active/Config/'..path)
			if value then
				CPAPI.Log('Value found for %s:', path)
				DevTools_Dump(value)
			else
				CPAPI.Log('Value not found for %s.', path)
			end
			return
		end

		local newstate = Compile(value)
		local __mt, __index = {};
		function __index(t, k)
			local v = setmetatable({}, __mt)
			rawset(t, k, v)
			return v;
		end
		__mt.__index = __index;

		setmetatable(config, __mt)
		if db('Gamepad/Active/Config/'..path, newstate) then
			db('Gamepad/Active'):ApplyConfig()
			return CPAPI.Log('Value %s set at %s.', tostring(newstate), path)
		end
	end;
	status = function(deviceID)
		local activeDevices = {};
		for _, i in ipairs(C_GamePad.GetAllDeviceIDs()) do
			local device = C_GamePad.GetDeviceRawState(i)
			local devicePowerLevel = C_GamePad.GetPowerLevel(i)
			local powerLevelInfo = db.Battery:GetPowerLevelInfo(devicePowerLevel)
			if device then
				tinsert(activeDevices, {
					id    = i;
					state = device;
					powerLevel = powerLevelInfo.color:WrapTextInColorCode(powerLevelInfo.name);
				})
			end
		end
		if next(activeDevices) then
			CPAPI.Log('Connected devices:')
			for _, device in ipairs(activeDevices) do
				local vendorID  = ('%04x'):format(device.state.vendorID):upper();
				local productID = ('%04x'):format(device.state.productID):upper();
				local powerLevel = device.powerLevel;
				local config = C_GamePad.GetConfig({
					vendorID  = device.state.vendorID;
					productID = device.state.productID;
				});

				CPAPI.Log('%d: |cFFFFFFFF%s|r', device.id, device.state.name)
				CPAPI.Log('   Vendor: |cFF00FFFF%s|r, Product: |cFF00FFFF%s|r, Config: %s, Battery Level: %s',
					vendorID, productID,
					config and ('|cFF00FF00%s|r'):format(config.name or 'custom') or '|cFFFFFFFFgeneric|r',
					powerLevel
				);
			end
		else
			CPAPI.Log('No connected devices found.')
		end
		-- TODO: show axis readings and state when device ID is provided
	end;
	-----------------------------------------------------------
	-- Reset/uninstall
	-----------------------------------------------------------
	clearbindings = function()
		ClearPadBindings()
		ReloadUI()
	end;
	resetall = function()
		CPAPI.Popup('ConsolePort_Uninstall_Settings', {
			text = 'This action will remove all your saved settings and reload your interface.';
			button1 = OKAY;
			button2 = CANCEL;
			timeout = 0;
			whileDead = 1;
			showAlert = 1;
			OnAccept = Uninstall;
		})
	end;
	resetconfigs = function()
		for i, config in ipairs(C_GamePad.GetAllConfigIDs()) do
			C_GamePad.DeleteConfig(config)
		end
		C_GamePad.ApplyConfigs()
		ReloadUI()
	end;
	uninstall = function()
		CPAPI.Popup('ConsolePort_Uninstall_Addon', {
			text = 'This action will remove all your saved settings and reload your interface.';
			button1 = OKAY;
			button2 = CANCEL;
			timeout = 0;
			whileDead = 1;
			showAlert = 1;
			OnAccept = function()
				DisableAddOn('ConsolePort')
				SetCVar('GamePadEnable', 0)
				ClearPadBindings()
				Uninstall()
			end;
		})
	end;
	-----------------------------------------------------------
	-- Help
	-----------------------------------------------------------
	help = function(command)
		local commands = {
			{'<empty>', '',
				'Open configuration interface.'};
			{'addframe', 'addonName frameName',
				'Add a custom frame to cursor stack.'};
			{'removeframe', 'addonName frameName',
				'Remove a frame from cursor stack.'};
			{'applyconfig', 'useBluetooth',
				'Apply config for the active device.'};
			{'bluetooth', 'true/false',
				'Change bluetooth state for active device.'};
			{'config', 'path/to/key [value]',
				'Change or print a value from the active device configuration.'};
			{'status', '[deviceID]',
				'Show connected devices.'};
			{'clearbindings', '',
				'Clear configured gamepad bindings and reload interface.'};
			{'resetall', '',
				'Remove all saved settings and reload interface.'};
			{'resetconfigs', '',
				'Reset all mapping configurations and reload. (will not affect bindings)'};
			{'uninstall', '',
				'Remove all saved settings and bindings, disable addon, and reload interface.'};
		}
		if not command then
			CPAPI.Log(HELP_STRING, 'command', '[args]')
			for i, command in ipairs(commands) do
				CPAPI.Log(DOCU_STRING, unpack(command))
			end
		end
	end;
}

---------------------------------------------------------------
-- Set up slash handler
---------------------------------------------------------------
setmetatable(ConsolePort, {
	__index = getmetatable(ConsolePort).__index;
	__call  = HandleSlashCommand;
})

RegisterNewSlashCommand(ConsolePort, 'consoleport', 'cp')

---------------------------------------------------------------
-- Temp?: add a game menu button to access config
---------------------------------------------------------------
do local GMB = CreateFrame('Button', '$parent'.._, GameMenuFrame)
	GMB:SetSize(58, 58)
	GMB:SetPoint('TOP', 0, 70)
	GMB:SetNormalTexture(CPAPI.GetAsset([[Textures\Logo\CP_Thumb]]))
	GMB:SetPushedTexture(CPAPI.GetAsset([[Textures\Logo\CP_Thumb]]))
	local pushed = GMB:GetPushedTexture()
	pushed:ClearAllPoints()
	pushed:SetSize(GMB:GetSize())
	pushed:SetPoint('CENTER', 0, -2)
	-- Protect against ElvUI shenanigans
	GMB.SetNormalTexture = nop;
	GMB.SetPushedTexture = nop;
	GMB.GetNormalTexture = nop;
	GMB.GetPushedTexture = nop;
	GMB:SetScript('OnClick', function()
		if not InCombatLockdown() then
			HideUIPanel(GameMenuFrame)
			ConsolePort()
		end
	end)
end