using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

namespace Passer {
    using Humanoid;

    /// <summary>A unified method for using controller input</summary>
    /// ControllerInput can be used to access the state and change of input buttons, touchpads and thumbsticks
    /// on any kind of input controller including game controllers and VR/XR controllers.
    /// \image html ControllerInputInspector.png
    /// \image rtf ControllerInputInspector.png
    /// * \ref ControllerInput::fingerMovements "Finger Movements"
    /// * \ref ControllerInput::gameController "Game Controller"
    /// 
    /// %Controllers
    /// -----------
    /// You can assign controller buttons to certain functions.
    /// %Controller input is split into a left and right side.
    /// For some controllers, this corresponds to the left or right controller
    /// (e.g. SteamVR or Oculus Touch controllers).
    /// For game controllers like the Xbox controller, this corresponds to the left and right side of the gamepad.
    /// Event input is using an EventHandler to define its behaviour.
    [HelpURL("https://passervr.com/apis/HumanoidControl/Unity/class_passer_1_1_controller_input.html")]
    public class ControllerInput : MonoBehaviour {

        public enum SideButton {
            StickVertical,
            StickHorizontal,
            StickButton,
            TouchpadVertical,
            TouchpadHorizontal,
            TouchpadButton,
            Button1,
            Button2,
            Button3,
            Button4,
            Trigger1,
            Trigger2,
            Option,
        }

        public HumanoidControl humanoid;
        protected HandTarget leftHandTarget;
        protected HandTarget rightHandTarget;

        /// <summary>
        /// Enables a built-in support for finger movements from the controller buttons.
        /// </summary>
        ///  This option is only available when 
        ///  the Controller Input Component is attached to an \ref HumanoidControl "Humanoid".
        public bool fingerMovements = true;

        /// <summary>
        /// Selects which controller type is showed in the Inspector.
        /// </summary>
        /// his setting has no effect on the working of the Controller Input.
        /// It helps to determine how the actions are assigned to buttons or various supported controllers.
        public GameControllers gameController;

        //private TraditionalDevice traditionalInput = new TraditionalDevice();

        public float leftStickVertical { get { return controller.left.stickVertical; } }
        public float leftStickHorizontal { get { return controller.left.stickHorizontal; } }
        public bool leftStickPressed { get { return controller.left.stickButton; } }

        public float leftTouchpadVertical { get { return controller.left.touchpadVertical; } }
        public float leftTouchpadHorizontal { get { return controller.left.touchpadHorizontal; } }
        public bool leftTouchpadPressed { get { return controller.left.touchpadPress; } }

        public float leftTrigger1 { get { return controller.left.trigger1; } }
        public bool leftTrigger1Touched { get { return controller.left.trigger1 > 0; } }
        public bool leftTrigger1Pressed { get { return controller.left.trigger1 > 0.8F; } }
        public float leftTrigger2 { get { return controller.left.trigger2; } }
        public bool leftTrigger2Touched { get { return controller.left.trigger2 > 0; } }
        public bool leftTrigger2Pressed { get { return controller.left.trigger2 > 0.8F; } }
        public bool leftButton1Pressed { get { return controller.left.buttons[0]; } }
        public bool leftButton2Pressed { get { return controller.left.buttons[1]; } }
        public bool leftButton3Pressed { get { return controller.left.buttons[2]; } }
        public bool leftButton4Pressed { get { return controller.left.buttons[3]; } }
        public bool leftOptionPressed { get { return controller.left.option; } }


        public float rightStickVertical { get { return controller.right.stickVertical; } }
        public float rightStickHorizontal { get { return controller.right.stickHorizontal; } }
        public bool rightStickPressed { get { return controller.right.stickButton; } }

        public float rightTouchpadVertical { get { return controller.right.touchpadVertical; } }
        public float rightTouchpadHorizontal { get { return controller.right.touchpadHorizontal; } }
        public bool rightTouchpadPressed { get { return controller.right.touchpadPress; } }

