--[=[
	@param gamepad Enum.UserInputType?
	@return Gamepad
	Constructs a gamepad object.

	If no gamepad UserInputType is provided, this object will always wrap
	around the currently-active gamepad, even if it changes. In most cases
	where input is needed from just the primary gamepad used by the player,
	leaving the `gamepad` argument blank is preferred.

	Only include the `gamepad` argument when it is necessary to hard-lock
	the object to a specific gamepad input type.

	```lua
	-- In most cases, construct the gamepad as such:
	local gamepad = Gamepad.new()

	-- If the exact UserInputType gamepad is needed, pass it as such:
	local gamepad = Gamepad.new(Enum.UserInputType.Gamepad1)
	```
]=]
function Gamepad.new(gamepad: Enum.UserInputType?)
	local self = setmetatable({}, Gamepad)
	self._trove = Trove.new()
	self._gamepadTrove = self._trove:Construct(Trove)
	self.ButtonDown = self._trove:Construct(Signal)
	self.ButtonUp = self._trove:Construct(Signal)
	self.Connected = self._trove:Construct(Signal)
	self.Disconnected = self._trove:Construct(Signal)
	self.GamepadChanged = self._trove:Construct(Signal)
	self.DefaultDeadzone = 0.05
	self.SupportsVibration = false
	self.State = {}
	self:_setupGamepad(gamepad)
	self:_setupMotors()
	return self
end


function Gamepad:_setupActiveGamepad(gamepad: Enum.UserInputType?)

	local lastGamepad = self._gamepad
	if gamepad == lastGamepad then return end

	self._gamepadTrove:Clean()
	table.clear(self.State)
	self.SupportsVibration = if gamepad then HapticService:IsVibrationSupported(gamepad) else false

	self._gamepad = gamepad

	-- Stop if disconnected:
	if not gamepad then
		self.Disconnected:Fire()
		self.GamepadChanged:Fire(nil)
		return
	end

	for _,inputObject in ipairs(UserInputService:GetGamepadState(gamepad)) do
		self.State[inputObject.KeyCode] = inputObject
	end

	self._gamepadTrove:Add(self, "StopMotors")

	self._gamepadTrove:Connect(UserInputService.InputBegan, function(input, processed)
		if input.UserInputType == gamepad then
			self.ButtonDown:Fire(input.KeyCode, processed)
		end
	end)

	self._gamepadTrove:Connect(UserInputService.InputEnded, function(input, processed)
		if input.UserInputType == gamepad then
			self.ButtonUp:Fire(input.KeyCode, processed)
		end
	end)

	if lastGamepad == nil then
		self.Connected:Fire()
	end
	self.GamepadChanged:Fire(gamepad)

end


function Gamepad:_setupGamepad(forcedGamepad: Enum.UserInputType?)

	if forcedGamepad then

		-- Forced gamepad:

		self._trove:Connect(UserInputService.GamepadConnected, function(gp)
			if gp == forcedGamepad then
				self:_setupActiveGamepad(forcedGamepad)
			end
		end)

		self._trove:Connect(UserInputService.GamepadDisconnected, function(gp)
			if gp == forcedGamepad then
				self:_setupActiveGamepad(nil)
			end
		end)

		if UserInputService:GetGamepadConnected(forcedGamepad) then
			self:_setupActiveGamepad(forcedGamepad)
		end

	else

		-- Dynamic gamepad:

		local function CheckToSetupActive()
			local active = GetActiveGamepad()
			if active ~= self._gamepad then
				self:_setupActiveGamepad(active)
			end
		end

		self._trove:Connect(UserInputService.GamepadConnected, CheckToSetupActive)
		self._trove:Connect(UserInputService.GamepadDisconnected, CheckToSetupActive)
		self:_setupActiveGamepad(GetActiveGamepad())

	end

end


function Gamepad:_setupMotors()
	self._setMotorIds = {}
	for _,motor in ipairs(Enum.VibrationMotor:GetEnumItems()) do
		self._setMotorIds[motor] = 0
	end
end