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

namespace Lightsaber
{
    public class LightsaberBehaviour : MonoBehaviour
    {
        public bool Activated;
        [SkipSerialisation]
        public AudioPreset AudioPreset;

        private LineRenderer lineRenderer;
        private PhysicalBehaviour phys;

        private AudioSource audioSource;
        private AudioSource swingSource;
        private AudioClip ignite;
        private AudioClip deactivate;
        private AudioClip idle;
        private AudioClip swing;
        private AudioClip[] hits;
        private RaycastHit2D[] buffer = new RaycastHit2D[16];
        private Color color;

        private Dictionary<GameObject, JointPhysPair> joints = new Dictionary<GameObject, JointPhysPair>();
        private const float strength = 4f;
        public float MaxSaberLength = 1.5f;
        public float MinSaberLength = 0.2f;
        private Transform gripper;

        private float useDelay = 0;

        public bool DoubleBladed;
        public bool Crossguard;

        private GameObject componentChild;
            
        private bool isBusy;
        private Vector3[] positions = { };

        private struct JointPhysPair
        {
            public FrictionJoint2D joint;
            public PhysicalBehaviour phys;

            public JointPhysPair(FrictionJoint2D joint, PhysicalBehaviour phys)
            {
                this.joint = joint;
                this.phys = phys;
            }
        }

        private void Awake()
        {
            componentChild = new GameObject("componentChild");
            componentChild.transform.SetParent(transform);
            componentChild.layer = gameObject.layer;
            componentChild.transform.localPosition = Vector3.zero;
            componentChild.transform.localRotation = Quaternion.identity;
            componentChild.AddComponent<Optout>();
            phys = GetComponentInParent<PhysicalBehaviour>();
            CreateLineRenderer();
        }

        private void CreateAudioSource()
        {
            if (!audioSource)
            {
                audioSource = componentChild.AddComponent<AudioSource>();
                audioSource.spatialBlend = 1;
                audioSource.minDistance = 3;
                audioSource.maxDistance = 25;
                audioSource.dopplerLevel = 0;
                audioSource.playOnAwake = false;
                audioSource.clip = idle;
                audioSource.loop = true;
                audioSource.volume = 0.5f;
                Global.main.AddAudioSource(audioSource);
            }

            swingSource = componentChild.AddComponent<AudioSource>();
            swingSource.spatialBlend = 1;
            swingSource.minDistance = 5;
            swingSource.maxDistance = 50;
            swingSource.playOnAwake = false;
            swingSource.clip = swing;
            swingSource.loop = true;
            swingSource.volume = 0f;
            Global.main.AddAudioSource(swingSource);
        }

        private void CreateLineRenderer()
        {
            lineRenderer = componentChild.AddComponent<LineRenderer>();
            lineRenderer.sharedMaterial = ModAPI.FindMaterial("VeryBright");
            lineRenderer.useWorldSpace = false;
            lineRenderer.positionCount = 2;
            lineRenderer.numCapVertices = 2;
            lineRenderer.widthMultiplier = 0.04f;
            lineRenderer.sortingLayerID = SortingLayer.NameToID("Background");
        }

