using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UDB;
using UnityEngine;
using UnityEngine.Events;

public class WaveSystem : MonoBehaviour
{
    public static WaveSystem Instance { get; private set; }

    public interface IWaveEnemy
    {
        public delegate void WaveEnemyDelegate(GameObject gameObject);
        public event WaveEnemyDelegate OnWaveEnemyDied;

        void StartWaveEnemy();
    }

    [System.Serializable]
    public class Wave {
        [SerializeField] public List<GameObject> enemies;
    }

    [SerializeField] private Wave[] waves;

    public List<GameObject> unassignedEnemies = new List<GameObject>();

    public float timeBetweenWaves = 120f;
    public float timeBeforeFirstWave = 120f;

    [ReadOnly]
    public int nextWaveIndex = 0;
    public int currentWaveIndex => nextWaveIndex - 1;

    public UnityEvent OnAllWavesComplete;
    public UnityEvent OnWaveComplete;
    public UnityEvent OnWaveStart;

    public float timeOfLastWave = 0f;
    public int EnemiesRemaining = 0;

    public float TimeRemainingInWave() 
    {
        return timeBetweenWaves - (Time.time - timeOfLastWave);
    }

    void Start()
    {
        Instance = this;
        RunNextWaveAfterDelay(timeBeforeFirstWave);
    }

    void OnDestroy()
    {
        Instance = null;
    }

    public int CurrentWaveIndex() 
    {
        return currentWaveIndex;
    }

    public int TotalWaves() 
    {
        return waves.Count();
    }

    public int CurrentEnemiesRemaining() 
    {
        return EnemiesRemaining;
    }

    public Wave CurrentWave() 
    {
        if (currentWaveIndex >= waves.Length || currentWaveIndex < 0) {
            return null;
        }
        return waves[currentWaveIndex];
    }

    [Button]
    public void RunNextWave()
    {
        if (nextWaveIndex >= waves.Length) {
            Debug.Log("No more waves, We won!");
            OnAllWavesComplete.Invoke();
            return;
        }
        Wave wave = waves[nextWaveIndex];
        foreach(GameObject enemy in wave.enemies) {
            IWaveEnemy waveEnemy = enemy.GetComponent<IWaveEnemy>();
            waveEnemy.StartWaveEnemy();
            waveEnemy.OnWaveEnemyDied -= EnemyDied;
            waveEnemy.OnWaveEnemyDied += EnemyDied;
        }
        RespawnDeadPlayers();
        OnWaveStart.Invoke();
        nextWaveIndex++;

        if (currentWaveIndex >= waves.Length || currentWaveIndex < 0) {
            EnemiesRemaining = 0;
        } else {
            EnemiesRemaining = waves[currentWaveIndex].enemies.Count;
        }
    }

    void RespawnDeadPlayers()
    {
        for (int i = 0; i < PlayerTarget.playerStats.Count; i++) {
            PlayerTarget.playerStats[i].Revive();
        }
    }

    void EnemyDied(GameObject deadEnemy)
    {
        foreach(Wave wave in waves) {
            for(int i = wave.enemies.Count - 1; i >= 0; i--) {
                if (wave.enemies[i] == deadEnemy) {
                    wave.enemies.RemoveAt(i);
                }
            }
        }
        if (waves[currentWaveIndex].enemies.Count == 0) {
            OnWaveComplete.Invoke();
            RunNextWaveAfterDelay(timeBetweenWaves);
        }

        if (currentWaveIndex >= waves.Length || currentWaveIndex < 0) {
            EnemiesRemaining = 0;
        } else {
            EnemiesRemaining = waves[currentWaveIndex].enemies.Count;
        }
    }

    void RunNextWaveAfterDelay(float delay)
    {
        StartCoroutine(RunNextWaveAfterDelayCoroutine(delay));
    }

    private IEnumerator RunNextWaveAfterDelayCoroutine(float delay)
    {
        timeOfLastWave = Time.time;
        yield return new WaitForSeconds(delay);
        RunNextWave();
    }

    [Button]
    public void FindAllGameObjectsWithInterface()
    {
        unassignedEnemies.Clear();
        var allObjects = FindObjectsOfType<GameObject>();
        foreach(var obj in allObjects) {
            if (obj.GetComponent<IWaveEnemy>() != null) {
                bool found = false;
                foreach(var wave in waves) {
                    foreach(var enemy in wave.enemies) {
                        if (enemy == obj) {
                            Debug.Log("Found " + obj.name + " in wave " + Array.IndexOf(waves, wave));
                            found = true;
                        }
                    }
                }
                if (!found) {
                    Debug.Log("Found " + obj.name + " but it's not in any wave");
                    unassignedEnemies.Add(obj);
                }
            }
        }
    }

    //Assign all gameobjects that are in unassignedEnemies to waves in a expenetial fashion
    [Button]
    public void AssignUnassignedEnemies()
    {
        int enemyIndex = unassignedEnemies.Count - 1;
        for (int waveIndex = 0; waveIndex < waves.Length && enemyIndex > 0; waveIndex++)
        {
            // Calculate the number of enemies to assign to this wave in an exponential manner.
            // For example, assign 2^waveIndex enemies to the wave at waveIndex.
            // Make sure not to exceed the total number of unassigned enemies.
            int enemiesToAssign = Mathf.Min((int)Mathf.Pow(2, waveIndex), enemyIndex);

            for (int i = 0; i < enemiesToAssign; i++)
            {
                // Add the enemy from unassignedEnemies to the current wave.
                waves[waveIndex].enemies.Add(unassignedEnemies[enemyIndex]);
                unassignedEnemies.RemoveAt(enemyIndex);

                // Move to the next unassigned enemy.
                enemyIndex--;
            }
        }
    }


    [Button]
    public void ClearAllNulls()
    {
        foreach(Wave wave in waves) {
            for(int i = wave.enemies.Count - 1; i >= 0; i--) {
                if (wave.enemies[i] == null) {
                    wave.enemies.RemoveAt(i);
                }
            }
        }
    }
}
