using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using System.Threading.Tasks;
using System;
using System.Linq;
using System.Text;

// Designed by the goat Jacob Weedman
// Use on any basic enemy
//
// Configure to your likeing 
// Not all options apply with every configuration
//
// PREREQUISITES
// 1. The "Seeker" script from the A* pathfinding library must be attatched to the same game object
// 2. A Rigidbody2D component must be attatched to the same game object
// 3. A _Collider2D component must be attatched to the same game object
// 4. Various game objects are required to be in the scene in order for this to function properly
// 5. Various children game objects are required in order for this to funciton properly

public class MasterEnemyAI : MonoBehaviour
{

    #region MISC VARIABLES (DO NOT CHANGE VALUES)
    GameObject Target = null;
    GameObject Camera = null;
    GameObject Player = null;
    GameObject Barrel = null;
    Vector2 StartPosition;
    public GameObject LastKnownPlayerLocation = null;
    float DistanceFromPlayer;
    public List<GameObject> SuitablePositions;
    public List<GameObject> AllPositions;
    public List<GameObject> PatrolPositions;
    // start pathfinding
    float nextWaypointDistance = 5;
    Path path = null;
    int currentWaypoint = 0;
    bool reachedEndOfPath = false;
    // end pathfinding
    #endregion

    #region CONDITIONS/GENERAL INFORMATION
    bool PositionRecomputeLock = false;
    public bool TargetingPlayer = false;
    public bool CurrentlyReloading = false;
    bool PatrolLock = false;
    bool Lock = false;
    bool MoveLock = false;
    bool FireLock = false;
    bool DashLock = false;
    bool TeleportLock = true;
    bool CurrentlyMovingToNextPatrolTarget = false;
    float DesiredDistance;
    public int WeaponCurrentMagazineAmmount;
    public int NumberOfHitsPerMag = 0;
    float BarrelAngle;
    bool Primed = false; // For self-explosion

    #endregion

    #region ENEMY STATS (Changeable in Unity)
    public float Speed = 1f; // In relation to player's walking speed
    public float JumpHeight = 1.5f; // In relation to player's regular jump height
    public float Health = 100;
    public float MinumumDistance = 5f;
    public float PatrolDistance = 10;
    public int PlayerDetectionRange = 50;
    float PatrolCooldown;
        public float PatrolCooldownReset = 20.0f;
    float JumpCooldown;
        public float JumpCooldownReset = 1.0f;
    float FireCooldown;
        public float FireCooldownReset = 0.1f;
    float ReloadCooldown;
        public float ReloadCooldownReset = 5.0f;
    public float DashSpeed = 40f; // Strength of the dash
    float DashCooldown;
        public float DashCooldownReset = 4.0f;
    float TeleportCooldown;
        public float TeleportCooldownReset = 0.5f;
    public float HiddenTransparencyAmmount = 0.05f; // Strength of the cloaked transparency
    public int MaxBatteryCapacity = 30; // Depletes one every second it is out of the bay
    public int RechargeTime = 6000; //ms

    // Capabiltiies
    public bool AbilityDash = false;
    public bool AbilityInvisible = false;
    public bool AbilityJump = true;
    public bool AbilityTeleport = false;
    public bool AbilityReloadAndMove = false;
    public bool AbilityMove = true;
    public bool AbilityDynamicRelocation = true;
    public bool AbilityExplodeOnContact = false;
    public bool AbilityExplodeNearPlayer = false;
    public bool AbilityExplodeOnDeath = false;
    public bool AbilityPlayerESP = false;

    #endregion

    #region WEAPON STATS (CHANGEABLE in Unity)

    public GameObject EnemyProjectile;
    public string EnemyType = "GROUND"; // Options: "GROUND", "AIR"
    public string WeaponType = "RANGED"; // Options: "RANGED", "ROCKET", "PARTICLE", "MELEE", "GRENADE", "NONE"
    public int WeaponDamage = 5; // Damage per hit
    public float WeaponRandomSpread = 7.5f; // Random direction of lanched projectiles
    public int WeaponRange = 15; // Maximum range of the projectile before it drops off
    public float WeaponProjectileSpeed = 40f; // Speed of launched projectiles
    public int WeaponMagazineSize = 20; // Number of shots the enemy will take before having to reloade

    #endregion