        private void Start()
        {
            if (DoubleBladed)
            {
                var test = new GameObject("otherBlade");
                test.transform.SetParent(componentChild.transform);
                test.transform.localPosition = Vector3.zero;
                test.transform.localEulerAngles = new Vector3(0, 0, 180);
                test.layer = gameObject.layer;

                LightsaberBehaviour otherBlade = test.AddComponent<LightsaberBehaviour>();
                otherBlade.AudioPreset = AudioPreset;
                otherBlade.MaxSaberLength = MaxSaberLength;
                otherBlade.MinSaberLength = MinSaberLength;
                otherBlade.SetColor(color);
            }

            if (Crossguard)
            {
                var test = new GameObject("otherBlade");
                test.transform.SetParent(componentChild.transform);
                test.transform.localPosition = new Vector3(0f, 0.14f, 0f);
                test.transform.localEulerAngles = new Vector3(0, 0, 90);
                test.layer = gameObject.layer;

                LightsaberBehaviour otherBlade = test.AddComponent<LightsaberBehaviour>();
                otherBlade.AudioPreset = AudioPreset;
                otherBlade.MaxSaberLength = MaxSaberLength * 0.2f;
                otherBlade.MinSaberLength = MinSaberLength * 0.2f;
                otherBlade.SetColor(color);
                
                var bruh = new GameObject("otherotherBlade");
                bruh.transform.SetParent(componentChild.transform);
                bruh.transform.localPosition = new Vector3(0f, 0.14f, 0f);
                bruh.transform.localEulerAngles = new Vector3(0, 0, -90);
                bruh.layer = gameObject.layer;

                LightsaberBehaviour otherotherBlade = bruh.AddComponent<LightsaberBehaviour>();
                otherotherBlade.AudioPreset = AudioPreset;
                otherotherBlade.MaxSaberLength = MaxSaberLength * 0.2f;
                otherotherBlade.MinSaberLength = MinSaberLength * 0.2f;
                otherotherBlade.SetColor(color);
            } 

            positions = new[] {
                new Vector3(0, MinSaberLength),
                new Vector3(0, MinSaberLength)
            };

            lineRenderer.SetPositions(positions);
            ignite = ModAPI.LoadSound(AudioPreset.On);
            deactivate = ModAPI.LoadSound(AudioPreset.Off);
            hits = new[]
            {
                ModAPI.LoadSound("sfx/hit_1.mp3"),
                ModAPI.LoadSound("sfx/hit_2.mp3"),
                ModAPI.LoadSound("sfx/hit_3.mp3"),
                ModAPI.LoadSound("sfx/hit_4.mp3"),
            };
            idle = ModAPI.LoadSound(AudioPreset.Idle);
            swing = ModAPI.LoadSound(AudioPreset.Swing);

            CreateAudioSource();
            UpdateActivation();
        }

        private void Use()
        {
            StartCoroutine(ChangeState());
        }

        private void UpdateActivation()
        {
            lineRenderer.enabled = Activated;
            positions[1].y = GetTarget();
            lineRenderer.SetPositions(positions);
            if (!Activated)
            {
                foreach (var item in joints)
                    Destroy(item.Value.joint);
                joints.Clear();
                audioSource.Stop();
                swingSource.Stop();
            }
            else
            {
                audioSource.Play();
                swingSource.Play();
            }
        }

        private void FixedUpdate()
        {
            if (Activated)
            {
                swingSource.volume = Mathf.Min(0.8f, 0.01f * phys.rigidbody.GetRelativePointVelocity(new Vector2(0, MaxSaberLength)).magnitude);
                /*
                swingSource.volume = Mathf.Min(0.8f,
                    0.01f * phys.rigidbody.GetRelativePointVelocity(new Vector2(0, MaxSaberLength)).magnitude
                    * Mathf.Lerp(1, Mathf.Sin(Mathf.Abs(phys.rigidbody.angularVelocity) * Time.time * 0.0009f) / 2 + 0.5f, Mathf.Abs(phys.rigidbody.angularVelocity * 0.0005f))
                    );
                */
                var minPos = transform.TransformPoint(new Vector2(0, MinSaberLength));
                var maxPos = transform.TransformPoint(new Vector2(0, MaxSaberLength));
                var up = transform.up;

                CastDamageLine(minPos, maxPos);

                foreach (var item in joints)
                {
                    if (!item.Key || !item.Value.phys || !item.Value.joint) continue;
                    var attachedPhys = item.Value.phys;
                    attachedPhys.BurnProgress += Time.fixedDeltaTime / 25f;
                    var newAnchor = minPos + up * Vector2.Distance(minPos, attachedPhys.spriteRenderer.bounds.ClosestPoint(minPos));
                    item.Value.joint.anchor = attachedPhys.transform.InverseTransformPoint(newAnchor);
                }
            }
        }

