using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

namespace Passer {
    using Humanoid;

    /// <summary> The Teleport Target provides control to where a player can teleport and can be used in combination with a generic Interaction Pointer. </summary>    
    /// The Teleport Target can be placed on a static or moving Rigidbody object with a collider.
    /// It has been implemented as a Event Trigger and will teleport when an PointerDown event is received.
    [HelpURLAttribute("https://passervr.com/documentation/humanoid-control/tools/teleport-target/")]
    public class TeleportTarget : EventTrigger {
        public enum TargetPosRot {
            Pointer,
            Transform
        }
        /// <summary>Teleport the root transform instead of the transform itself.</summary>
        /// If this is enabled the root of the transform (the topmost transform)
        /// will be teleported instead of the transform itself.
        public bool teleportRoot = false;

        /// <summary>Check the target location for collisions.</summary>
        /// if enabled this will check if the location to which the pointer is pointing contains a collider.
        /// Teleporting will only take place if no collider has been found.
        /// The check is executed using a capsule of 2 meters/units high and 0.2 meters/units radius.
        public bool checkCollision = true;

        /// <summary>The type of movement used to reacht the teleport target.</summary> 
        ///  determines how the Transform is moved to the Target Point.
        /// Teleport = direct placement a the target point.
        /// Dash = a quick movement is a short time from the originating point to the target point.
        public MovementType movementType;

        /// <summary>Determines the target position and rotation.</summary>
        /// TargetPosRot.Pointer = The interaction pointer location is the target position and rotation.
        /// TargetPosRot.Transform = The targetTransform determines the target position and rototation.
        public TargetPosRot targetPosRot = TargetPosRot.Pointer;

        /// <summary>The target transform for the teleport.</summary> 
        /// If targetPosRot is set to TargetPosRot.Transform the targetTransform will determine the 
        /// position and rotation of the transform to teleport.
        /// Next to that, the teleported transform will become a child of this teleportTarget after teleporting
        public Transform targetTransform;

        /// <summary>The pose of a humanoid after it has been teleported.</summary>
        /// This is an optional Pose of the Humanoid after it has been teleported.
        /// This enables you to teleport to a different pose like a seating pose for instance.
        public Pose pose;

        /// <summary>Enables humanoid animators after teleporting.</summary>
        /// This will enable or disable the hips and foot animator after teleporting.
        /// For poses like seating a walking foot animation is not required.
        /// In such cases the foot animator can be switch off with this setting.
        public bool enableAnimators = true;

        /// <summary>Teleport the transform to this Teleport Target.</summary>
        /// This function will teleport the given transform to this teleport target using the
        /// teleport target settings.
        /// If the targetPosRot is set to TargetPosRot.Pointer, the transform will be transported
        /// using the transform of the gameObject as there is no pointer information.
        /// <param name="t">The transform to teleport</param>
        public void TeleportToHere(Transform t) {
            if (targetPosRot == TargetPosRot.Transform && targetTransform != null)
                Teleport(t, targetTransform.position, targetTransform.rotation, movementType, targetTransform);
            else
                Teleport(t, gameObject.transform.position, gameObject.transform.rotation, movementType, null);
        }

        /// <summary>Teleport the Humanoid to this Teleport Target.</summary>
        /// This function will teleport the given humanoid to this teleport target using the
        /// teleport target settings.
        /// If the targetPosRot is set to TargetPosRot.Pointer, the humanoid will be transported
        /// using the transform of the gameObject as there is no pointer information.
        /// <param name="humanoid">The humanoid to teleport</param>
        public void TeleportToHere(HumanoidControl humanoid) {
            if (targetPosRot == TargetPosRot.Transform && targetTransform != null)
                Teleport(humanoid.transform, targetTransform.position, targetTransform.rotation, movementType, targetTransform);
            else
                Teleport(humanoid.transform, gameObject.transform.position, gameObject.transform.rotation, movementType);
        }

