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

public partial class OrbitalBehaviour
{
    public class BehaviourManager : MonoBehaviour
    {
        private static BehaviourManager Instance;
        
        private Vector3[] m_PrecalculatedPositions;

        private bool m_CurrentRingAllowsSpawning;
        private bool m_IsSimulationRunning;
        private bool m_AllowPlayerInput;

        public static bool IsSimulationRunning => Instance.m_IsSimulationRunning;
        public static bool AllowPlayerInput => Instance.m_AllowPlayerInput;

        private float m_LocalDeltaTimeScale = 1f;
        private float m_TimeOfLastRingSpawn;
        
        private int m_LastGeneratedRing;

        private readonly Dictionary<int, float> m_Distances = new();
        private readonly List<OrbitalBehaviour> m_ObjectInstances = new();
        private readonly List<Ring> m_Rings = new();
        
        private OrbitalBehaviour m_PlayerInstance;

        private GameObject m_ModiferParent;
        private GameObject m_EnemyParent;
        private GameObject m_RingParent;

        private const int CirclePoints = 100;
        
        public static IEnumerator StartPlayerSpeedupModifier()
        {
            Instance.m_LocalDeltaTimeScale *= 1.5f;

            yield return new WaitForSeconds(10f);

            Instance.m_LocalDeltaTimeScale /= 1.5f;
            Instance.m_LocalDeltaTimeScale *= 2f;

            yield return new WaitForSeconds(5f);
        
            Instance.m_LocalDeltaTimeScale /= 2f;
        }
        
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        private static void OnApplicationStart()
        {
            GameObject manager = new("OrbitalManager");
            DontDestroyOnLoad(manager);
            Instance = manager.AddComponent<BehaviourManager>();
            
            Instance.PrecalculatePositions();
            Instance.StartCoroutine(RestartSimulation());

            Instance.m_ModiferParent = new GameObject("ModifierParent");
            Instance.m_EnemyParent = new GameObject("EnemyHolder");
            Instance.m_RingParent = new GameObject("RingHolder");
        }

        private void PrecalculatePositions()
        {
            m_PrecalculatedPositions = new Vector3[CirclePoints];
            for (int idx = 0; idx < m_PrecalculatedPositions.Length; idx++)
            {
                m_PrecalculatedPositions[idx] = new Vector3
                (
                    x: Mathf.Cos(Mathf.Deg2Rad * (359f / CirclePoints * idx)),
                    y: Mathf.Sin(Mathf.Deg2Rad * (359f / CirclePoints * idx)),
                    z: 0
                );
            }
        }

        public static IEnumerator RestartSimulation()
        {
            Instance.m_IsSimulationRunning = false;
            Instance.m_AllowPlayerInput = false;
            
            Instance.m_LocalDeltaTimeScale = 7f;

            for (int idx = 0; idx < 100; idx++) // 100 = 5 seconds of fixed updates
            {
                if (Instance.m_PlayerInstance != null)
                {
                    Instance.m_PlayerInstance.m_AttachedRing = Instance.m_LastGeneratedRing;
                }

                yield return new WaitForFixedUpdate();
            }

            PlayerController.AttachPlayer();

            foreach (OrbitalBehaviour behaviour in Instance.m_ObjectInstances)
            {
                behaviour.OnSimulationRestart();
            }

            yield return new WaitForSeconds(1.3f);
            
            Instance.m_IsSimulationRunning = true;
            Instance.m_AllowPlayerInput = true;

            Instance.m_LocalDeltaTimeScale = 1f;
        }

        private void SpawnNewRing()
        {
            GameObject newRing = Instantiate(Settings.Instance.RingPrefab, m_RingParent.transform);
            newRing.transform.localScale = new Vector3(12f, 12f, 1f);
            
            Ring ring = newRing.AddComponent<Ring>();
            ring.m_ID = m_LastGeneratedRing + 1;
            m_Rings.Add(ring);
            
            LineRenderer lineRenderer = newRing.GetComponentInChildren<LineRenderer>();
            lineRenderer.loop = true;
            lineRenderer.positionCount = CirclePoints;
            lineRenderer.startWidth = 0.1f;
            lineRenderer.endWidth = 0.1f;
            lineRenderer.material = new Material(Shader.Find("Sprites/Default"));

            m_LastGeneratedRing = ring.m_ID;
            m_TimeOfLastRingSpawn = Time.time;

            m_CurrentRingAllowsSpawning = Random.Range(0, 25) != 0;
            if (!m_CurrentRingAllowsSpawning && m_AllowPlayerInput)
            {
                Instantiate(Settings.Instance.ModifierPrefab, m_ModiferParent.transform);
            }
        }