        private void CastDamageLine(Vector2 start, Vector2 end)
        {
            if (phys.beingHeldByGripper && gripper == null)
            {
                var near = Physics2D.OverlapPointAll(transform.position);
                foreach (var item in near)
                {
                    if (item.GetComponent<GripBehaviour>())
                    {
                        gripper = item.transform.root;
                        break;
                    }
                }
            }
            else if (!phys.beingHeldByGripper)
                gripper = null;

            var rayHits = Physics2D.LinecastNonAlloc(start, end, buffer);
            List<GameObject> hitGameObjects = new List<GameObject>();
            for (int i = 0; i < rayHits; i++)
            {
                var hit = buffer[i];
                if (hit.transform.root == transform.root) continue;
                if (gripper && hit.transform.root == gripper) continue;
                PhysicalBehaviour otherPhys;
                if (!hit.transform.TryGetComponent(out otherPhys)) continue;
                hitGameObjects.Add(otherPhys.gameObject);
                if (joints.ContainsKey(hit.transform.gameObject))
                {
                    if (Random.value > 0.99f)
                    {
                        ModAPI.CreateParticleEffect("Flash", hit.point);
                        phys.PlayClipOnce(hits.PickRandom());
                    }
                }
                else
                {
                    HandleNewVictim(otherPhys, hit.point);
                }
            }

            foreach (var pair in joints)
            {
                if (!pair.Key || !pair.Value.phys || !pair.Value.joint) continue;
                var attachedPhys = pair.Value.phys;
                if (!hitGameObjects.Contains(attachedPhys.gameObject))
                    Destroy(pair.Value.joint);
            }

            joints.RemoveAll(v => v.joint == null);
        }

        private void HandleNewVictim(PhysicalBehaviour otherPhys, Vector2 pos)
        {
            ModAPI.CreateParticleEffect("Flash", pos);
            phys.PlayClipOnce(hits.PickRandom());
            otherPhys.BroadcastMessage("Damage", 1500, SendMessageOptions.DontRequireReceiver);

            var joint = otherPhys.gameObject.AddComponent<FrictionJoint2D>();
            joint.autoConfigureConnectedAnchor = true;
            joint.enableCollision = true;
            joint.maxForce = strength;
            joint.connectedBody = phys.rigidbody;
            joint.maxTorque = strength;
            joint.anchor = otherPhys.transform.InverseTransformPoint(pos);
            joints.Add(otherPhys.gameObject, new JointPhysPair(joint, otherPhys));
            if (Random.value > 0.8f)
            {
                otherPhys.BroadcastMessage("Slice", SendMessageOptions.DontRequireReceiver);
                CirculationBehaviour circ = null;
                if (otherPhys.gameObject.TryGetComponent(out circ))
                {
                    circ.BloodLossRateMultiplier = 0;
                    if (circ.Source)
                        circ.Source.BloodLossRateMultiplier = 0;
                    foreach (var item in circ.PushesTo)
                        if (item)
                            item.BloodLossRateMultiplier = 0;
                }
            }
        }

        private IEnumerator ChangeState()
        {
            if (isBusy) yield break;
            yield return new WaitForSeconds(useDelay);
            Activated = !Activated;

            phys.PlayClipOnce(Activated ? ignite : deactivate);
            isBusy = true;

            if (!Activated)
                yield return new WaitForSeconds(0.3f);

            lineRenderer.enabled = true;

            float origin = positions[1].y;
            float target = GetTarget();
            float delta = 4 / (Activated ? 1 : 0.7f);
            float progress = 0;

            if (Activated)
                audioSource.Play();

            while (progress < 0.99f)
            {
                positions[1].y = Mathf.Lerp(origin, target, progress);
                lineRenderer.SetPositions(positions);
                progress += delta * Time.deltaTime;
                yield return new WaitForEndOfFrame();
            }
            lineRenderer.SetPositions(positions);
            isBusy = false;
            UpdateActivation();
        }

        private float GetTarget()
        {
            return Activated ? MaxSaberLength : MinSaberLength;
        }

        public void SetColor(Color color)
        {
            this.color = color;
            lineRenderer.startColor = color * 0.32f;
            lineRenderer.endColor = color * 0.32f;
        }
    }
}