// 04.10.2021 11:00

using System;
using System.Threading.Tasks;
using UnityEngine;

public class ThresholdedPositioner : MonoBehaviour
{

    #region Inspector Properties

    [Header("Config Values")]
    [Tooltip("Sets the desired position relative to target.")]
    [SerializeField]
    private Vector3 offsetToTarget;

    [Tooltip("Should the position be fixed on height of the camera?")]
    [SerializeField]
    private bool keepOnCamHeight;

    [Tooltip("Specifies the desired OnEnable behaviour.")]
    [SerializeField]
    private EEnablingBehaviour enablingBehaviour = EEnablingBehaviour.Transition;

    [Tooltip("Specifies the delay in seconds before initializing.")]
    [SerializeField]
    private float initDelay = 0.03f;

    [Tooltip("Specifies how fast the object will move.")]
    [SerializeField]
    private float movingSpeed = 4f;

    [Tooltip("Distance from desiredPos which will start movement.")]
    [SerializeField]
    private float startMovingDistance = 0.2f;

    [Tooltip("Distance to desiredPos which will stop movement.")]
    [SerializeField]
    private float stopMovingDistance = 0.02f;

    [Tooltip("If keepAwayFromTransform is set: Minimal distance to keepAwayFromTransform.")]
    [SerializeField]
    private float keepAwayDistance = 0.5f;

    [Header("Scene Objects")]
    [Tooltip("Uses camera if not set")]
    [SerializeField]
    private Transform target;

    [Tooltip("Stays away from this transform")]
    [SerializeField]
    private Transform keepAwayTransform;

    #endregion

    #region Public Properties

    #endregion

    #region Private Properties

    private Camera cam;

    private bool isInitialized = false;

    private bool isMoving = false;

    #endregion

    #region Framework Functions

    void Awake()
    {
        this.cam = Camera.main;
    }

    void OnEnable()
    {
        this.initPositionerWithDelay();
    }

    void Update()
    {
        if (this.isInitialized)
        {
            this.updateIsMoving();

            if (this.isMoving)
            {
                this.updatePosition();
            }

            if (this.keepAwayTransform != null)
            {
                this.ensureMinDistanceToKeepAwayTransform();
            }
        }
    }

    void OnDisable()
    {
        this.isInitialized = false;
    }

    #endregion

    #region Events

    #endregion

    #region Public Functions

    public void OverrideEnablingBehaviour(EEnablingBehaviour newBehaviour)
    {
        this.enablingBehaviour = newBehaviour;
    }

    public void OverrideStartMovingDistance(float newStartMovingDistance)
    {
        this.startMovingDistance = newStartMovingDistance;
    }

    public void OverrideStopMovingDistance(float newStopMovingDistance)
    {
        this.stopMovingDistance = newStopMovingDistance;
    }

    public void OverrideMovingSpeed(float newMovingSpeed)
    {
        this.movingSpeed = newMovingSpeed;
    }

    public void OverrideKeepAwayFromTransform(Transform _keepAwayTransform, float _keepAwayDistance)
    {
        this.keepAwayTransform = _keepAwayTransform;
        this.keepAwayDistance = _keepAwayDistance;
    }

    public void JumpToDesiredPosition()
    {
        this.transform.position = this.getDesiredPosition();
    }

    #endregion

    #region Private Functions

    private async void initPositionerWithDelay()
    {
        await Task.Delay(TimeSpan.FromSeconds(this.initDelay));

        if (this.enablingBehaviour == EEnablingBehaviour.Transition)
        {
            this.isMoving = true;
        }

        if (this.enablingBehaviour == EEnablingBehaviour.Hold)
        {
            this.isMoving = false;
        }

        if (this.enablingBehaviour == EEnablingBehaviour.Jump)
        {
            this.transform.position = this.getDesiredPosition();
        }

        this.isInitialized = true;
    }

    private Vector3 getDesiredPosition()
    {
        // Ensure that we always have a target (in case this gets called before init)
        if (this.target == null)
        {
            this.target = this.cam.transform;
        }

        Vector3 desiredPos = this.target.position
            + this.target.right * this.offsetToTarget.x
            + this.target.up * this.offsetToTarget.y
            + this.target.forward * this.offsetToTarget.z;

        if (this.keepOnCamHeight)
        {
            // Override y position to keep on Cam height
            desiredPos.y = this.cam.transform.position.y;

            // Check if distance is still sufficient after overriding y pos
            float distance = Vector3.Distance(desiredPos, this.target.position);

            if (distance < this.offsetToTarget.z)
            {
                desiredPos = (desiredPos - this.target.position).normalized * this.offsetToTarget.z + this.target.position;
                //Vector3 newPos = (this.transform.position - this.keepAwayTransform.position).normalized * this.keepAwayDistance + this.keepAwayTransform.position;
            }
        }

        return desiredPos;
    }

    private void updateIsMoving()
    {
        float distance = Vector3.Distance(this.transform.position, this.getDesiredPosition());

        if (this.isMoving && distance < this.stopMovingDistance)
        {
            this.isMoving = false;
        }
        else if (!this.isMoving && distance > this.startMovingDistance)
        {
            this.isMoving = true;
        }
    }

    private void updatePosition()
    {
        Vector3 newPosition = Vector3.Lerp(this.transform.position, this.getDesiredPosition(), this.movingSpeed * Time.deltaTime);

        if (this.keepAwayTransform != null)
        {
            float distance = Vector3.Distance(newPosition, this.keepAwayTransform.position);

            if (distance < this.keepAwayDistance)
            {
                // Getting too close to keep away from transform -> don't move
                newPosition = this.transform.position;
            }
        }

        this.transform.position = newPosition;
    }

    private void ensureMinDistanceToKeepAwayTransform()
    {
        float distance = Vector3.Distance(this.transform.position, this.keepAwayTransform.position);

        if (distance < this.keepAwayDistance)
        {
            Vector3 newPos = (this.transform.position - this.keepAwayTransform.position).normalized * this.keepAwayDistance + this.keepAwayTransform.position;
            this.transform.position = newPos;
        }
    }


    #endregion

}