        private void Update()
        {
            if (Time.time - m_TimeOfLastRingSpawn > Settings.Instance.GapDistance / m_LocalDeltaTimeScale)
            {
                SpawnNewRing();
            }

            List<Ring> toRemove = new();
            m_Distances.Clear();

            foreach (Ring ring in m_Rings)
            {
                float diff = (m_LocalDeltaTimeScale * Time.deltaTime) * Settings.Instance.DistanceSpeed;
                ring.transform.localScale -= new Vector3(diff, diff, 0f);

                LineRenderer lineRenderer = ring.GetComponentInChildren<LineRenderer>();
                for (int vert = 0; vert < CirclePoints; vert++)
                {
                    lineRenderer.SetPosition(vert, new Vector3
                    (
                        x: m_PrecalculatedPositions[vert].x * ring.transform.localScale.x,
                        y: m_PrecalculatedPositions[vert].y * ring.transform.localScale.y,
                        z: 10
                    ));
                }

                float lerp = (ring.transform.localScale.x - 0.5f) / 4f;
                Color c = Color.Lerp(Color.black, Color.white, Mathf.Clamp01(lerp));
                if (ring.transform.localScale.x < 0.5f)
                {
                    if (m_IsSimulationRunning)
                        PlayerController.s_PlayerScore++;
                
                    toRemove.Add(ring);
                }

                lineRenderer.startColor = c;
                lineRenderer.endColor = c;

                m_Distances[ring.m_ID] = ring.transform.localScale.x;
            }
            
            foreach (Ring ring in toRemove)
            {
                m_Rings.Remove(ring);
                Destroy(ring.gameObject);
            }

            foreach (OrbitalBehaviour orbitalPosition in m_ObjectInstances)
            {
                if (!orbitalPosition.IsAttachedToRings)
                    continue;

                if (!m_Distances.TryGetValue(orbitalPosition.m_AttachedRing, out float distance))
                {
                    orbitalPosition.m_AttachedRing = m_LastGeneratedRing;
                    orbitalPosition.OnReachCentre();
                    continue;
                }

                orbitalPosition.m_DistanceFromCentre = distance;

                float deltaTime = Time.deltaTime * m_LocalDeltaTimeScale;
                float movement = deltaTime * Settings.Instance.RadiusSpeed * orbitalPosition.m_SpinSpeed;
                orbitalPosition.m_DistanceAlongCircumference += movement;
                orbitalPosition.m_DistanceAlongCircumference %= Mathf.PI * 2;
                orbitalPosition.transform.position = orbitalPosition.TranslateToVector3();
            }
        }
        
        private void FixedUpdate()
        {
            foreach (OrbitalBehaviour orbital in m_ObjectInstances)
            {
                if (orbital == m_PlayerInstance)
                    continue;

                Vector2 a = orbital.TranslateToVector3();
                Vector2 b = m_PlayerInstance.TranslateToVector3();
                float d = (a - b).magnitude;

                float r1 = m_PlayerInstance.m_ObjectRadius * m_PlayerInstance.ObjectRadiusMultiplier;
                float r2 = orbital.m_ObjectRadius * orbital.ObjectRadiusMultiplier;
                if (d > (r1 + r2))
                    continue;
                
                m_PlayerInstance.OnOrbitalCollision(orbital);
                orbital.OnOrbitalCollision(m_PlayerInstance);
            }

            if (Random.Range(0, 15) == 0 && m_AllowPlayerInput && m_CurrentRingAllowsSpawning)
            {
                Instantiate(Settings.Instance.EnemyPrefab, m_EnemyParent.transform);
            }
        }

        public static void UnregisterOrbitalInstance(OrbitalBehaviour instance)
        {
            Instance.m_ObjectInstances.Remove(instance);
        }
        
        public static void RegisterOrbitalInstance(OrbitalBehaviour instance)
        {
            instance.m_AttachedRing = Instance.m_LastGeneratedRing; // Connects it to last generated ring
            Instance.m_ObjectInstances.Add(instance);
        }

        public static void SetPlayer(OrbitalBehaviour player)
        {
            Instance.m_PlayerInstance = player;
        }
    }
}
