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

public class Player : MonoBehaviour
{
    // ============================================================
    [Header("Stats")]
    // ============================================================

    [SerializeField]
    private float _speed = 5.0f;

    [SerializeField]
    private float _jumpForce = 300.0f;

    [SerializeField]
    private float _sensitivity = 3.0f;

    [SerializeField]
    private int _ammo1 = 100;

    [SerializeField]
    private int _ammo2 = 100;

    [SerializeField]
    private float _slingshotForce = 1000f;

    [SerializeField]
    private int _equippedSlotNumber = 1;

    // ============================================================
    [Header("Refs")]
    // ============================================================

    [SerializeField]
    private GameObject _cameraObject;

    [SerializeField]
    private GameObject _armature;

    [SerializeField]
    private GameObject _handAttachPoint;

    [SerializeField]
    private GameObject _slot1Prefab;

    [SerializeField]
    private GameObject _slot2Prefab;

    [SerializeField]
    private GameObject _ammo1Prefab;

    [SerializeField]
    private GameObject _ammo2Prefab;

    [SerializeField]
    private AudioClip _whooshClip;
    private GameManager _gameManager;

    private GameObject _weaponInstance;

    private GameObject _projectileSpawnPoint;

    private Rigidbody _rb;

    private CapsuleCollider _collider;

    private Animator _animator;

    private Weapon _activeWeapon;

    private Camera _camera;

    // ============================================================
    // Private members
    // ============================================================

    private Vector2 _mouseLook;

    private bool _isAnimationLocked = false;
    private bool _dead = false;

