/*
 * author: ryan lin
 * date: 30/7/2024
 * description: making the car go to a game object's position
 */

using System.Collections;
using UnityEngine;

/// <summary>
///     making the car go to a game object's position
/// </summary>
public class AICar : MonoBehaviour
{
    /// <summary>
    ///     reference to the car model
    /// </summary>
    [SerializeField] private Transform carPosition;

    /// <summary>
    ///     the radius of which the car can stop in
    /// </summary>
    [SerializeField] private float stoppingDistance;

    /// <summary>
    ///     the speed of which the car needs to be at/under to when it is in the slowed state
    /// </summary>
    [SerializeField] private float slowingSpeed;

    /// <summary>
    ///     the radius that the car switches to the slowed state
    /// </summary>
    [SerializeField] private float slowingDistance;

    /// <summary>
    ///     the distance that the car is allowed to reverse
    /// </summary>
    [SerializeField] private float reverseDist;

    /// <summary>
    ///     the acceleration input for the car script
    /// </summary>
    private float _accelerationInput;

    /// <summary>
    ///     the angular input for the car script
    /// </summary>
    private float _angularDirection;

    /// <summary>
    ///     reference to the car script
    /// </summary>
    private CarController _car;

    /// <summary>
    ///     the current state the car is in for fsm
    /// </summary>
    private string _currentState;

    /// <summary>
    ///     the distance that the car is from the target
    /// </summary>
    private float _distanceToTarget;

    /// <summary>
    ///     the transform of the target
    /// </summary>
    private Transform _driveTarget;

    private NextState _nextState;

    /// <summary>
    ///     the turn input for the car script
    /// </summary>
    private float _turnInput;

    /// <summary>
    ///     the car is in front or behind the target, a positive will be returned if the car is in front
    /// </summary>
    private float _verticalDirection;

    /// <summary>
    ///     initialise values
    /// </summary>
    private void Awake()
    {
        var hasChild = false;
        var i = 0;
        while (!hasChild)
        {
            var test = gameObject.transform.GetChild(i).gameObject;
            if (test.name == "Target")
            {
                _driveTarget = test.transform;
                hasChild = true;
            }
            else
            {
                i++;
            }
        }

        _car = GetComponent<CarController>();
        _nextState = NextState.Stopped;
        _currentState = "Stopped";
        StartCoroutine(_currentState);
    }

    /// <summary>
    ///     to update the next state value when it changes
    /// </summary>
    private void Update()
    {
        if (_nextState == NextState.Stopped)
            _currentState = "Stopped";
        else if (_nextState == NextState.Slowed)
            _currentState = "Slowed";
        else
            _currentState = "Driving";
    }

    /// <summary>
    ///     is called at the end of states to call the next state
    /// </summary>
    private void ChangeState()
    {
        StartCoroutine(_currentState);
    }

    /// <summary>
    ///     a function that allows the car to steer
    /// </summary>
    private void Steering()
    {
        _angularDirection = Vector3.SignedAngle(carPosition.transform.forward,
            (_driveTarget.position - carPosition.transform.position).normalized, Vector3.up);
        if (_angularDirection < 0)
            _turnInput = -1;
        else
            _turnInput = 1;
    }

    /// <summary>
    ///     to put the car in a stopped state
    /// </summary>
    private IEnumerator Stopped()
    {
        while (_nextState != NextState.Stopped)
        {
            _distanceToTarget = Vector3.Distance(carPosition.position, _driveTarget.position);
            _car.braking = true;
            _accelerationInput = 0f;
            _turnInput = 0f;
            if (_distanceToTarget > stoppingDistance) _nextState = NextState.Slowed;
            yield return new WaitForSeconds(1);
        }

        ChangeState();
    }

    /// <summary>
    ///     to put the car in a slowed state
    /// </summary>
    private IEnumerator Slowed()
    {
        _car.braking = false;
        while (_nextState != NextState.Slowed)
        {
            _verticalDirection = Vector3.Dot(carPosition.transform.forward,
                (_driveTarget.position - carPosition.transform.position).normalized);
            _distanceToTarget = Vector3.Distance(carPosition.position, _driveTarget.position);
            if (_verticalDirection > 0)
            {
                if (carPosition.GetComponent<Rigidbody>().velocity.magnitude > slowingSpeed)
                    _accelerationInput = -1;
                else
                    _accelerationInput = 1;
            }
            else
            {
                if (_distanceToTarget < reverseDist)
                    _accelerationInput = -1;
                else
                    _accelerationInput = 1;
            }

            Steering();
            SlowedCheck();
            _car.SetInputs(_accelerationInput, _turnInput);
            yield return new WaitForEndOfFrame();
        }

        ChangeState();
    }

    /// <summary>
    ///     to put the car in a driving state
    /// </summary>
    private IEnumerator Driving()
    {
        _car.braking = false;
        while (_nextState != NextState.Driving)
        {
            _verticalDirection = Vector3.Dot(carPosition.transform.forward,
                (_driveTarget.position - carPosition.transform.position).normalized);
            _distanceToTarget = Vector3.Distance(carPosition.position, _driveTarget.position);
            _accelerationInput = 1;


            Steering();
            if (_distanceToTarget < slowingDistance) _nextState = NextState.Slowed;
            _car.SetInputs(_accelerationInput, _turnInput);
            yield return new WaitForEndOfFrame();
        }

        ChangeState();
    }

    /// <summary>
    ///     a function to check if the car should be a slowed state
    /// </summary>
    private void SlowedCheck()
    {
        if (_distanceToTarget < stoppingDistance) _nextState = NextState.Stopped;

        if (_distanceToTarget > slowingDistance) _nextState = NextState.Driving;
    }

    /// <summary>
    ///     enum of states that the car can be in
    /// </summary>
    private enum NextState
    {
        Slowed,
        Stopped,
        Driving
    }
}