    #region COMPONENT REFERENCES
    Seeker seeker = null;
    Rigidbody2D rb = null;
    #endregion

    #region ONCE THE ENEMY IS SPAWNED IN
    void Awake()
    {
        if (GetComponent<Seeker>())
            seeker = GetComponent<Seeker>();
        if (GetComponent<Rigidbody2D>())
            rb = GetComponent<Rigidbody2D>();

        if (transform.Find("ReloadingIndicator"))
            transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
        if (transform.Find("PursuingIndicator"))
            transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;

        StartPosition = transform.position;

        if (GameObject.FindGameObjectsWithTag("PossiblePositions").Count() > 0)
            AllPositions = GameObject.FindGameObjectsWithTag("PossiblePositions").ToList();

        if (EnemyType == "GROUND") // Configure settings for Ground enemies
        {
            foreach (GameObject pos in AllPositions)
            {
                if (Vector2.Distance(pos.transform.position, StartPosition) <= PatrolDistance)
                {
                    PatrolPositions.Add(pos);
                }
            }

            gameObject.layer = LayerMask.NameToLayer("Enemies");

            foreach (Transform child in gameObject.transform)
            {
                child.gameObject.layer = LayerMask.NameToLayer("Enemies");
            }

            if (PatrolPositions.Count() > 0)
            {
                Target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
            }
            else
            {
                Target = null;
            }
        }

        if (EnemyType == "AIR") // Configure settings for Air enemies
        {
            gameObject.layer = LayerMask.NameToLayer("FlyingEnemies");

            foreach (Transform child in gameObject.transform)
            {
                child.gameObject.layer = LayerMask.NameToLayer("FlyingEnemies");
            }

            rb.gravityScale = 0;
            if (GameObject.Find("FlyingTarget"))
                Target = Instantiate(GameObject.Find("FlyingTarget"), transform.position, Quaternion.identity);
        }

        if (GameObject.FindGameObjectWithTag("MainCamera"))
            Camera = GameObject.FindGameObjectWithTag("MainCamera");
        if (GameObject.FindGameObjectWithTag("Player"))
            Player = GameObject.FindGameObjectWithTag("Player");

        if (transform.Find("Barrel"))
            Barrel = transform.Find("Barrel").gameObject;

        WeaponCurrentMagazineAmmount = WeaponMagazineSize;
        DesiredDistance = WeaponRange - 5;

        if (EnemyProjectile == null) // Automatically set enemy projectile if level designer did not
        {
            switch (WeaponType)
            {
                case "RANGED":
                    EnemyProjectile = GameObject.Find("GameObjectFolder").transform.Find("EnemyProjectile").gameObject;
                break;

                case "ROCKET":
                    EnemyProjectile = GameObject.Find("GameObjectFolder").transform.Find("EnemyRocket").gameObject;
                break;

                case "PARTICLE":
                    EnemyProjectile = GameObject.Find("GameObjectFolder").transform.Find("EvilAura").gameObject;
                break;

                case "GRENADE":
                    EnemyProjectile = GameObject.Find("GameObjectFolder").transform.Find("Grenade").gameObject;
                break;

                default:
                    EnemyProjectile = GameObject.Find("GameObjectFolder").transform.Find("EnemyProjectile").gameObject;
                break;
            }
        }

        InvokeRepeating("UpdatePath", 0f, 0.1f);
        InvokeRepeating("PathfindingTimeout", 0f, 30);

        // Initialize timers
        JumpCooldown = JumpCooldownReset;
        FireCooldown = FireCooldownReset;
        ReloadCooldown = ReloadCooldownReset;
        DashCooldown = DashCooldownReset;
        PatrolCooldown = 0;
        TeleportCooldown = TeleportCooldownReset;

    }
    #endregion

    #region INVOKE REPEATING METHODS
    // When enemy has reached the next node
    void OnPathComplete(Path p)
    {
        if (!p.error)
        {
            path = p;
            currentWaypoint = 0;
        }
    }

    // Select next node
    void UpdatePath()
    {
        if (seeker.IsDone() && Target != null)
        {
            seeker.StartPath(rb.position, Target.transform.position, OnPathComplete);
        }
    }

