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

public class Character : MonoBehaviour {

    //[SerializeField]
    //protected WeaponStates WeaponState;

    const float GRAVITY = 20.0f;
    const float MIN_AIR_TIME = 0.15f; // 0.15 sec
    const float SLOPE = 0.7f;

    private float moveSpeed = 0.15f;
    public float walkSpeed = 2.0f;
    public float runSpeed = 6.0f;
    public float jumpSpeed = 8.0f;
    public float rotationSpeed = 15.0f;
    public bool slideOnSlope = false;

    [SerializeField]
    protected GameObject bulletPrefab;

    protected float lookAheadDistance = 20.0f;

    protected bool isPlayer = false;

    protected bool IsMoving { get; set; }
    protected bool IsAttacking { get; set; }
    protected bool IsThinking { get; set; }
    protected bool IsAiming { get; set; }
    protected bool IsJumping { get; set; }
    protected bool IsRunning { get; set; }
    protected bool IsHurting { get; set; }
    protected bool IsWaitForAiming { get; set; }

    protected bool IsDead { get; set; }

    protected virtual WeaponStates WeaponState { get; set; }

    protected Outfitter weaponsOutfitter { get; private set; }

    protected Transform[] hands { get; private set; }

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

    [SerializeField]
    private float _handsAttackRange = 1.0f;
    protected float HandsAttackRange
    {
        get { return _handsAttackRange; }
        set { _handsAttackRange = value; }
    }

    [SerializeField]
    private float _Damage = 1.0f;
    protected float Damage
    {
        get { return _Damage; }
        set { _Damage = value; }
    }

    [SerializeField]
    private float _Health = 100.0f;
    protected float Health {
        get { return _Health; }
        set { _Health = value; }
    }

    protected float MaxHealth { get; set; }

    protected float HealthPercent
    {
        get { return Health / MaxHealth * 100; }
    }


    protected Vector3 CurrentEulerAngle { get; set; }

    private bool IsNonCombat {
        get
        {
            if (IsAttacking)
            {
                return false;
            } else
            {
                if (IsMoving && IsRunning)
                {
                    return false;
                }
                return true;
            }
        }
    }

    private bool IsGrounded
    {
        get { return characterController.isGrounded; }
    }

    private bool IsFalling
    {
        get { return inAirTime > MIN_AIR_TIME; }
    }

    protected Vector3 AttackPosition { get; set; }

    protected Vector3 MoveDirection
    {
        get { return moveDirection; }
    }

    protected Animator animator;
    protected CharacterController characterController;


    protected float currentSpeed = 0.0f;
    float verticalSpeed = 0.0f;
    float inAirTime = 0.0f;
    float inAirStartTime;
    Vector3 slideDown;
    Vector3 currentPosition;
    Vector3 movement;
    Vector3 moveDirection;
    bool wasAttacking;



    protected virtual void Awake()
    {
        characterController = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();


        weaponsOutfitter = GetComponent<Outfitter>();

        Messenger<WeaponStates, string>.AddListener(GameEvent.WEAPON_STATE_CHANGE_COMPLETED, OnWeaponStateChangeCompleted);
    }

    private void OnDestroy()
    {
        Messenger<WeaponStates, string>.RemoveListener(GameEvent.WEAPON_STATE_CHANGE_COMPLETED, OnWeaponStateChangeCompleted);
    }

    // Use this for initialization
    protected virtual void Start ()
    {        
        currentPosition = transform.position;
        GetHands();
        MaxHealth = Health;
    }

    void GetHands()
    {
        // Initialise the hands variable
        hands = new Transform[2];

        // Locate all the child objects in the character game object
        Transform[] children = GetComponentsInChildren<Transform>();

        // Loop through each of them to find the hands
        foreach (Transform child in children)
        {
            if (child.name.Equals("L Hand"))
            {
                hands[0] = child;
            } else if (child.name.Equals("R Hand"))
            {
                hands[1] = child;
            }
        }
    }
	
	// Update is called once per frame
	void Update () {
		
	}

    private void UpdateAnimatorParameters(bool isNonCombat, bool isIdling, string triggerName)
    {
        animator.SetBool("Idling", isIdling);
        animator.SetBool("NonCombat", isNonCombat);

        // Check if the trigger name has been specified before setting the trigger
        if (string.IsNullOrEmpty(triggerName) == false)
        {
            animator.SetTrigger(triggerName);
        }
    }