        /// <summary>Teleporting initialized by an Unity UI Event.</summary>
        public override void OnPointerDown(PointerEventData eventData) {
            Vector3 pointingPosition = eventData.pointerCurrentRaycast.worldPosition;

            if (IsValid(pointingPosition) && eventData.currentInputModule != null) {
                GameObject originator = eventData.currentInputModule.gameObject;

                Transform teleportingTransform = originator.transform;
                HumanoidControl humanoid = originator.transform.GetComponentInParent<HumanoidControl>();
                if (humanoid != null) {
                    teleportingTransform = humanoid.transform;

                    humanoid.hipsTarget.torsoAnimator.enabled = enableAnimators;
                    humanoid.leftFootTarget.legAnimator.enabled = enableAnimators;
                    humanoid.rightFootTarget.legAnimator.enabled = enableAnimators;
                }

                if (targetPosRot == TargetPosRot.Pointer)
                    Teleport(teleportingTransform, pointingPosition, originator.transform.rotation, movementType, null);
                else if (targetTransform != null)
                    Teleport(teleportingTransform, targetTransform.position, targetTransform.rotation, movementType, targetTransform);
                else
                    Teleport(teleportingTransform, transform.position, transform.rotation, movementType, targetTransform);

                if (humanoid != null) {
                    humanoid.pose = pose;
                    if (pose != null) {
                        float oldNeckHeight = humanoid.headTarget.head.target.transform.position.y;
                        humanoid.pose.Show(humanoid);
                        float deltaNeckHeight = humanoid.headTarget.head.target.transform.position.y - oldNeckHeight;
                        humanoid.AdjustTracking(new Vector3(0, deltaNeckHeight, 0));
                        humanoid.CopyRigToTargets();
                    }
                }
            }

            base.OnPointerDown(eventData);
        }

        /// <summary>Teleport the transform to a position and rotation.</summary>
        /// This function will teleport the given transform to this teleport target using the
        /// teleport target settings.
        /// If the targetPosRot is set to TargetPosRot.Pointer, the transform will be transported
        /// using the transform of the gameObject as there is no pointer information.
        /// <param name="transform">The transform to teleport</param>
        /// <param name="position">The new world position of the transform after teleporting</param>
        /// <param name="rotation">The new world rotation of the transform after teleporting</param>
        /// <param name="movementType">The movement type to use to get to the target</param>
        /// <param name="newParent">The new parent of the transform after teleporting</param>
        protected virtual void Teleport(Transform transform, Vector3 position, Quaternion rotation, MovementType movementType = MovementType.Teleport, Transform newParent = null) {
            if (newParent != null && transform.parent == newParent)
                // The transform is already at the teleport target, do not teleport again
                return;

            Vector3 originPosition = transform.position;

            Quaternion humanoidRotation = Quaternion.identity;
            HumanoidControl humanoid = transform.GetComponent<HumanoidControl>();
            if (humanoid != null) {
                Vector3 humanoidPosition = humanoid.GetHumanoidPosition();
                Vector3 deltaHumanoid = humanoidPosition - transform.position;

                Vector3 humanoidForward = humanoid.hipsTarget.transform.forward;
                humanoidForward.y = 0;
                humanoidRotation = Quaternion.LookRotation(humanoidForward, humanoid.up);

                GameObject realWorld = HumanoidControl.GetRealWorld(humanoid.transform);
                realWorld.transform.position -= deltaHumanoid;
                realWorld.transform.rotation *= Quaternion.Inverse(humanoidRotation);
            }

            if (teleportRoot) {
                Vector3 translation = position - originPosition; // World coordinates

                transform = transform.root;

                position = transform.position + translation;
            }
            Debug.Log("TeleportRoot: " + teleportRoot + ", teleporting " + transform.name);
            transform.MoveTo(position, movementType);
            transform.rotation = rotation;
            transform.SetParent(newParent, true);
        }

        /// <summary>Check is the target position is valid.</summary>
        protected virtual bool IsValid(Vector3 position) {
            if (!checkCollision)
                return true;

            Vector3 start = position + new Vector3(0, 0.3F, 0);
            Vector3 end = position + new Vector3(0, 1.8F, 0);
            bool isOccupied = Physics.CheckCapsule(start, end, 0.2F);
            return (!isOccupied);
        }

        public UnityEvent unityEvents;
    }
}