    void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
        _rb = GetComponent<Rigidbody>();
        _collider = GetComponent<CapsuleCollider>();
        _animator = _armature.GetComponent<Animator>();
        _camera = _cameraObject.GetComponent<Camera>();
        if (_slot1Prefab != null)
        {
            EquipWeapon(1);
        }
        if (_gameManager == null)
        {
            _gameManager = GameObject.Find("Game Manager").GetComponent<GameManager>();
        }
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Cursor.lockState = CursorLockMode.None;
        }

        if (!_dead)
        {
            HandleStrafeInput();
            HandleMouseLook();
            HandleJumpInput();
            HandleFireInput();
            HandleChangeWeaponInput();
        }
    }

    void HandleStrafeInput()
    {
        Vector3 normalizedMoveDelta = (new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"))).normalized;
        transform.Translate(normalizedMoveDelta * _speed * Time.deltaTime);
    }

    void HandleMouseLook()
    {
        Vector2 mouseDelta = _sensitivity * new Vector2(
            Input.GetAxisRaw("Mouse X"),
            Input.GetAxisRaw("Mouse Y")
        );

        _mouseLook = new Vector2(
            _mouseLook.x + mouseDelta.x,
            Mathf.Clamp(_mouseLook.y + mouseDelta.y, -90f, 90f)
        );

        // Look up and down, relative to the camera
        _cameraObject.transform.localRotation = Quaternion.AngleAxis(-_mouseLook.y, Vector3.right);

        // Rotate left and right
        transform.localRotation = Quaternion.AngleAxis(_mouseLook.x, transform.up);
    }

    void HandleJumpInput()
    {
        if (Input.GetButtonDown("Jump") && IsGrounded())
        {
            _rb.AddForce(Vector3.up * _jumpForce);
        }
    }

    void HandleFireInput()
    {
        if (_isAnimationLocked == true)
        {
            return;
        }
        if (Input.GetButtonDown("Fire1"))
        {
            if (_activeWeapon.isMelee == true)
            {
                _animator.SetTrigger("Melee");
                StartCoroutine(LockAnimationRoutine(0, (17f / 24f)));
                StartCoroutine(MeleeAttackWindupAndSwing());
            }
            else
            {
                if (_ammo1 > 0 && GetActiveAmmoPrefab() != null)
                {
                    RangedAttack();
                }
            }
        }
    }

    IEnumerator MeleeAttackWindupAndSwing()
    {
        yield return new WaitForSeconds(0.25f); // this should probably be based on the animation per weapon
        MeleeAttack();
    }

    void MeleeAttack()
    {
        // cast a ray in front of us. if it hits an enemy, damage it
        Ray ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
        RaycastHit hit;
        Debug.DrawRay(ray.origin, ray.direction, Color.red, 1f);
        if (Physics.Raycast(ray, out hit, _activeWeapon.meleeDistance))
        {
            if (hit.collider.gameObject.tag == "Enemy")
            {
                Debug.Log("Hit an enemy");
                hit.collider.gameObject.GetComponent<Enemy>().TakeDamage(_activeWeapon.meleeDamage);
                hit.collider.gameObject.GetComponent<Rigidbody>().velocity = transform.forward * 20f;
            }
        }
        AudioSource.PlayClipAtPoint(_whooshClip, transform.position, 0.5f);
    }

    void RangedAttack()
    {
        GameObject spawnParent = _projectileSpawnPoint ?? _handAttachPoint;
        GameObject projectile = Instantiate(
            GetActiveAmmoPrefab(),
            spawnParent.transform.position,
            Quaternion.identity
        );
        Rigidbody projectileRb = projectile.GetComponent<Rigidbody>();
        // Fudge the direction to actually hit what's centered in the reticle

        Ray ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
        RaycastHit hit;
        // Check whether your are pointing to something so as to adjust the direction
        Vector3 targetPoint;
        if (Physics.Raycast(ray, out hit))
            targetPoint = hit.point;
        else
            targetPoint = ray.GetPoint(50);

        Vector3 direction = (targetPoint - spawnParent.transform.position).normalized;
        projectileRb.AddForce(direction * _slingshotForce);
        projectile.transform.rotation = _cameraObject.transform.rotation;

        AudioSource.PlayClipAtPoint(_whooshClip, transform.position, 0.5f);
    }

    void HandleChangeWeaponInput()
    {
        if (_isAnimationLocked)
        {
            return;
        }
        if (Input.GetButtonDown("Change1") && _equippedSlotNumber != 1)
        {
            StartCoroutine(ChangeWeaponAnimationRoutine(1));
        }
        else if (Input.GetButtonDown("Change2") && _equippedSlotNumber != 2)
        {
            StartCoroutine(ChangeWeaponAnimationRoutine(2));
        }
    }

    IEnumerator ChangeWeaponAnimationRoutine(int slot)
    {
        _animator.SetTrigger("Reload");
        StartCoroutine(LockAnimationRoutine(0, 1f));
        yield return new WaitForSeconds(.5f);
        EquipWeapon(slot);
    }

    IEnumerator LockAnimationRoutine(float delay, float duration)
    {
        yield return new WaitForSeconds(delay);
        _isAnimationLocked = true;
        yield return new WaitForSeconds(duration);
        _isAnimationLocked = false;
    }

    void EquipWeapon(int slot)
    {
        // Despawn the existing instance
        if (_weaponInstance != null)
        {
            Destroy(_weaponInstance);
        }

        // Figure out which prefab to use
        _equippedSlotNumber = slot;
        GameObject prefab = null;
        if (slot == 1)
        {
            prefab = _slot1Prefab;
        }
        else if (slot == 2)
        {
            prefab = _slot2Prefab;
        }
        if (prefab == null)
        {
            Debug.LogError("No prefab for slot " + slot);
            return;
        }

        // Create the new instance
        _weaponInstance = Instantiate(prefab, _handAttachPoint.transform);
        _activeWeapon = _weaponInstance.GetComponent<Weapon>();
        if (!_activeWeapon.isMelee)
        {
            _projectileSpawnPoint = _weaponInstance.GetComponent<Weapon>().GetProjectileSpawnPoint();
        }
        else
        {
            _projectileSpawnPoint = null;
        }
    }

    void SwapWeaponPrefabAndEquip(int slot, GameObject prefab)
    {
        if (slot == 1)
        {
            _slot1Prefab = prefab;
        }
        else
        {
            _slot2Prefab = prefab;
        }
        StartCoroutine(ChangeWeaponAnimationRoutine(slot));
    }

    bool IsGrounded()
    {
        float distanceToGround = _collider.bounds.extents.y;
        return Physics.CheckCapsule(
            _collider.bounds.center,
            new Vector3(
                _collider.bounds.center.x,
                _collider.bounds.min.y - 0.1f,
                _collider.bounds.center.z
            ),
            0.18f,
            LayerMask.GetMask("Terrain")
        );
    }

    GameObject GetActiveAmmoPrefab()
    {
        if (_equippedSlotNumber == 1)
        {
            return _ammo1Prefab;
        }
        else
        {
            return _ammo2Prefab;
        }
    }

    void SetAmmoType(GameObject prefab, int ammo)
    {
        if (_equippedSlotNumber == 1)
        {
            _ammo1Prefab = prefab;
            _ammo1 = ammo;
        }
        else
        {
            _ammo2Prefab = prefab;
            _ammo2 = ammo;
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Enemy" && !_dead)
        {
            Die();
        }
        else if (other.tag == "Pickup")
        {
            Pickup pickup = other.gameObject.GetComponent<Pickup>();
            if (pickup.pickupType == PickupType.Weapon)
            {
                SwapWeaponPrefabAndEquip(_equippedSlotNumber, pickup.pickupPrefab);
                Destroy(pickup.gameObject);
            }
            else
            {
                SetAmmoType(pickup.pickupPrefab, 100);
                Destroy(pickup.gameObject);
            }
        }
    }

    private void Die()
    {
        Debug.Log("dead");
        _dead = true;
        transform.localRotation = Quaternion.AngleAxis(-90, transform.right);
        _rb.constraints = RigidbodyConstraints.FreezeAll;
        _gameManager.GameOver();
    }
}