    protected void ApplyGravity()
    {
        // Check if character is currently grounded
        if (IsGrounded)
        {
            // Character is on ground, no need for gravity, set jumping variables to default
            IsJumping = false;
            verticalSpeed = 0.0f;
            inAirTime = 0.0f;
            inAirStartTime = Time.time;
        } else
        {
            // Character is in air, reduce vertical speed gradually and update how long has been in air
            verticalSpeed -= GRAVITY * Time.deltaTime;
            inAirTime = Time.time - inAirStartTime;
        }
    }


    protected virtual Vector3 GetTargetDirection()
    {
        return Vector3.zero;
    }

    protected virtual bool CheckHitTarget()
    {
        return false;
    }


    protected void ApplyMoveDirection()
    {
        Vector3 targetDirection = GetTargetDirection();
        if (targetDirection != Vector3.zero)
        {
            // there is a change in direction, gradually change the direction and record the change 
            moveDirection = Vector3.Slerp
                (moveDirection, targetDirection, rotationSpeed * Time.deltaTime).normalized;
        } else
        {
            moveDirection = Vector3.zero;
        }
    }

    protected void ApplyMoveSpeed()
    {
        if (IsRunning)
        {
            if (moveDirection != Vector3.zero)
            {
                // Running and moving (not running on spot), increment speed to a max of running speed.
                // Will set to smaller of currentSpeed (which you are increasing) and running speed
                currentSpeed += moveSpeed;
                currentSpeed = Mathf.Min(currentSpeed, runSpeed);
            }
        } else
        {
            // Not running, but moving (walking on the spot), decrease speed to a larger of currentSpeed (which you are decreasing) and walking speed
            if (moveDirection != Vector3.zero)
            {
                currentSpeed -= moveSpeed;
                currentSpeed = Mathf.Max(walkSpeed, currentSpeed);
            } else
            {
                currentSpeed = 0.0f;
            }
        }
    }

    protected void ApplyJumping (bool charIsJumping)
    {
        if (charIsJumping)
        {
            verticalSpeed = jumpSpeed;
            IsJumping = true;
            inAirTime = 0.0f;
            inAirStartTime = Time.time;            
        }
    }


    protected void ApplyAttacking(Vector3 hitTarget)
    {
        //// establish the point that we hit with the mouse
        //AttackPosition = hitTarget;

        ////use our height for the LOOKAT function, so we stay level and dont lean the character in weird angles
        //AttackPosition.y = transform.position.y;

        // establish the point that we hit with the mouse
        //use our height for the LOOKAT function, so we stay level and dont lean the character in weird angles
        AttackPosition = new Vector3(hitTarget.x, transform.position.y, hitTarget.z);

        //we need the Vector delta which is an un-normalized direction vector
        Vector3 attackOffset = AttackPosition - transform.position * lookAheadDistance;

        //look 20 meters ahead, so we don't spin around wildly if mecanim moves past the target
        AttackPosition = transform.position + attackOffset.normalized * lookAheadDistance;
    }

    protected void UpdateMovement()
    {
        // Did not do slide down

        movement = ((moveDirection * currentSpeed) + new Vector3(0, verticalSpeed, 0));
        movement *= Time.deltaTime;

        // Debug.Log("Movement: " + movement + "   MoveDirection: " + moveDirection);

        if (movement != Vector3.zero)
        {
            characterController.Move(movement);
        }

        transform.eulerAngles = CurrentEulerAngle;
        if (moveDirection != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(moveDirection);
        }
        CurrentEulerAngle = transform.eulerAngles;
    }


    private void OnAnimatorMove()
    {
        UpdateAnimator();
    }

