/*
 * author: mark joshwel
 * date: 30/5/2024
 * description: enemy AI based off <https://youtu.be/UjkSFoLxesw>
 */

using System;
using UnityEngine;
using UnityEngine.AI;
using Random = UnityEngine.Random;

/// <summary>
///     AI patrolling, chasing and capturing behaviour for the enemy
/// </summary>
public class HerAI : MonoBehaviour
{
    /// <summary>
    ///     variable for the nav mesh agent that determines where the enemy can move
    /// </summary>
    public NavMeshAgent agent;

    /// <summary>
    ///     variable for the player's position
    /// </summary>
    public Transform player;

    /// <summary>
    ///     variables to distinguish ground and player for sensing
    /// </summary>
    public LayerMask whatIsGround, whatIsPlayer;

    /// <summary>
    ///     patrolling: variable for the next point for her to walk to
    /// </summary>
    public Vector3 walkPoint;

    /// <summary>
    ///     patrolling: variable for the range of the walk point
    /// </summary>
    public float walkPointRange;

    /// <summary>
    ///     capturing: variable for the time range between captures
    /// </summary>
    public float timeBetweenCaptures;

    /// <summary>
    ///     variable specifying the sight range of the enemy
    /// </summary>
    public float sightRange;

    /// <summary>
    ///     variable specifying the attack range of the enemy
    /// </summary>
    public float attackRange;

    /// <summary>
    ///     boolean variable for the player being in sight range
    /// </summary>
    public bool playerInSightRange;

    /// <summary>
    ///     boolean variable for the player being in attack range
    /// </summary>
    public bool playerInCaptureRange;

    /// <summary>
    ///     capturing: variable for if the enemy has attempted to capture the player
    /// </summary>
    private bool _attemptedCapture;

    /// <summary>
    ///     variable to store game manager
    /// </summary>
    private GameManager _game;

    /// <summary>
    ///     patrolling: variable to determine if the next point is set
    /// </summary>
    private bool _walkPointSet;

    /// <summary>
    /// </summary>
    private void Awake()
    {
        player = GameObject.Find("PlayerObj").transform;
        agent = GetComponent<NavMeshAgent>();
    }

    /// <summary>
    /// </summary>
    private void Update()
    {
        //Check for sight and attack range
        playerInSightRange = Physics.CheckSphere(transform.position, sightRange, whatIsPlayer);
        playerInCaptureRange = Physics.CheckSphere(transform.position, attackRange, whatIsPlayer);

        if (!playerInSightRange && !playerInCaptureRange) Patrolling();
        if (playerInSightRange && !playerInCaptureRange) ChasePlayer();
        if (playerInCaptureRange && playerInSightRange) CapturePlayer();
    }

    /// <summary>
    ///     function to find and store the game manager
    /// </summary>
    /// <exception cref="Exception">generic exception when the object is in an unplayable state</exception>
    private void OnEnable()
    {
        // get the game manager
        _game = GameObject.Find("GameManager").GetComponent<GameManager>();
        if (_game == null)
            throw new Exception("HerAI: could not find GameManager (unreachable?)");
    }

    // /// <summary>
    // /// function to destroy the enemy
    // /// </summary>
    // private void DestroyEnemy()
    // {
    //     Destroy(gameObject);
    // }

    /// <summary>
    ///     function to draw/visualize the sight and attack range of the enemy
    /// </summary>
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, attackRange);
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, sightRange);
    }

    /// <summary>
    ///     function handling patrolling behaviour
    /// </summary>
    private void Patrolling()
    {
        if (!_walkPointSet) SearchWalkPoint();
        else agent.SetDestination(walkPoint);

        // reached walk point
        if ((transform.position - walkPoint).magnitude < 1f)
            _walkPointSet = false;
    }

    /// <summary>
    ///     function to look for and set the next walk point
    /// </summary>
    private void SearchWalkPoint()
    {
        //Calculate random point in range
        var randomZ = Random.Range(-walkPointRange, walkPointRange);
        var randomX = Random.Range(-walkPointRange, walkPointRange);

        walkPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ);

        if (Physics.Raycast(walkPoint, -transform.up, 2f, whatIsGround))
            _walkPointSet = true;
    }

    /// <summary>
    ///     function to chase the player
    /// </summary>
    private void ChasePlayer()
    {
        agent.SetDestination(player.position);
    }

    /// <summary>
    ///     function that captures the player and signals the game manager appropriately
    /// </summary>
    private void CapturePlayer()
    {
        // // don't move the enemy
        // agent.SetDestination(transform.position);
        // look at me!
        transform.LookAt(player);
        // are we under cooldown?
        if (_attemptedCapture) return;

        // signal the game manager to show the 'caught!' menu
        _game.SetDisplayState(GameManager.DisplayState.ScreenCaughtMenu);

        // set the cooldown
        _attemptedCapture = true;
        Invoke(nameof(ResetCapture), timeBetweenCaptures);
    }

    /// <summary>
    ///     function to reset the capture cooldown, called by <c>Invoke()</c>
    /// </summary>
    private void ResetCapture()
    {
        _attemptedCapture = false;
    }
}