    // Pathfiniding Timeout
    void PathfindingTimeout()
    {
        if (Target != null && Vector2.Distance(transform.position, Target.transform.position) > 0.5 || LastKnownPlayerLocation == null)
        {
            LastKnownPlayerLocation = null;
            MoveNextPatrol();

            if (AbilityTeleport && TeleportLock == false)
            {
                Teleport();
            }
        }
    }

    #endregion

    #region MAIN LOGIC
    void FixedUpdate()
    {
        #region DEATH
        if (Health <= 0)
        {
            if (AbilityExplodeNearPlayer == false && AbilityExplodeOnContact == false && AbilityExplodeOnDeath == false)
            {
            // Create carcas
            GameObject DeadBody;
            DeadBody = Instantiate(gameObject, transform.position, Quaternion.identity);
            DeadBody.GetComponent<Rigidbody2D>().gravityScale = 1.5f;
            Destroy(GameObject.Find(DeadBody.name).GetComponent<MasterEnemyAI>());
            Destroy(GameObject.Find(DeadBody.name).GetComponent<Seeker>());

            foreach (Transform child in DeadBody.transform)
            {
                GameObject.Destroy(child.gameObject);
            }

            Destroy(gameObject);
            }
            else if (AbilityExplodeOnDeath == true)
            {
                Explode();
            }
        }

        // Explode when close to the player
        if (AbilityExplodeNearPlayer && Primed && Vector2.Distance(Player.transform.position, transform.position) <= 2)
        {
            Explode();
        }

        #endregion

        #region ICONS

        // See where the enemy wants to go (DEBUGGING ONLY)
        /*
        if (Target != null)
        {
            foreach (GameObject pos in AllPositions)
            {
                pos.GetComponent<SpriteRenderer>().enabled = false;
            }
            Target.GetComponent<SpriteRenderer>().enabled = true;
        }
        */

        if (TargetingPlayer && transform.Find("PursuingIndicator"))
        {
            transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = true;
            if (AbilityInvisible)
            {
                Cloak(false);
            }
        }
        else if (transform.Find("PursuingIndicator"))
        {
            transform.Find("PursuingIndicator").GetComponent<SpriteRenderer>().enabled = false;
            if (AbilityInvisible)
            {
                Cloak(true);
            }
        }

        if (CurrentlyReloading && transform.Find("ReloadingIndicator"))
        {
            transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = true;
        }
        else if (transform.Find("ReloadingIndicator"))
        {
            transform.Find("ReloadingIndicator").GetComponent<SpriteRenderer>().enabled = false;
        }

        //if (90 <= angle || angle <= 270) // FIX THIS
        //{
        //    barrel.transform.localScale = new Vector2(-barrel.transform.localScale.x, barrel.transform.localScale.y);
        //}
        #endregion

        #region MISC PATHFINDING
        // Player ESP
        if (AbilityPlayerESP)
        {
            LastKnownPlayerLocation = Player;
        }

        if (path == null)
            return;

        if (Target == null)
            return;
        if (currentWaypoint >= path.vectorPath.Count)
        {
            reachedEndOfPath = true;
            return;
        }
        else
        {
            reachedEndOfPath = false;
        }
        
        #endregion

        // Rotate barrel towards player
        if (Barrel != null) // PRevents error
        {
            Quaternion targetRotation = Quaternion.Euler(new Vector3(0, 0, BarrelAngle));
            Barrel.transform.rotation = Quaternion.RotateTowards(Barrel.transform.rotation, targetRotation, 200 * Time.deltaTime);
        }

        #region TARGET DETERMINATION & PATROL & PLAYER DETECTION

        // Check if the enemy is at the target location, used for moving to the next patrol target
        if (Vector2.Distance(transform.position, Target.transform.position) <= 1)
        {
            PatrolLock = false;
            MoveNextPatrol();
        }

        // Patrol Countdown
        PatrolCooldown -= Time.deltaTime;
        if (PatrolCooldown <= UnityEngine.Random.Range(-3, 3))
            PatrolLock = false;
        else
            PatrolLock = true;

        // If enemy has line of sight and is close enough the player is detected
        if (DetermineLineOfSight(gameObject, Player) && Vector2.Distance(transform.position, Player.transform.position) <= PlayerDetectionRange)
        {
            TargetingPlayer = true;
            PositionRecomputeLock = false;
            Primed = true; // for explosive enemies

            // Get the last know player location by finding which of the positions is closest to the player
            if (LastKnownPlayerLocation == null)
            {
                LastKnownPlayerLocation = gameObject;
            }
            foreach (GameObject pos in AllPositions)
            {
                if (Vector2.Distance(Player.transform.position, pos.transform.position) < Vector2.Distance(Player.transform.position, LastKnownPlayerLocation.transform.position))
                {
                    LastKnownPlayerLocation = pos;
                }
            }

            // Angle barrel towards player
            if (Barrel != null)
            {
                BarrelAngle = Mathf.Atan2(Player.transform.position.y - Barrel.transform.position.y, Player.transform.position.x - Barrel.transform.position.x) * Mathf.Rad2Deg;
            }

        }
        // Player has broken line of sight and the enemy will attempt to move to the last known location
        else if (LastKnownPlayerLocation != null)
        {
            if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) > 0.5)
            {
                PositionRecomputeLock = true;
                Target = LastKnownPlayerLocation;

                if (AbilityTeleport && TeleportLock == false)
                {
                    Teleport();
                }

            }
            if (Vector2.Distance(transform.position, LastKnownPlayerLocation.transform.position) < 0.5 && DetermineLineOfSight(Player, gameObject) == false)
            {
                TargetingPlayer = false;
                LastKnownPlayerLocation = null;
            }