    private void UpdateAnimator()
    {
        if (!animator)
        {
            return;
        }

        if (!Managers.IsGameOver)
        {
         
            if (IsMoving )
            {
                //wasAttacking = false;
            }

            // Is character attacking
            // IsNonCombat will return true or false, depending on the characters current state
            // It is used to put the character in a noncombat or combat stance
            // Note, idling parameter has to be true for character to be in combat stance
            if (IsAttacking)
            {
                //Debug.Log("Attacking");
                // Check was not attacking at last check to avoid triggering animator unnecessarily
                if (!wasAttacking)
                {

                    // Update the animator parameters - combat-true, idling has to be true to attack, USE weapon state
                    UpdateAnimatorParameters(IsNonCombat, true, "Use");

                    //animator.SetTrigger("Use");
                    //animator.SetBool("Idling", true);

                    // Set was attacking to true, so next frame will not bother animator if still attacking
                    wasAttacking = true;

                    // Get current playback time of current animation state
                    float playbackTime = animator.GetCurrentAnimatorStateInfo(0).length;
                    //Debug.Log(playbackTime);

                    
                    StartCoroutine(WaitForNextAttack(playbackTime)); //1.0f;
                } else
                {
                    // UpdateAnimatorParameters(IsNonCombat, false, string.Empty);
                    // Debug.Log("Attacking IsNonCombat: " + IsNonCombat);
                    //UpdateAnimatorParameters(IsNonCombat, false, string.Empty);
                    //float playbackTime = animator.GetCurrentAnimatorStateInfo(0).length;

                    //if (!isPlayer)
                    //    StartCoroutine(WaitForAttack(1.0f));
                    //Debug.Log(HitTarget());
                }
            } else
            {
                // Debug.Log("Not attacking");
                // TO DO Change to hit cod

                if (IsMoving)
                {
                    UpdateAnimatorParameters(IsNonCombat, false, string.Empty);
                } else
                {
                    UpdateAnimatorParameters(IsNonCombat, true, string.Empty);
                    //animator.SetBool("NonCombat", true);

                    //if (isPlayer)
                    //{
                    //    Debug.Log("player" + Time.time + " " + IsNonCombat);
                    //    Debug.Log("Non Combat: " + animator.GetBool("NonCombat") + " Idling: " + animator.GetBool("Idling") + " Use: ");
                    //}
                }
            }
        } else
        {
            
        }
    }



    protected void ApplyWeaponChange(WeaponStates newWeaponState)
    {
        // Broad cast a weapon state change request
        //Debug.Log("Before change: " + gameObject.name);

        Messenger<WeaponStates, string>.Broadcast(GameEvent.WEAPON_STATE_CHANGED, 
            newWeaponState, gameObject.name);
    }

    protected void ApplyWeaponChange(int weaponNumber)
    {
        // Check that the integer can be converted to a valid weapon state 
        // if yes, cast (convert) it to the corresponding weapon state
        // Call the other ApplyWeaponChange method to change the character's weapon
        if (System.Enum.IsDefined(typeof(WeaponStates), weaponNumber))
        {
            WeaponStates newWeaponState = (WeaponStates)weaponNumber;
            //Debug.Log(newWeaponState);
            ApplyWeaponChange(newWeaponState);
        }
    }

    private void OnWeaponStateChangeCompleted(WeaponStates updatedWeaponState, string character)
    {
        // If this was the character that changed its weapon, update the current weapon state
        if (character == gameObject.name)
        {
            WeaponState = updatedWeaponState;
        }        
    }


    IEnumerator WaitForNextAttack(float waitTime)
    {

        yield return new WaitForSeconds(waitTime);

        CheckHitTarget();
        wasAttacking = false;
        // IsWaitForAiming = false;

    }

    protected virtual void UpdateHitPoint (Collision collision)
    {
        if (!Managers.IsGameOver)
        {
            Debug.Log(collision.transform.tag);
            Debug.Log(collision.transform.name);
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        UpdateHitPoint(collision);
    }

    private void OnTriggerEnter(Collider other)
    {
        if (!Managers.IsGameOver)
        {
            Debug.Log("Trigger " + other.transform.tag);
            Debug.Log(other.transform.name);
        }
    }


    private void OnControllerColliderHit(ControllerColliderHit hit)
    {


        if (!Managers.IsGameOver)
        {
            if (hit.transform.CompareTag("Player") && IsAttacking)
            {
                Debug.Log(gameObject.name + " hit " + hit.transform.tag + " " + IsAttacking);
                Debug.Log(gameObject.name + " hit " + hit.transform.name + " " + IsAttacking);
            }
        }
        //slideDown = Vector3.zero;
        //if (slideOnSlope && IsGrounded && hit.collider.CompareTag(""))
        //{

        //}
    }

    protected void Hurt (float damage)
    {
        Health = Mathf.Max(Health - damage, 0);

        if (damage > 0)
        {
            // Hurt, show some pain
            animator.SetTrigger("Pain");
        }
    }

    protected void Die (DeathTypes deathType)
    {
        // Set default death type
        int deathNum = 1;

        // Check if the passed in death type exists
        if (System.Enum.IsDefined(typeof(DeathTypes), deathType))
        {
            // Yes, use it
            deathNum = (int)deathType + 1;
        }

        // Play the corresponding death animation
        animator.SetInteger("Death", deathNum);
    }

    protected IEnumerator BulletIndicator(Vector3 pos)
    {
        if (bulletPrefab == null)
            yield return null;
        // creates a sphere at a point in the scene hit and then removes that sphere a second later
        // cursor problem fixed next
        GameObject bullet = GameObject.Instantiate(bulletPrefab);
        bullet.transform.position = pos;

        yield return new WaitForSeconds(1);

        Destroy(bullet);
    }
}
