local s=(require(((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\083\099\114\105\112\116\115")):WaitForChild("\067\111\110\115\111\108\101"))).sourced("\077\111\110\105\116\111\114")s.log("\076\111\097\100\105\110\103 \100\101\112\101\110\100\101\110\099\105\101\115\046\046\046")local U=game:GetService("\085\115\101\114\073\110\112\117\116\083\101\114\118\105\099\101")local g=require(script.Parent:WaitForChild("\073\110\112\117\116\084\121\112\101"))local L=require(script.Parent:WaitForChild("\068\105\114\101\099\116\105\111\110\077\101\116\104\111\100"))s.log("\067\111\110\115\116\114\117\099\116\105\110\103 \102\117\110\099\116\105\111\110\115\046\046\046")local function P(s,g)local L=U:GetGamepadState(g)for U,g in ipairs(L)do if g.KeyCode==s then return g end end end s.log("\073\110\105\116\105\097\108\105\122\105\110\103 \118\097\114\105\097\098\108\101\115\046\046\046")local p={}s.log("\067\111\110\115\116\114\117\099\116\105\110\103 \111\098\106\101\099\116\115\046\046\046")p.__index=p function p.new()local s=setmetatable({},p)s.bound=false s.inputs={}return s end function p.Bound(s)return s.bound end function p.GetValue(s)return s.value end function p.Update(p)local i=p:nullValue()local C=0 for F,d in ipairs(p.inputs)do local Q local I local V=true if d.Type==g.None then Q=false elseif d.Type==g.Keyboard then Q=U:IsKeyDown(d.Code)elseif d.Type==g.MouseButton then Q=U:IsMouseButtonPressed(d.Code)elseif d.Type==g.MouseMovement then if d.Code==L.None then Q=false elseif d.Code==L.Absolute then Q=U:GetMouseLocation()elseif d.Code==L.Relative then Q=U:GetMouseDelta()else s.error("\073\110\112\117\116 \104\097\115 \105\110\118\097\108\105\100 \100\105\114\101\099\116\105\111\110 \109\101\116\104\111\100\046")V=false end elseif d.Type==g.GamepadButton then Q=U:IsGamepadButtonDown(d.Code,d.Device)elseif d.Type==g.GamepadDirection then if d.Code==L.None then Q=false elseif d.Code==L.Absolute then Q=(P(d.Code,d.Device)).Position elseif d.Code==L.Relative then Q=(P(d.Code,d.Device)).Delta else s.error("\073\110\112\117\116 \104\097\115 \105\110\118\097\108\105\100 \100\105\114\101\099\116\105\111\110 \109\101\116\104\111\100\046")V=false end elseif d.Type==g.Scheme then Q=d.Code else s.error("\073\110\112\117\116 \104\097\115 \105\110\118\097\108\105\100 \116\121\112\101\046")V=false end if V then Q,I=p:transformValue(d,Q)i=p:addValue(i,Q)C=C+I end end p.value=p:scaleValue(i,C)return p:GetValue()end function p.Bind(U,s)if not U:Bound()then for s,g in ipairs(s.inputs)do local L={}L.Type=g.Type L.Code=g.Code L.Offset=g.Offset L.Devices=g.Devices U:processEntry(L)table.insert(U.inputs,L)end U.bound=true end end function p.Unbind(s)if s:Bound()then s.inputs={}s:clean()s.bound=false end end function p.transformValue(U)s.warn("\117\112\100\097\116\101\073\110\112\117\116 \109\117\115\116 \098\101 \111\118\101\114\114\105\100\100\101\110 \097\110\100 \115\104\111\117\108\100 \110\111\116 \098\101 \099\097\108\108\101\100 \102\114\111\109 \077\111\110\105\116\111\114\046")end function p.addValue(U)s.warn("\097\100\100\086\097\108\117\101 \109\117\115\116 \098\101 \111\118\101\114\114\105\100\100\101\110 \097\110\100 \115\104\111\117\108\100 \110\111\116 \098\101 \099\097\108\108\101\100 \102\114\111\109 \077\111\110\105\116\111\114\046")end function p.scaleValue(U)s.warn("\115\099\097\108\101\086\097\108\117\101 \109\117\115\116 \098\101 \111\118\101\114\114\105\100\100\101\110 \097\110\100 \115\104\111\117\108\100 \110\111\116 \098\101 \099\097\108\108\101\100 \102\114\111\109 \077\111\110\105\116\111\114\046")end function p.defaultValue(U)s.warn("\100\101\102\097\117\108\116\086\097\108\117\101 \109\117\115\116 \098\101 \111\118\101\114\114\105\100\100\101\110 \097\110\100 \115\104\111\117\108\100 \110\111\116 \098\101 \099\097\108\108\101\100 \102\114\111\109 \077\111\110\105\116\111\114\046")end function p.processEntry(U)s.warn("\112\114\111\099\101\115\115\069\110\116\114\121 \109\117\115\116 \098\101 \111\118\101\114\114\105\100\100\101\110 \097\110\100 \115\104\111\117\108\100 \110\111\116 \098\101 \099\097\108\108\101\100 \102\114\111\109 \077\111\110\105\116\111\114\046")end function p.clean(U)s.warn("\099\108\101\097\110 \109\117\115\116 \098\101 \111\118\101\114\114\105\100\100\101\110 \097\110\100 \115\104\111\117\108\100 \110\111\116 \098\101 \099\097\108\108\101\100 \102\114\111\109 \077\111\110\105\116\111\114\046")end s.log("\068\111\110\101\046")return p.new()
--- An abstract Monitor for tracking inputs.
-- A monitor is used to keep track of the inputs of a single Control. This is an
-- abstract class and should be used to inherit from in other monitors. It
-- should not be instantiated directly.
--
-- @author LastTalon
-- @version 0.1.1, 2020-10-01
-- @since 0.1
--
-- @module Monitor

local Console = require(game:GetService("ReplicatedStorage"):WaitForChild("Scripts"):WaitForChild("Console")).sourced("Monitor")

-- Dependencies --
Console.log("Loading dependencies...")

local UserInputService = game:GetService("UserInputService")

local InputType = require(script.Parent:WaitForChild("InputType"))
local DirectionMethod = require(script.Parent:WaitForChild("DirectionMethod"))

-- Functions --
Console.log("Constructing functions...")

local function getDeviceInput(code, device)
	local inputs = UserInputService:GetGamepadState(device)
	for _, input in ipairs(inputs) do
		if input.KeyCode == code then
			return input
		end
	end
end

-- Variables --
Console.log("Initializing variables...")

local Monitor = {}

-- Local Objects --
Console.log("Constructing objects...")

Monitor.__index = Monitor

--- The default constructor.
-- Creates a new, unbound Monitor.
--
-- @return the new Monitor
function Monitor.new()
	local self = setmetatable({}, Monitor)
	self.bound = false
	self.inputs = {}
	return self
end

--- Tells if this Monitor is currently bound.
--
-- @return true if bound, false otherwise
function Monitor:Bound()
	return self.bound
end

--- Gets this Monitor's value.
--
-- @return the value
function Monitor:GetValue()
	return self.value
end

--- Updates the value of this Monitor.
-- Gathers all Inputs and updates the value of this Monitor's input based on
-- the scaling of this Monitor. This method should not typically be called
-- directly and should be called by the ControlModule.
--
-- @return the value
-- @throws error when an input has an invalid enum
--
-- @see Input
-- @see ControlModule
function Monitor:Update()
	local value = self:nullValue()
	local scale = 0
	for _, input in ipairs(self.inputs) do
		local inputValue
		local inputScale
		local valid = true
		
		-- Get specific input
		if input.Type == InputType.None then
			inputValue = false
		elseif input.Type == InputType.Keyboard then
			inputValue = UserInputService:IsKeyDown(input.Code)
		elseif input.Type == InputType.MouseButton then
			inputValue = UserInputService:IsMouseButtonPressed(input.Code)
		elseif input.Type == InputType.MouseMovement then
			if input.Code == DirectionMethod.None then
				inputValue = false
			elseif input.Code == DirectionMethod.Absolute then
				inputValue = UserInputService:GetMouseLocation()
			elseif input.Code == DirectionMethod.Relative then
				inputValue = UserInputService:GetMouseDelta()
			else
				Console.error("Input has invalid direction method.")
				valid = false
			end
		elseif input.Type == InputType.GamepadButton then
			inputValue = UserInputService:IsGamepadButtonDown(input.Code, input.Device)
		elseif input.Type == InputType.GamepadDirection then
			if input.Code == DirectionMethod.None then
				inputValue = false
			elseif input.Code == DirectionMethod.Absolute then
				inputValue = getDeviceInput(input.Code, input.Device).Position
			elseif input.Code == DirectionMethod.Relative then
				inputValue = getDeviceInput(input.Code, input.Device).Delta
			else
				Console.error("Input has invalid direction method.")
				valid = false
			end
		elseif input.Type == InputType.Scheme then
			inputValue = input.Code
		else
			Console.error("Input has invalid type.")
			valid = false
		end
		
		if valid then
			inputValue, inputScale = self:transformValue(input, inputValue)
			value = self:addValue(value, inputValue)
			scale = scale + inputScale
		end
	end
	
	self.value = self:scaleValue(value, scale)
	return self:GetValue()
end

--- Binds a Control to this Monitor. 
-- Only one Control can be bound at a time. At the time of binding all Inputs
-- of the Control are processed and added to the Monitor. Any changes to the
-- Control after its bound will not be reflected in the Monitor's operation.
--
-- @param control the Control to bind
function Monitor:Bind(control)
	if not self:Bound() then
		for _, input in ipairs(control.inputs) do
			local entry = {}
			entry.Type = input.Type
			entry.Code = input.Code
			entry.Offset = input.Offset
			entry.Devices = input.Devices
			self:processEntry(entry)
			table.insert(self.inputs, entry)
		end
		self.bound = true
	end
end

--- Unbinds this Monitor from any Control it may be bound to.
-- Automatically resets this Monitor.
function Monitor:Unbind()
	if self:Bound() then
		self.inputs = {}
		self:clean()
		self.bound = false
	end
end

--- Transforms an Input's value for this Monitor based on its InputType.
-- The Monitor, InputType, and offset of the Input are used to transform the
-- value accordingly during the transform step of this Monitor's update.
-- Typically this means converting the value to the appropriate type and'
-- applying the offset accordingly. During this process a scaling factor may be
-- produced. This should not be called from the abstract class Monitor and
-- should be overridden. This should typically not be called directly and
-- should be called by the associated Monitor's update.
--
-- @param input the input that produced the value being transformed
-- @param value the value being transformed
-- @return the transformed value
-- @return the scaling factor
function Monitor:transformValue()
	Console.warn("updateInput must be overridden and should not be called from Monitor.")
end

--- Adds two Inputs' values together based on this Monitor type.
-- Occurs during the accumulation step of this Monitor's update. This should
-- not be called from the abstract class Monitor and should be overridden. This
-- should typically not be called directly and should be called by the
-- associated Monitor's update.
--
-- @param lhs the left hand side operand of the add operation
-- @param rhs the right hand side operand of the add operation
-- @return the added value
function Monitor:addValue()
	Console.warn("addValue must be overridden and should not be called from Monitor.")
end

--- Scales an Input's value by a scaling factor.
-- Occurs during the scaling step of this Monitor's update. The scaling factor
-- is the same type of scaling factor returned by transformValue. This should
-- not be called from the abstract class Monitor and should be overridden. This
-- should typically not be called directly and should be called by the
-- associated Monitor's update.
--
-- @param value the value to be scaled
-- @param scale the scaling factor
-- @return the scaled value
function Monitor:scaleValue()
	Console.warn("scaleValue must be overridden and should not be called from Monitor.")
end

--- Gets the default value for this Monitor.
-- Occurs during the initialization step of this Monitor's update.
--
-- @return the default value
function Monitor:defaultValue()
	Console.warn("defaultValue must be overridden and should not be called from Monitor.")
end

--- Performs any necessary post-processing of an entry for this Monitor.
-- Occurs when new entries are added to this Monitor during the binding step.
-- The monitor may modify or add any values to the entry. An entry has fields
-- Type, Code, Offset, and, Devices, from its associated Input.
--
-- @param entry the entry to process
function Monitor:processEntry()
	Console.warn("processEntry must be overridden and should not be called from Monitor.")
end

--- Cleans up any additional data or state created by this Monitor.
-- Occurs during this Monitor's unbind step. Leaves this Monitor in a state
-- ready to be bound again.
function Monitor:clean()
	Console.warn("clean must be overridden and should not be called from Monitor.")
end

-- End --
Console.log("Done.")

return Monitor.new()