            // Reset barrel rotation
            BarrelAngle = 0f;
        }
        // Go back to patrol move
        else
        {
            Primed = false;
            TargetingPlayer = false;

            if (MoveLock == false)
            {
                MoveNextPatrol();
            }

            // Reset barrel rotation
            BarrelAngle = 0f;
        }

        if (PositionRecomputeLock == false)
        {
            // Catch desired distance error
            if (DesiredDistance <= MinumumDistance)
            {
                DesiredDistance = MinumumDistance + 1;
            }

            // If the player moves away (CHANGE TARGET)
            if (Vector2.Distance(Target.transform.position, Player.transform.position) > DesiredDistance)
            {
                ComputeClosestPositionToPlayer();
            }

            // If the player is too close to the target (CHANGE TARGET)
            if (Vector2.Distance(Target.transform.position, Player.transform.position) < MinumumDistance)
            {
                ComputeClosestPositionToPlayer();
            }

            // If the target is on the other side of the player (CHANGE TARGET)
            //if (Vector2.Distance(transform.position, player.transform.position) < Vector2.Distance(target.transform.position, player.transform.position))
            //{
            //    ComputeClosestPositionToPlayer();
            //}

            // If the player is not within line of sight of the desired position (CHANGE TARGET)
            if (DetermineLineOfSight(Target, Player) == false)
            {
                ComputeClosestPositionToPlayer();
            }

            if (TeleportLock == false && AbilityTeleport)
            {
                if (Vector2.Distance(transform.position, Target.transform.position) > 0.5)
                {
                    Teleport();
                }
            }

            // If the enemy reaches the target
            if (Vector2.Distance(Target.transform.position, transform.position) <= 1 && DetermineLineOfSight(gameObject, Player))
            {
                if (EnemyType == "AIR")
                {
                    ComputeClosestPositionToPlayer();
                }
                if (TeleportLock == false && AbilityTeleport)
                {
                    ComputeClosestPositionToPlayer();
                    Teleport();
                }
            }
        }

        #endregion 

        #region MOVEMENT & GROUND DETECTION

        //Detecting if the enemy has reached the ground
        if (EnemyType == "GROUND" && AbilityJump && GetComponentInChildren<GroundCheck>()) // Makes sure the enemy can jump before calculating
        {
            if (GetComponent<GroundCheck>().isGrounded == true)
            {
                JumpCooldown -= Time.deltaTime;
            }
            else
            {
                JumpCooldown = JumpCooldownReset;
            }
        }

        // Decriment dash timer
        DashCooldown -= Time.deltaTime;
        if (DashCooldown <= 0 && Vector2.Distance(transform.position, Target.transform.position) > 2)
            DashLock = false;
        else
            DashLock = true;
        // Call Dash
        if (DashLock == false && AbilityDash)
        {
            Dash();
        }

        // Decriment teleport timer
        TeleportCooldown -= Time.deltaTime;
        if (TeleportCooldown <= 0 && Vector2.Distance(transform.position, Target.transform.position) > 2)
            TeleportLock = false;
        else
            TeleportLock = true;

        // Ground Movement
        if (MoveLock == false && EnemyType == "GROUND" && AbilityMove)
        {
            //FireLock = false;

            Vector2 direction = (Vector2)path.vectorPath[currentWaypoint] - rb.position;

            if (GetComponent<GroundCheck>().isGrounded == true) // Movement while grounded
            { 
                if (direction.x > 0) // Move right
                {   
                    if (TargetingPlayer)
                    {
                        if (Math.Abs(rb.linearVelocity.x) < Speed * 20) // Targeting player speed Cap
                            rb.AddForce(new Vector2(Speed * 20, rb.linearVelocity.y));
                    }
                    else
                    {
                        if (Math.Abs(rb.linearVelocity.x) < Speed * 5) // Wandering speed Cap
                            rb.AddForce(new Vector2(Speed * 10, rb.linearVelocity.y));
                    }
                }

                if (direction.x < 0) // Move left
                {
                    if (TargetingPlayer)
                    {
                        if (Math.Abs(rb.linearVelocity.x) < Speed * 20) // Targeting player speed Cap
                            rb.AddForce(new Vector2(-1 * Speed * 20, rb.linearVelocity.y));
                    }
                    else
                    {
                        if (Math.Abs(rb.linearVelocity.x) < Speed * 5) // Wandering speed Cap
                            rb.AddForce(new Vector2(-1 * Speed * 10, rb.linearVelocity.y));
                    }
                }

                if (direction.y > 1f) // Wants to jump
                {
                    Jump(direction.x);
                }
            }
            else // Movement while in the air
            {
                if (direction.x > 0) // Move right
                {
                    rb.AddForce(new Vector2(Speed * 2, rb.linearVelocity.y));
                }

                if (direction.x < 0) // Move left
                {
                    rb.AddForce(new Vector2(-1 * Speed * 2, rb.linearVelocity.y));
                }
            }

            // A* logic
            float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);

            if (distance < nextWaypointDistance)
            {
                currentWaypoint++;
            }
        }
        // Air Movement
        if (MoveLock == false && EnemyType == "AIR" && AbilityMove)
        {
            Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
            Vector2 force = direction * Speed * 20;

            rb.AddForce(force);

            // A* logic
            float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);

            if (distance < nextWaypointDistance)
            {
                currentWaypoint++;
            }
        }

        #endregion 

        #region ATTACK

        // Decriment timer
        FireCooldown -= Time.deltaTime;
        if (FireCooldown <= 0 && CurrentlyReloading == false)
            FireLock = false;
        else
            FireLock = true;

        if (WeaponCurrentMagazineAmmount > 0 && FireLock == false && CurrentlyReloading == false && WeaponType != "NONE")
        {
            if (DetermineLineOfSight(gameObject, Player) == true && Vector2.Distance(transform.position, Player.transform.position) <= WeaponRange)
            {
                UseWeapon();
            }
        }
        else if (WeaponCurrentMagazineAmmount == 0 && CurrentlyReloading == false)
        {
            CurrentlyReloading = true;
        }

        // Engage periodical reloading if player moves too far away and mag is low
        if (Vector2.Distance(transform.position, Player.transform.position) > WeaponRange + 5 && WeaponCurrentMagazineAmmount / WeaponMagazineSize < 0.75f)
        {
            ReloadCooldown = ReloadCooldownReset / 5; // Make reloading faster
            CurrentlyReloading = true;
        }

        // Reloading
        if (CurrentlyReloading) 
        {
            ReloadCooldown -= Time.deltaTime;
            if (ReloadCooldown <= 0)
            {
                ReloadCooldown = ReloadCooldownReset;
                CurrentlyReloading = false;
                WeaponCurrentMagazineAmmount = WeaponMagazineSize;
                // Dynamic Relocation
                if (NumberOfHitsPerMag / WeaponMagazineSize < 0.5 && AbilityDynamicRelocation)
                    DesiredDistance -= 5;
                NumberOfHitsPerMag = 0;
            }
        }

        void UseWeapon()
        {
            FireCooldown = FireCooldownReset;
        
            switch (WeaponType)
            {
                case "RANGED":
                    // Create Projectile
                    GameObject BulletInstance;
                    BulletInstance = Instantiate(EnemyProjectile, Barrel.transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));
                    //BulletInstance.transform.parent = transform;

                    // Variables
                    BulletInstance.GetComponent<EnemyProjectileScript>().WeaponDamage = WeaponDamage;
                    BulletInstance.GetComponent<EnemyProjectileScript>().WeaponRange = WeaponRange;

                    // Send it on it's way
                    BulletInstance.GetComponent<Rigidbody2D>().linearVelocity = BulletInstance.transform.forward * -1 * WeaponProjectileSpeed;
                    BulletInstance.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, BulletInstance.transform.forward) - 90));
                    break;

                case "ROCKET":

                    // Create Rocket
                    GameObject Rocket;
                    Rocket = Instantiate(EnemyProjectile, Barrel.transform.position, Quaternion.LookRotation(transform.position - GameObject.FindGameObjectWithTag("Player").transform.position + new Vector3(UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), UnityEngine.Random.Range((-1 * WeaponRandomSpread), WeaponRandomSpread), 0)));

                    // Variables
                    Rocket.GetComponent<EnemyRocket>().WeaponDamage = WeaponDamage;
                    Rocket.GetComponent<EnemyRocket>().duration = 30;

                    // Send it on its way
                    Rocket.GetComponent<Rigidbody2D>().linearVelocity = Rocket.transform.forward * -1 * WeaponProjectileSpeed;
                    Rocket.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Vector2.SignedAngle(Vector2.right, Rocket.transform.forward) - 90));
                    
                    break;

                case "PARTICLE":

                    // Create particle
                    GameObject ParticleWeapon;

                    ParticleWeapon = Instantiate(EnemyProjectile, new Vector3(Barrel.transform.position.x + UnityEngine.Random.Range(-0.5f, 0.5f), Barrel.transform.position.y + UnityEngine.Random.Range(-0.5f, 0.5f), EnemyProjectile.transform.position.z), Quaternion.identity);
                    ParticleWeapon.GetComponent<Rigidbody2D>().linearVelocity = new Vector2(transform.position.x - Player.transform.position.x + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread), transform.position.y - Player.transform.position.y + UnityEngine.Random.Range(-WeaponRandomSpread, WeaponRandomSpread)).normalized * 1.25f * WeaponRange * -1;

                    // Set variables
                    ParticleWeapon.GetComponent<EnemyParticleWeapon>().destroy = true;
                    ParticleWeapon.GetComponent<EnemyParticleWeapon>().opacity = true;
                    ParticleWeapon.GetComponent<EnemyParticleWeapon>().damageAmmount = WeaponDamage;

                    break;

                case "MELEE":

                    GameObject.Find("GameData").GetComponent<GameData>().CurrentHealth -= WeaponDamage;

                    break;

                case "GRENADE":

                    break;

                default:
                    Debug.Log("Invalid or no weapon seleted.");
                    break;

            }

            WeaponCurrentMagazineAmmount--;
        }

        #endregion 

    }

    // General Utility
    bool DetermineLineOfSight(GameObject object1, GameObject object2)
    {
        Vector3 RaycastStart = object1.transform.position;
        Vector3 RaycastDirection = (object2.transform.position - object1.transform.position).normalized;
        float RaycastDistance = Vector3.Distance(object2.transform.position, object1.transform.position);

        if (Physics2D.Raycast(RaycastStart, RaycastDirection, RaycastDistance, LayerMask.GetMask("SolidGround")) == false)
        {
            Debug.DrawRay(RaycastStart, RaycastDirection * RaycastDistance);
            return true;
        }
        else
        {
            return false;
        }
    }

    void Jump(float JumpDirection)
    {
        if (JumpCooldown <= 0 && AbilityJump)
        {
            JumpCooldown = JumpCooldownReset;
            rb.linearVelocity = new Vector2(rb.linearVelocity.x + JumpDirection, JumpHeight * 12);
        }
    }

    // Part of the pursue cycle
    void ComputeClosestPositionToPlayer()
    {
        MoveLock = false;

        if (EnemyType == "GROUND")
        {

            SuitablePositions.Clear();
            foreach (GameObject query in AllPositions)
            {
                // Check the distance of the position
                if (Vector2.Distance(query.transform.position, Player.transform.position) < DesiredDistance && Vector2.Distance(query.transform.position, Player.transform.position) >= MinumumDistance)
                {
                    // Check line of sight of the position
                    if (DetermineLineOfSight(query, Player))
                    {
                        SuitablePositions.Add(query);
                    }
                }
            }

            if (SuitablePositions.Count > 0)
            {
                Target = SuitablePositions[UnityEngine.Random.Range(0, SuitablePositions.Count)];
                
                if (AbilityTeleport == false)
                {
                    foreach (GameObject pos in SuitablePositions)
                    {
                        //Find the point that is closest to the enemy
                        if (Vector2.Distance(transform.position, pos.transform.position) < Vector2.Distance(transform.position, Target.transform.position))
                        {
                            Target = pos;
                        }
                    }
                }
            }
        }

        if (EnemyType == "AIR")
        {
            //if (DetermineLineOfSight(player, gameObject))
            //{
            Target.transform.position = new Vector2((Player.transform.position.x + UnityEngine.Random.Range(-MinumumDistance, MinumumDistance)), (Player.transform.position.y + MinumumDistance + UnityEngine.Random.Range(-5, 0)));
            //}
        }
    }

    // Part of the patrol cycle
    void MoveNextPatrol() // Determine the next place to move to
    {
        if (PatrolLock == false)
        {
            PatrolCooldown = PatrolCooldownReset;
            PositionRecomputeLock = true;
            LastKnownPlayerLocation = null;
            DesiredDistance = WeaponRange - 5;

            if (EnemyType == "GROUND")
            {
                //Find Random Position nearby
                if (PatrolPositions.Count > 1)
                    Target = PatrolPositions[UnityEngine.Random.Range(0, PatrolPositions.Count)];
            }

            if (EnemyType == "AIR")
            {
                //Find Random Position nearby
                Target.transform.position = new Vector2((StartPosition.x + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)), (StartPosition.y + UnityEngine.Random.Range(-1 * PatrolDistance, PatrolDistance)));
            }
        }
    }

        // Explode
    void Explode()
    {
        // Shake Camera
        Camera.GetComponent<CameraMovement>().shakeCamera(0.8f, 0.5f);

        //  Create Explosion
        GameObject Explosion;
        Explosion = Instantiate(GameObject.Find("Explosion"), new Vector3(transform.position.x, transform.position.y, GameObject.Find("Explosion").transform.position.z), Quaternion.identity);
        Explosion.transform.rotation = Quaternion.Euler(Vector3.forward);

        // Set Variables
        Explosion.GetComponent<EnemyParticleWeapon>().destroy = true;
        Explosion.GetComponent<EnemyParticleWeapon>().opacity = true;
        Explosion.GetComponent<EnemyParticleWeapon>().timer = 3;
        Explosion.GetComponent<EnemyParticleWeapon>().damageAmmount = WeaponDamage;

        // Destroy Flying Target
        //if (EnemyType == "AIR")
        //{
        //    Destroy(target);
        //}

        //gameObject.GetComponent<MasterEnemyAI>().enabled = false;

        // Destroy GameObject
        Destroy(gameObject);
    }

    void Cloak(bool state)
    {
        if (state)
        {
            gameObject.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, HiddenTransparencyAmmount);
        }
        else
        {
            gameObject.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
        }
    }

    void Dash()
    {
        DashCooldown = DashCooldownReset;
        // Perform dash
        if (AbilityPlayerESP)
            rb.linearVelocity = new Vector2(transform.position.x - Player.transform.position.x, transform.position.y - Player.transform.position.y).normalized * DashSpeed * -1;
        else
            rb.linearVelocity = new Vector2(transform.position.x - Target.transform.position.x, transform.position.y - Target.transform.position.y).normalized * DashSpeed * -1;
    }

    void Teleport()
    {
        TeleportCooldown = TeleportCooldownReset;
        transform.position = Target.transform.position;
    }

    // On contact
    void OnTriggerEnter2D(Collider2D collision)
    {
        if (Primed && AbilityExplodeOnContact)
        {
            if (collision.gameObject.layer == LayerMask.NameToLayer("SolidGround") || collision.gameObject.layer == LayerMask.NameToLayer("Enemies") || collision.gameObject.layer == LayerMask.NameToLayer("Player"))
            {
                Explode();
            }
        }
    }

    #endregion
}