        public float rightTrigger1 { get { return controller.right.trigger1; } }
        public bool rightTrigger1Touched { get { return controller.right.trigger1 > 0; } }
        public bool rightTrigger1Pressed { get { return controller.right.trigger1 > 0.8F; } }
        public float rightTrigger2 { get { return controller.right.trigger2; } }
        public bool rightTrigger2Touched { get { return controller.right.trigger2 > 0; } }
        public bool rightTrigger2Pressed { get { return controller.right.trigger2 > 0.8F; } }
        public bool rightButton1Pressed { get { return controller.right.buttons[0]; } }
        public bool rightButton2Pressed { get { return controller.right.buttons[1]; } }
        public bool rightButton3Pressed { get { return controller.right.buttons[2]; } }
        public bool rightButton4Pressed { get { return controller.right.buttons[3]; } }
        public bool rightOptionPressed { get { return controller.left.option; } }

        protected static string[] eventTypeLabels = new string[] {
                "Never",
                "On Press",
                "On Release",
                "While Down",
                "While Up",
                "On Change",
                "Continuous"
        };

        public ControllerEventHandlers[] leftInputEvents;
        public void InitializeLeftInputEvents() {
            leftInputEvents = new ControllerEventHandlers[] {
                new ControllerEventHandlers() { label = "Left Vertical", id = 0, defaultParameterProperty = "leftStickVertical" },
                new ControllerEventHandlers() { label = "Left Horizontal", id = 1, defaultParameterProperty = "leftStickHorizontal" },
                new ControllerEventHandlers() { label = "Left Stick Button", id = 2, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Touchpad Vertical", id = 3, defaultParameterProperty = "leftTouchpadVertical" },
                new ControllerEventHandlers() { label = "Left Touchpad Horizontal", id = 4, defaultParameterProperty = "leftTouchpadHorizontal" },
                new ControllerEventHandlers() { label = "Left Touchpad Button", id = 5, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Button 1", id = 6, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Button 2", id = 7, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Button 3", id = 8, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Button 4", id = 9, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Trigger 1", id = 10, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Trigger 2", id = 11, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Left Option", id = 12, eventTypeLabels = eventTypeLabels },
            };
        }
        public ControllerEventHandlers[] rightInputEvents;
        public void InitializeRightInputEvents() {
            rightInputEvents = new ControllerEventHandlers[] {
                new ControllerEventHandlers() { label = "Right Vertical", id = 0, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Horizontal", id = 1, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Stick Button", id = 2, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Touchpad Vertical", id = 3, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Touchpad Horizontal", id = 4, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Touchpad Button", id = 5, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Button 1", id = 6, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Button 2", id = 7, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Button 3", id = 8, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Button 4", id = 9, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Trigger 1", id = 10, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Trigger 2", id = 11, eventTypeLabels = eventTypeLabels },
                new ControllerEventHandlers() { label = "Right Option", id = 12, eventTypeLabels = eventTypeLabels },
            };
        }

        protected Controller controller;

        public ControllerEventHandlers GetInputEvent(bool isLeft, SideButton controllerButton) {
            if (isLeft)
                return leftInputEvents[(int)controllerButton];
            else
                return rightInputEvents[(int)controllerButton];
        }

        protected virtual void Awake() {
            if (leftInputEvents == null)
                InitializeLeftInputEvents();

            humanoid = GetComponent<HumanoidControl>();
            if (humanoid != null) {
                leftHandTarget = humanoid.leftHandTarget;
                rightHandTarget = humanoid.rightHandTarget;
            }
            HumanoidControl.onNewHumanoid += HumanoidControl_onNewHumanoid;
        }

        private void HumanoidControl_onNewHumanoid(HumanoidControl newHumanoid) {
            if (humanoid == null && newHumanoid.isRemote == false) {
                humanoid = newHumanoid;
                leftHandTarget = newHumanoid.leftHandTarget;
                rightHandTarget = newHumanoid.rightHandTarget;
            }
        }

        protected virtual void OnDestroy() {
        }

        protected virtual void Start() {
            controller = Controllers.GetController(0);
        }

        protected virtual void Update() {
            if (leftInputEvents.Length < 13) {
                UpdateInputList(leftInputEvents[0], Mathf.Clamp(controller.left.stickVertical + controller.left.touchpadVertical, -1, 1));
                UpdateInputList(leftInputEvents[1], Mathf.Clamp(controller.left.stickHorizontal + controller.left.touchpadHorizontal, -1, 1));
                UpdateInputList(leftInputEvents[2], Mathf.Clamp(
                    (controller.left.stickTouch ? 0 : -1) + (controller.left.stickButton ? 1 : 0) +
                    (controller.left.touchpadTouch ? 0 : -1) + (controller.left.touchpadPress ? 1 : 0),
                    -1, 1));
                UpdateInputList(leftInputEvents[3], controller.left.buttons[0] ? 1 : 0);
                UpdateInputList(leftInputEvents[4], controller.left.buttons[1] ? 1 : 0);
                UpdateInputList(leftInputEvents[5], controller.left.buttons[2] ? 1 : 0);
                UpdateInputList(leftInputEvents[6], controller.left.buttons[3] ? 1 : 0);
                UpdateInputList(leftInputEvents[7], controller.left.trigger1);
                UpdateInputList(leftInputEvents[8], controller.left.trigger2);
                UpdateInputList(leftInputEvents[9], controller.left.option ? 1 : 0);
            }
            else {
                UpdateInputList(leftInputEvents[0], controller.left.stickVertical);
                UpdateInputList(leftInputEvents[1], controller.left.stickHorizontal);
                UpdateInputList(leftInputEvents[2], (controller.left.stickTouch ? 0 : -1) + (controller.left.stickButton ? 1 : 0));
                UpdateInputList(leftInputEvents[3], controller.left.touchpadVertical);
                UpdateInputList(leftInputEvents[4], controller.left.touchpadHorizontal);
                UpdateInputList(leftInputEvents[5], (controller.left.touchpadTouch ? 0 : -1) + (controller.left.touchpadPress ? 1 : 0));
                UpdateInputList(leftInputEvents[6], controller.left.buttons[0] ? 1 : 0);
                UpdateInputList(leftInputEvents[7], controller.left.buttons[1] ? 1 : 0);
                UpdateInputList(leftInputEvents[8], controller.left.buttons[2] ? 1 : 0);
                UpdateInputList(leftInputEvents[9], controller.left.buttons[3] ? 1 : 0);
                UpdateInputList(leftInputEvents[10], controller.left.trigger1);
                UpdateInputList(leftInputEvents[11], controller.left.trigger2);
                UpdateInputList(leftInputEvents[12], controller.left.option ? 1 : 0);
            }

            if (rightInputEvents.Length < 13) {
                UpdateInputList(rightInputEvents[0], Mathf.Clamp(controller.right.stickVertical + controller.right.touchpadVertical, -1, 1));
                UpdateInputList(rightInputEvents[1], Mathf.Clamp(controller.right.stickHorizontal + controller.right.touchpadHorizontal, -1, 1));
                UpdateInputList(rightInputEvents[2], Mathf.Clamp(
                    (controller.right.stickTouch ? 0 : -1) + (controller.right.stickButton ? 1 : 0) +
                    (controller.right.touchpadTouch ? 0 : -1) + (controller.right.touchpadPress ? 1 : 0),
                    -1, 1));
                UpdateInputList(rightInputEvents[3], controller.right.buttons[0] ? 1 : 0);
                UpdateInputList(rightInputEvents[4], controller.right.buttons[1] ? 1 : 0);
                UpdateInputList(rightInputEvents[5], controller.right.buttons[2] ? 1 : 0);
                UpdateInputList(rightInputEvents[6], controller.right.buttons[3] ? 1 : 0);
                UpdateInputList(rightInputEvents[7], controller.right.trigger1);
                UpdateInputList(rightInputEvents[8], controller.right.trigger2);
                UpdateInputList(rightInputEvents[9], controller.right.option ? 1 : 0);
            }
            else {
                UpdateInputList(rightInputEvents[0], controller.right.stickVertical);
                UpdateInputList(rightInputEvents[1], controller.right.stickHorizontal);
                UpdateInputList(rightInputEvents[2], (controller.right.stickTouch ? 0 : -1) + (controller.right.stickButton ? 1 : 0));
                UpdateInputList(rightInputEvents[3], controller.right.touchpadVertical);
                UpdateInputList(rightInputEvents[4], controller.right.touchpadHorizontal);
                UpdateInputList(rightInputEvents[5], (controller.right.touchpadTouch ? 0 : -1) + (controller.right.touchpadPress ? 1 : 0));
                UpdateInputList(rightInputEvents[6], controller.right.buttons[0] ? 1 : 0);
                UpdateInputList(rightInputEvents[7], controller.right.buttons[1] ? 1 : 0);
                UpdateInputList(rightInputEvents[8], controller.right.buttons[2] ? 1 : 0);
                UpdateInputList(rightInputEvents[9], controller.right.buttons[3] ? 1 : 0);
                UpdateInputList(rightInputEvents[10], controller.right.trigger1);
                UpdateInputList(rightInputEvents[11], controller.right.trigger2);
                UpdateInputList(rightInputEvents[12], controller.right.option ? 1 : 0);
            }

            if (fingerMovements)
                UpdateFingerMovements();
        }

        protected void UpdateInputList(ControllerEventHandlers inputEventList, float value) {
            for (int i = 0; i < inputEventList.events.Count; i++)
                inputEventList.events[i].floatValue = value;
        }

        public void SetEventHandler(bool isLeft, SideButton sideButton, UnityAction<bool> boolEvent) {
            ControllerEventHandlers eventHandlers = GetInputEvent(isLeft, sideButton);
            SetBoolMethod(gameObject, eventHandlers, EventHandler.Type.OnChange, boolEvent);

        }

        public static void SetBoolMethod(GameObject gameObject, ControllerEventHandlers eventHandlers, EventHandler.Type eventType, UnityAction<bool> boolEvent) {
            if (boolEvent == null)
                return;

            Object target = (Object)boolEvent.Target;
            string methodName = boolEvent.Method.Name;
            methodName = target.GetType().Name + "/" + methodName;

            if (eventHandlers.events == null || eventHandlers.events.Count == 0)
                eventHandlers.events.Add(new ControllerEventHandler(gameObject, eventType));
            else
                eventHandlers.events[0].eventType = eventType;

            ControllerEventHandler eventHandler = eventHandlers.events[0];
            eventHandler.functionCall.targetGameObject = FunctionCall.GetGameObject(target);
            eventHandler.functionCall.methodName = methodName;
            eventHandler.functionCall.AddParameter();
            FunctionCall.Parameter parameter = eventHandler.functionCall.AddParameter();
            parameter.type = FunctionCall.ParameterType.Bool;
            parameter.localProperty = "From Event";
            parameter.fromEvent = true;
        }

        protected void UpdateFingerMovements() {
            if (leftHandTarget != null)
                UpdateFingerMovementsSide(leftHandTarget.fingers, controller.left);
            if (rightHandTarget != null)
                UpdateFingerMovementsSide(rightHandTarget.fingers, controller.right);
        }

        protected void UpdateFingerMovementsSide(FingersTarget fingers, ControllerSide controllerSide) {
            //float thumbCurl = Mathf.Max(controllerSide.trigger2, controllerSide.trigger1);
            //fingers.thumb.curl = controllerSide.stickTouch ? thumbCurl + 0.3F : thumbCurl;
            //fingers.index.curl = controllerSide.trigger1;
            //fingers.middle.curl = Mathf.Max(controllerSide.trigger2, controllerSide.trigger1);
            //fingers.ring.curl = Mathf.Max(controllerSide.trigger2, controllerSide.trigger1);
            //fingers.little.curl = Mathf.Max(controllerSide.trigger2, controllerSide.trigger1);

            float thumbCurl = Mathf.Max(controllerSide.trigger2, controllerSide.trigger1);
            fingers.thumb.curl = !controllerSide.stickTouch ? -0.5F : thumbCurl;

            float indexValue = controllerSide.trigger1;
            SetFingerCurl(fingers.index, indexValue);

            float fingersValue = Mathf.Max(controllerSide.trigger2, controllerSide.trigger1);
            SetFingerCurl(fingers.middle, fingersValue);
            SetFingerCurl(fingers.ring, fingersValue);
            SetFingerCurl(fingers.little, fingersValue);
        }

        private void SetFingerCurl(FingersTarget.TargetedFinger finger, float inputValue) {
            if (inputValue < 0)
                finger.curl = 0.1F * inputValue;
            else
                finger.curl = inputValue;
        }

        public void SetRightTrigger1Value(float value) {
            rightInputEvents[3].floatValue = value;
            rightInputEvents[7].floatValue = value;
        }
    }
}