﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyAI : Character {

    /*
     * public float shotRange = 15.0f;
	public float playerRange = 5.0f;
	public float distanceToShot = 10.0f;
     */

    [SerializeField]
    private Transform _Target;
    public Transform Target {
        get { return _Target; }
        set { _Target = value; }
    }

    [SerializeField]
    private float _ThinkingTime = 3.0f;
    public float ThinkingTime {
        get { return _ThinkingTime; }
        set { _ThinkingTime = value; }
    }

    [SerializeField]
    private float _WalkingTime = 6.0f;
    public float WalkingTime {
        get { return _WalkingTime; }
        set { _WalkingTime = value; }
    }

    [SerializeField]
    private float _PlayerRange = 2.0f;
    public float PlayerRange
    {
        get { return _PlayerRange; }
        set { _PlayerRange = value; }
    }

    [SerializeField]
    private float _MaxJumpHeight = 2.5f;
    public float MaxJumpHeight
    {
        get { return _MaxJumpHeight; }
        set { _MaxJumpHeight = value; }
    }

    [SerializeField]
    private float _PathNearDistance = 10.0f;
    public float PathNearDistance
    {
        get { return _PathNearDistance; }
        set { _PathNearDistance = value; }
    }

    //override protected WeaponStates WeaponState { get;set; }

    private float Speed = 0.15f;
    private float lastTime = 0f;    
    private AgentPath agentPath;

    protected override void Awake()
    {
        base.Awake();
        Messenger<float>.AddListener(GameEvent.HIT_ENEMY, OnHitEnemy);

        // If target is null, set in code
        if (Target == null)
        {
            Player player = FindObjectOfType<Player>();
            Target = player.transform;
        }
    }

    private void OnDestroy()
    {
        Messenger<float>.RemoveListener(GameEvent.HIT_ENEMY, OnHitEnemy);
    }

    // Use this for initialization
    protected override void Start () {
        base.Start();
        agentPath = GetComponent<AgentPath>();
        currentSpeed = 0.0f;

        // ApplyWeaponChange(WeaponStates.Pistol);
    }

    private bool ShouldJump(Vector3 direction)
    {
        // Check for jumping if next y position is different than the current y position
        
        // (0.0, -0.9, 0.0)   (0.0, 0.9, 0.0)
        // Original
        // Vector3 up = Vector3.up * (-characterController.height * 0.5f);
        // Vector3 up2 = Vector3.up * (characterController.height * 0.5f);
        // Vector3 leg = transform.position + characterController.center + up; // legLenth

        //Debug.Log("up: " + up + "  " + (transform.position + characterController.center - up));
        //Debug.Log("up2: " + up2 + "  " + (transform.position + characterController.center + up2));

        // Get the position of the character's height * half in along y-axis (upwards) direction only (approx. waist position)
        Vector3 halfHeightPosition = Vector3.up * (characterController.height * 0.5f); 

        // Get the character's leg (foot) position
        // Adding adjustment for cases when character controller's centre does not match the half height position
        Vector3 legPosition = transform.position + characterController.center - halfHeightPosition;

        // Max distance to check for obstacle to jump over - body width
        float distance = characterController.radius * 2;

        // Declare a raycast hit variable in readiness to receive what the hit is (obstacle in front of character)
        RaycastHit hit;

        // Cast a ray in the move direction to see if character's leg hits anything
        if (Physics.Raycast(legPosition, direction, out hit, distance))
        {
            // Check if what was hit is a box
            if (hit.transform.CompareTag("Box"))
            {
                // Check the height to jump
                // Height of object hit minus hit point
                float height = hit.collider.bounds.max.y - hit.point.y;

                // If not more than max jump height, can jump
                if (height <= MaxJumpHeight)
                {
                    return true;
                }
            }
        }
        
        return false;
    }


    private bool IsAwayFromPlayer()
    {

        Vector3 distanceToPlayer = (Target.position - transform.position);
        float sqrDistance = distanceToPlayer.sqrMagnitude;

        // Run distance is sum of range to target player and shooting distance range
        float runDistance = (PlayerRange + ShootRange);
        //float shootDistance = ShootRange * ShootRange;

        // Debug.Log("Run: " + distanceToPlayer + "   sqrDistance: " + sqrDistance  + "   runDistance" + (runDistance * runDistance));  //+ shootDistance); //
        if (sqrDistance <= (runDistance * runDistance)) // && distanceToPlayer > 1.0f)
        {
             //+ shootDistance
            return false;
        } else
        {
            return true;
        }
    }

    bool wasAwayFromWaypoint;

    private bool ShouldRun()
    {
        if (Target == null)
        {
            return false;
        }

        // Only run if NOT away from the player (i.e. close to player)
        return !IsAwayFromPlayer();

        //bool run = false;

        //// Check if enemy is no longer following the path (reached the end of the path when looping is set to false)
        //if (agentPath.IsFinished)
        //{
        //    // Only run when player is nearby
        //    run = !IsAwayFromPlayer();
        //}
        //else
        //{
        //    run = !IsAwayFromPlayer();
        //    //return run;

        //    //// Check not within range of path near distance to waypoints
        //    //if (!agentPath.IsAwayFromWaypoint(transform.position, PathNearDistance))
        //    //{
        //    //    run = !IsAwayFromPlayer();

        //    //    //if (run && !wasChasing)
        //    //    //{
        //    //    //    wasChasing = true;
        //    //    //}
        //    //    Debug.Log("Not away! " + IsAttacking);
        //    //} else
        //    //{
        //    //    Debug.Log("away! " + IsAttacking);
        //    //}
        //}

        //return run;
    }


    // Update is called once per frame
    void Update () {
        if (Managers.IsGameOver || IsDead)
            return;

        ApplyGravity();

        if (!IsJumping)
        {
            IsAttacking = CanAttack();

            //Debug.Log(Time.time  + "  " + IsAttacking);

            if (IsAttacking)
            {
                lastTime = Time.time;
                IsThinking = false;
                IsMoving = CheckIsMoving();
            } else
            {
                // Show Laser
                IsRunning = ShouldRun();
                ApplyMoveDirection();
                ApplyMoveSpeed();
                ApplyJumping(ShouldJump(MoveDirection));
            }

        } else
        {

        }

        if (!IsAttacking)
        {
            UpdateMovement();
        }       
        
    }

    public bool CheckIsThinking()
    {
        if (IsAiming)
        {
            // Debug.Log("hi");
            lastTime = Time.time;
            IsThinking = false;
            // wasChasing = false;
            return IsThinking;
        }

        float time;
        // Get the time the enemy stopped walking
        if (IsThinking)
        {
            time = ThinkingTime;
        } else
        {
            time = WalkingTime;
        }

        // Debug.Log(Time.time + "  " + (lastTime + time) + " IsThinking: " + IsThinking);
        // Maximum time for thinking/walking has passed, time to switch
        if (Time.time >= lastTime + time)
        {
            // Switch modes, set is thinking to reverse of whatever is currently doing
            IsThinking = !IsThinking;
            lastTime = Time.time;
        }

        //if (isThinking)
        //    Debug.Log("Is Thinking: " + isThinking);
        return IsThinking;
    }

    protected override Vector3 GetTargetDirection()
    {
        if (Target == null)
        {
            return Vector3.zero;
        }

        Vector3 targetDirection;

        if (IsRunning || IsAttacking)
        {
            //  Debug.Log("running");
            // move towards player
            targetDirection = agentPath.GetDirectionToTarget(transform.position, Target.position);
            lastTime = Time.time;
            IsThinking = false;
            // Debug.Log("running: " + targetDirection);
        } else
        {
            if (agentPath.IsFinished)
            {
                //Debug.Log("I am done! ");
                targetDirection = Vector3.zero;
            } else if (ThinkingTime > 0 && CheckIsThinking())
            {
                targetDirection = Vector3.zero;
            } else
            {
                targetDirection = agentPath.GetDirection(transform.position);
            }

            //Debug.Log("not running: " + targetDirection);
        }

        //Debug.Log(targetDirection);

        IsMoving = CheckIsMoving();
        
        return targetDirection;
    }

    private bool CheckIsMoving()
    {
        if (!agentPath.IsFinished)
        {
            // Agent is still following path and is moving when NOT thinking 
            // or if attacking, when not chasing to attack
            if (IsAttacking)
            {
                return !ShouldRun();
            }
            return !IsThinking; //  && !IsAttacking
        }
        else
        {
            return IsRunning;
        }
    }


    private bool CanAttack()
    {
        // NOTE added direction parameter

        if (Target == null)
            return false;

        //if (IsWaitForAiming)
        //    return false;

        Vector3 distanceToTarget = Target.position - transform.position;
        float sqrDistance = distanceToTarget.sqrMagnitude;

        float attackDistance = (characterController.radius * 2) + (characterController.skinWidth);
        RaycastHit hit;

        switch (WeaponState)
        {
            case WeaponStates.Pistol:
            case WeaponStates.Rifle:
                attackDistance = ShootRange;
                break;
            default:
                //attackDistance += 0.5f;
                break;
        }

        //Debug.Log("distanceToTarget: " + sqrDistance + "   attackDistance: " + (attackDistance * attackDistance));

        if (sqrDistance <= attackDistance * attackDistance)
        {

            Vector3 halfHeightPosition = Vector3.up * (characterController.height * 0.5f);

            // Get the character's upper body  position
            // Adding adjustment for cases when character controller's centre does not match the half height position
            Vector3 bodyPosition = transform.position + 
                characterController.center + (halfHeightPosition * 0.5f);

             //Debug.Log("Can Attack Player, " + "distanceToTarget: " + sqrDistance + "   attackDistance: " + (attackDistance * attackDistance) + "  position: " + transform.position);
            if (Physics.Raycast(bodyPosition, distanceToTarget.normalized, out hit, attackDistance))
            {
                //Debug.Log("Hit something: " + hit.transform.tag);
                if (hit.transform.CompareTag("Player")) {
                    // Debug.Log("Can Attack Player");
                    // Look at the target before attacking
                    Vector3 lookat = Target.transform.position;
                    lookat.y = transform.position.y;
                    transform.LookAt(lookat);
                    return true;
                }
            }
        }


        return false;
    }


    override protected bool CheckHitTarget()
    {
        RaycastHit hit;
        Transform[] attackWeapons;
        float attackDistance = ShootRange;
        

        switch (WeaponState)
        {
            case WeaponStates.Unarmed:
                // Character is fighting with hands, attack distance needs to be smaller
                attackWeapons = hands;
                attackDistance = HandsAttackRange;
                break;
            default:
                // Get the current weapons character is kitted with from the outfitter script
                attackWeapons = weaponsOutfitter.GetCurrentWeapons();
                break;
        }

        foreach (Transform weapon in attackWeapons)
        {
            Vector3 distanceToTarget = Target.position - weapon.position;
            float sqrDistance = distanceToTarget.sqrMagnitude;
            if (Physics.Raycast(weapon.position, distanceToTarget.normalized, out hit, attackDistance))
            {
                if (hit.transform.CompareTag("Player"))
                {
                    // Debug.Log("hit the " + hit.transform.name + " at " + sqrDistance);
                    Messenger<float>.Broadcast(GameEvent.HIT_PLAYER, Damage);

                    //if (WeaponState == WeaponStates.Rifle || WeaponState == WeaponStates.Pistol)
                    //{
                    //    Debug.Log("Hit point: " + hit.point + " Player position: " + hit.transform.position);
                    //    Vector3 pos = hit.point;
                    //    pos.y = weapon.position.y;
                    //    StartCoroutine(BulletIndicator(pos));
                    //}

                    return true;
                }
            }
        }        

        return false;
    }

    private void OnHitEnemy(float damage)
    {
        Hurt(damage);
        if (Health == 0)
        {
            IsDead = true;
            IsAttacking = false;
            Die(DeathTypes.b);
            StartCoroutine(KillMe());
        }
    }

    private IEnumerator KillMe()
    {
        // Wait 2 seconds before removing me
        yield return new WaitForSeconds(5);
        Destroy(this.gameObject);
        Messenger.Broadcast(GameEvent.ENEMY_KILLED);
    }
}

