class PlayerInput
{
	constructor(name)
	{
		if (name !== undefined)
		{
			this.name = name;
		} else {
			this.name = "unnnamed";
		}

		this.inputs = 1;

		this.state = false;
		this.lastState = false;
		this.value = 0;
		this.lastValue = 0;
		this.hasChanged = false;
		this.isPressed = false;
		this.justPressed = false;
		this.justReleased = false;
		this.lowerDeadZone = -0.15;
		this.upperDeadZone = 0.15;
		this.stateThreshold = 0.1;
	}

	updateState(newValue /* value of 0 - 1 */)
	{
		if (newValue > this.lowerDeadZone && newValue < this.upperDeadZone) newValue = 0;

		this.lastState = this.state;
		this.state = Math.abs(newValue) > this.stateThreshold;
		
		this.hasChanged = this.lastValue != newValue;

		this.lastValue = this.value;
		this.value = newValue;

		//was pressed on the last frame, is not now.
		this.justReleased = this.isPressed && !this.state;

		this.justPressed = !this.isPressed && this.state;
		this.isPressed = this.state;

		if (this.justReleased){
			console.log(this.name + "was released" + " is pressed is "+this.isPressed)
		}
	}
}

class PlayerInput2D extends PlayerInput
{
	constructor(name)
	{
		super(name);
		this.inputs = 2;
		this.x = 0;
		this.y = 0;
	}

	updateState(xValue, yValue)
	{
		if (xValue > this.lowerDeadZone && xValue < this.upperDeadZone) xValue = 0;
		if (yValue > this.lowerDeadZone && yValue < this.upperDeadZone) yValue = 0;

		this.lastState = this.state;
		this.state = Math.abs(xValue) > this.stateThreshold && Math.abs(yValue) > this.stateThreshold;
		
		this.hasChanged = this.lastX != xValue || this.lastY != yValue;

		this.lastX = this.x;
		this.x = xValue;

		this.lastY = this.y;
		this.y = yValue;

		//was pressed on the last frame, is not now.
		this.justReleased = this.isPressed && !this.state;

		this.justPressed = !this.isPressed && this.state;
		this.isPressed = this.state;
	}

}


class KeyboardInputBinding
{
	constructor(playerInput, key, reverseKey)
	{
		this.playerInput = playerInput;
		this.key = key;
		this.reverseKey = reverseKey;

		this.isTwoAxis = false;
	}

	setTwoAxis(keyX, reverseKeyX, keyY, reverseKeyY)
	{
		this.isTwoAxis = true;
		this.keyX = keyX;
		this.reverseKeyX = reverseKeyX;
		this.keyY = keyY;
		this.reverseKeyY = reverseKeyY;
	}

}

class GamepadInputBinding
{
	constructor(playerInput, button, isAxis)
	{
		this.playerInput = playerInput;
		this.button = button;

		this.isTwoAxis = false;

		if (isAxis === undefined) isAxis = false;
		this.isAxis = isAxis;
	}

	setTwoAxis(axisX, axisY)
	{
		this.isTwoAxis = true;
		this.axisX = axisX;
		this.axisY = axisY;
	}

}

class InputBindingSet
{
	constructor()
	{
		this.bindings = [];
		this.bindingsByName = {};
		this.activeBindingsByName = {};
		this.hasChanged = false;
	}

	getInput(name)
	{
		if (this.bindingsByName[name] !== undefined)
		{
			var activeBinding = this.activeBindingsByName[name];
			for (var i = 0; i < this.bindingsByName[name].length; i++)
			{
				if (this.bindingsByName[name][i].playerInput.hasChanged)
				{
					this.activeBindingsByName[name] = this.bindingsByName[name][i];
				}
			}

			if (activeBinding === undefined) activeBinding = this.bindingsByName[name][0];
			return activeBinding.playerInput;
		}

		return null;
	}

