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

namespace BNG {
    /// <summary>
    /// A Grabbable object that can stick to objects and deal damage
    /// </summary>
    public class Arrow : MonoBehaviour {
        Rigidbody rb;
        Grabbable grab;
        public bool Flying = false;
        public float ZVel = 0;

        public Collider ShaftCollider;
        AudioSource impactSound;

        float flightTime = 0f;
        float destroyTime = 10f; // Time in seconds to destroy arrow
        Coroutine queueDestroy;

        public Projectile ProjectileObject;

        // Get this value from the ProjectileObject
        float arrowDamage;

        // Start is called before the first frame update
        void Start() {
            rb = GetComponent<Rigidbody>();
            impactSound = GetComponent<AudioSource>();
            ShaftCollider = GetComponent<Collider>();
            grab = GetComponent<Grabbable>();

            if(ProjectileObject == null) {
                ProjectileObject = gameObject.AddComponent<Projectile>();
                ProjectileObject.Damage = 50;
                ProjectileObject.StickToObject = true;
                ProjectileObject.enabled = false;
            }

            arrowDamage = ProjectileObject.Damage;
        }

        void FixedUpdate() {

            bool beingHeld = grab != null && grab.BeingHeld;

            // Align arrow with velocity
            if (!beingHeld && rb != null && rb.velocity != Vector3.zero && Flying && ZVel > 0.02) {
                rb.rotation = Quaternion.LookRotation(rb.velocity);
            }

            ZVel = transform.InverseTransformDirection(rb.velocity).z;

            if (Flying) {
                flightTime += Time.fixedDeltaTime;
            }

            // Cancel Destroy if we just picked this up
            if(queueDestroy != null && grab != null && grab.BeingHeld) {
                StopCoroutine(queueDestroy);
            }
        }

        public void ShootArrow(Vector3 shotForce) {

            flightTime = 0f;
            Flying = true;

            transform.parent = null;

            rb.isKinematic = false;
            rb.useGravity = true;
            rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
            rb.constraints = RigidbodyConstraints.None;
            rb.AddForce(shotForce, ForceMode.VelocityChange);
            StartCoroutine(ReEnableCollider());
            queueDestroy = StartCoroutine(QueueDestroy());
        }

        IEnumerator QueueDestroy() {
            yield return new WaitForSeconds(destroyTime);

            if (grab != null && !grab.BeingHeld && transform.parent == null) {
                Destroy(this.gameObject);
            }
        }

        IEnumerator ReEnableCollider() {

            // Wait a few frames before re-enabling collider on bow shaft
            // This prevents the arrow from shooting ourselves, the bow, etc.
            // If you want the arrow to still have physics while attached,
            // parent a collider to the arrow near the tip
            int waitFrames = 3;
            for (int x = 0; x < waitFrames; x++) {
                yield return new WaitForFixedUpdate();
            }

            ShaftCollider.enabled = true;
        }

        private void OnCollisionEnter(Collision collision) {

            // Ignore parent collisions
            if (transform.parent != null && collision.transform == transform.parent) {
                return;
            }

            // Don't count collisions if being held
            if(grab != null && grab.BeingHeld) {
                return;
            }

            // Don't Count Triggers
            if(collision.collider.isTrigger) {
                return;
            }

            string colNameLower = collision.transform.name.ToLower();
           
            // Ignore other very close bows and arrows
            if (flightTime < 1 && (colNameLower.Contains("arrow") || colNameLower.Contains("bow"))) {
                Physics.IgnoreCollision(collision.collider, ShaftCollider, true);
                return;
            }

            // ignore player collision if quick shot
            if (flightTime < 1 && collision.transform.name.ToLower().Contains("player")) {
                Physics.IgnoreCollision(collision.collider, ShaftCollider, true);
                return;
            }

            // Damage if possible
            float zVel = System.Math.Abs(transform.InverseTransformDirection(rb.velocity).z);
            bool doStick = true;
            if (zVel > 0.02f && !rb.isKinematic) {
                
                Damageable d = collision.gameObject.GetComponent<Damageable>();
                if (d) {
                    d.DealDamage(arrowDamage, collision.GetContact(0).point, collision.GetContact(0).normal, true, gameObject, collision.collider.gameObject);
                }

                // Don't stick to dead objects
                if (d != null && d.Health <= 0) {
                    doStick = false;
                }
            }
            
            // Check to stick to object
            if (!rb.isKinematic && Flying) {
                if (zVel > 0.02f) {
                    if (grab != null && grab.BeingHeld) {
                        grab.DropItem(false, false);
                    }
                    if (doStick) {
                        tryStickArrow(collision);
                    }

                    Flying = false;

                    playSoundInterval(2.462f, 2.68f);
                }                                
            }            
        }

        // Attach to collider
        void tryStickArrow(Collision collision) {

            Rigidbody colRigid = collision.collider.GetComponent<Rigidbody>();
            transform.parent = null; // Start out with arrow being in World space

            // If the collider is static then we don't need to do anything. Just stop it.
            if (collision.gameObject.isStatic) {
                rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
                rb.isKinematic = true;
            }
            // Next try attaching to rigidbody via FixedJoint
            else if (colRigid != null && !colRigid.isKinematic) {
                FixedJoint joint = gameObject.AddComponent<FixedJoint>();
                joint.connectedBody = colRigid;
                joint.enableCollision = false;
                joint.breakForce = float.MaxValue;
                joint.breakTorque = float.MaxValue;
            }
            else if (colRigid != null && colRigid.isKinematic && collision.transform.localScale == Vector3.one) {
                transform.SetParent(collision.transform);
                rb.useGravity = false;
                rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
                rb.isKinematic = true;
                rb.constraints = RigidbodyConstraints.FreezeAll;
                rb.WakeUp();
            }
            // Finally, try parenting or just setting the arrow to kinematic
            else {
                if (collision.transform.localScale == Vector3.one) {
                    transform.SetParent(collision.transform);
                    rb.constraints = RigidbodyConstraints.FreezeAll;
                }
                else {
                    rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
                    rb.useGravity = false;
                    rb.isKinematic = true;
                }
            }
        }

        void playSoundInterval(float fromSeconds, float toSeconds) {
            if (impactSound) {

                if (impactSound.isPlaying) {
                    impactSound.Stop();
                }

                impactSound.time = fromSeconds;
                impactSound.pitch = Time.timeScale;
                impactSound.Play();
                impactSound.SetScheduledEndTime(AudioSettings.dspTime + (toSeconds - fromSeconds));
            }
        }
    }
}