	addBinding(binding)
	{
		this.bindings.push(binding);

		if (this.bindingsByName[binding.playerInput.name] === undefined)
		{
			this.bindingsByName[binding.playerInput.name] = [];
		}
		var bindings = this.bindingsByName[binding.playerInput.name];
		if (bindings.indexOf(binding) == -1)
		{
			bindings.push(binding);
		}
	}

	update()
	{
		//impliment in extended class
	}

}

class KeyboardInputBindingSet extends InputBindingSet
{

	constructor()
	{
		super();
	}

	createInputBinding(name, key, reverseKey)
	{
		var input = new PlayerInput(name);
		var binding = this.createBinding(input, key, reverseKey);
		return binding;
	}

	createTwoAxisInputBinding(name, keyX, reverseKeyX, keyY, reverseKeyY)
	{
		var input = new PlayerInput2D(name);
		var binding = this.createBinding(input, keyX, reverseKeyX);
		binding.setTwoAxis(keyX, reverseKeyX, keyY, reverseKeyY);
		return binding;
	}

	createBinding(playerInput, key, reverseKey)
	{
		var newBinding = new KeyboardInputBinding(playerInput, key, reverseKey);
		this.addBinding(newBinding);
		return newBinding;
	}

	update()
	{
		this.hasChanged = false;
		var keyStates = KeyboardManager.keyStates;

		for (var i = 0; i < this.bindings.length; i++)
		{
			if (this.bindings[i].isTwoAxis)
			{
				this.bindings[i].playerInput.updateState(keyStates[this.bindings[i].keyX] - keyStates[this.bindings[i].reverseKeyX], keyStates[this.bindings[i].keyY] - keyStates[this.bindings[i].reverseKeyY]);
			} 
			else if (this.bindings[i].reverseKey)
			{
				this.bindings[i].playerInput.updateState(keyStates[this.bindings[i].key] - keyStates[this.bindings[i].reverseKey]);
			} else {
				this.bindings[i].playerInput.updateState(keyStates[this.bindings[i].key]);
			}

			if (this.bindings[i].playerInput.hasChanged) this.hasChanged = true;
		}
	}
}

class GamepadInputBindingSet extends InputBindingSet
{

	constructor(gamePadIndex)
	{
		super();
		this.gamePadIndex = gamePadIndex;
	}

	createInputBinding(name, button, isAxis)
	{
		var input = new PlayerInput(name);
		var binding = this.createBinding(input, button, isAxis);
		return binding;
	}

	createTwoAxisInputBinding(name, axisX, axisY)
	{
		var input = new PlayerInput2D(name);
		var binding = this.createBinding(input, axisX, true);
		binding.setTwoAxis(axisX, axisY);
		return binding;
	}

	createBinding(playerInput, button, isAxis)
	{
		var newBinding = new GamepadInputBinding(playerInput, button, isAxis);
		this.addBinding(newBinding);
		return newBinding;
	}

	update()
	{
		this.hasChanged = false;
		GamepadManager.scangamepads();
		var controller = GamepadManager.controllers[this.gamePadIndex];
		if (controller === undefined || controller == null) return;

		for (var i = 0; i < this.bindings.length; i++)
		{
			if (!this.bindings[i].isAxis)
			{
				var val = controller.buttons[this.bindings[i].button];
				var pressed = val == 1.0;
                if (typeof(val) == "object")
                {
                    pressed = val.pressed;
                    val = val.value;
                }

                this.bindings[i].playerInput.updateState(val);

			} else {

				if (this.bindings[i].isTwoAxis)
				{
					this.bindings[i].playerInput.updateState(controller.axes[this.bindings[i].axisX], controller.axes[this.bindings[i].axisY]);
				} 
				else
				{
					this.bindings[i].playerInput.updateState(controller.axes[this.bindings[i].button]);
				}
			}

			if (this.bindings[i].playerInput.hasChanged) this.hasChanged = true;
		}

	}

}
