using GatherAndDefend.Events;
using System.Threading.Tasks;
using System.Collections.Generic;
using UnityEngine;

public class WaveObserver : Singleton<WaveObserver>
{
    private const int MAXTOUGHNESS = 10;

    private List<SpawnerTile> _subjects = new List<SpawnerTile>();
    private List<float> _aliveEnemyCount = new List<float>();
    private List<float> _groupSpawnTimers;
    private List<int> _copyConstantSpawn;
    private List<int> _intervalTiming = new List<int>();
    private List<List<int>> _copyGroupSpawn;  //Contains count of enemies per group
    private WaveConfig _levelConfig;
    private int _spawnerTiming = 0;
    private int _currentGroupIndex = 0;
    private bool _once = true;

    public void Init(WaveConfig config)
    {
        _subjects.Clear();
        _aliveEnemyCount.Clear();
        _intervalTiming.Clear();

        _levelConfig = Object.Instantiate(config);
        _copyConstantSpawn = new List<int>();
        _copyGroupSpawn = new List<List<int>>();
        _groupSpawnTimers = new List<float>();

        _spawnerTiming = 0;
        _currentGroupIndex = 0;
        _once = true;


        if (!_levelConfig)
        {
            Debug.LogError("level config was null");
            return;
        }
        foreach (EnemyType enemy in _levelConfig.ConstantSpawn)
        {
            _copyConstantSpawn.Add(enemy.Count);
        }
        for (int index = 0; index < _levelConfig.NestedGroupSpawn.Count; index++)
        {
            _copyGroupSpawn.Add(new List<int>());
            for (int nestedIndex = 0; nestedIndex < _levelConfig.NestedGroupSpawn[index].groupSpawn.Count; nestedIndex++)
            {
                _copyGroupSpawn[index].Add(_levelConfig.NestedGroupSpawn[index].groupSpawn[nestedIndex].Count);
            }
            _groupSpawnTimers.Add(_levelConfig.NestedGroupSpawn[index].triggerTime);
        }

        // Start game timer, at the end, player wins.
        GameTimer();
    }

    private async void GameTimer()
    {
        await Task.Delay((int) _levelConfig.GameDuration * 60 * 1000);
        EventAggregator.Instance.GetEvent<LastWaveCompletedEvent>().Invoke();
    }

    /**
     * Called by spawner at the start of the game
     * Assigns enemy to spawn and registers them
     */
    public void Attach(SpawnerTile spawnerSubject)
    {
        if (_subjects.Contains(spawnerSubject)) return;

        spawnerSubject.Prefab = _levelConfig.GetRandomSpawn().GetEnemyObject();
        _subjects.Add(spawnerSubject);
        _aliveEnemyCount.Add(0);
        _intervalTiming.Add(++_spawnerTiming);
        //  Ensures that only one spawner keeps track of the grouped spawn timer.
        if (_once)
        {
            _once = false;
            spawnerSubject.SetGroupSpawnTimers(_groupSpawnTimers);
        }
    }

    /**
     * Called by spawner when making enemies
     * Assigns a new interval
     */
    public void NotifySpawned(SpawnerTile spawnerSubject)
    {
        GameObject paramPrefab = spawnerSubject.Prefab;
        spawnerSubject.ChangeSpawnSpeed(_levelConfig.Interval * _spawnerTiming);
        for (int spawnIndex = 0; spawnIndex < _copyConstantSpawn.Count; spawnIndex++)
        {
            if (_copyConstantSpawn[spawnIndex] == 0)  //Doesn't compare this iteration because it's already empty
            {
                continue;
            }
            if (paramPrefab.Equals(_levelConfig.ConstantSpawn[spawnIndex].GetEnemyObject()))
            {
                _copyConstantSpawn[spawnIndex]--;
                
                if (_copyConstantSpawn[spawnIndex] == 0)
                {
                    foreach (SpawnerTile spawner in _subjects)
                    {
                        if (spawner.Prefab.Equals(paramPrefab))
                        {
                            EnemyType randomEnemy = _levelConfig.GetRandomSpawn(paramPrefab);
                            if (randomEnemy?.GetEnemyObject() == spawner.Prefab || randomEnemy == null)
                            {
                                spawner.StopSpawn();
                            }
                            else
                            {
                                spawner.Prefab = randomEnemy.GetEnemyObject();
                            }
                        }
                    }
                }
                break;
            }
        }
    }

    /**
     * Called by enemy when they spawn
     * Keeps track of their row
     */
    public int NotifyEnemy(float yPosition, float toughness)
    {
        int index = FindEnemyIndex(yPosition);
        _aliveEnemyCount[index] += toughness;
        if (_aliveEnemyCount[index] >= MAXTOUGHNESS)
        {
            _subjects[index].StopSpawn();
        }
        return index;
    }

    /**
     * Called when an enemy dies
     * Reactivates spawning on that row if disabled before
     */
    public void NotifyDies(int position, float toughness, GameObject paramPrefab)
    {
        _aliveEnemyCount[position] -= toughness;
        if (_aliveEnemyCount[position] < MAXTOUGHNESS)
        {
            for (int i = 0; i < _copyConstantSpawn.Count; i++)
            {
                if (_levelConfig.ConstantSpawn[i].GetEnemyObject() == paramPrefab)
                {
                    //  Checks if there are more of the same type to create
                    if (_copyConstantSpawn[i] > 0)
                    {
                        _subjects[position].StartSpawn();
                        break;
                    }
                    for (int j = i; j < _copyConstantSpawn.Count; j++)
                    {
                        //  Checks if there are other types to create
                        if (_copyConstantSpawn[_copyConstantSpawn.Count - j] > 0)
                        {
                            _subjects[position].Prefab = _levelConfig.ConstantSpawn[j].GetEnemyObject();
                            _subjects[position].StartSpawn();
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }

    /**
     * Called when an enemy is spawned automatically at the start of the game
     * Adjusts the intervall between spawns
     */
    public void NotifyOnStart(SpawnerTile spawnerSubject)
    {
        float interval = _levelConfig.GetUpdatedInterval();
        foreach (var subject in _subjects)
        {
            subject.ChangeSpawnSpeed(interval);
        }
        NotifySpawned(spawnerSubject);
    }

    // To find which spawner an ennemy came from.
    private int FindEnemyIndex(float yPosition)
    {
        for (int i = 0; i < _subjects.Count; i++)
        {
            if (_subjects[i].Position.y == yPosition)
            {
                return i;
            }
        }
        return -1;
    }

    /**
     * Spawners wait 1 second to have time to register them all
     * Then gets assigned a random spawn interval
     */
    public void NotifyEndCooldown(SpawnerTile spawnerTile)
    {
        System.Random rand = new System.Random();
        int index;
        do
        {
            index = rand.Next(_subjects.Count);
        } while (_intervalTiming.Count <= index);
        spawnerTile.ChangeSpawnSpeed(_levelConfig.Interval * _intervalTiming[index]);
        _intervalTiming.Remove(index);
    }

    /**
     * Called when it is time to spawn a group
     * Assigns a random element of the group to a random spawner
     */
    public bool NotifyGroupSpawn()
    {
        List<int> usedRows = new List<int>();
        List<EnemyType> currentGroup = _levelConfig.NestedGroupSpawn[_currentGroupIndex].groupSpawn;
        for (int groupIndex = 0; groupIndex < currentGroup.Count; groupIndex++)  //Loops through enemy groups
        {
            if (_copyGroupSpawn[_currentGroupIndex][groupIndex] != 0)
            {
                CycleRows(usedRows, currentGroup, groupIndex);
                //  If group is done OR max rows reached while group is not done
                if (_copyGroupSpawn[_currentGroupIndex][groupIndex] > 0 || (usedRows.Count == _subjects.Count && _copyGroupSpawn[_currentGroupIndex][groupIndex] > 0))
                {
                    return false;
                }
            }
        }
        _currentGroupIndex++;
        return true;
    }

    /**
     * Called to go through every row randomly without duplicate rows
     */
    private void CycleRows(List<int> usedRows, List<EnemyType> currentGroup, int groupIndex)
    {
        System.Random rand = new System.Random();
        while (usedRows.Count < _subjects.Count)
        {
            int currentRow = rand.Next(_subjects.Count);
            if (!usedRows.Contains(currentRow))  //If picked row has laready been used
            {
                _subjects[currentRow].TriggerSpawn(currentGroup[groupIndex].GetEnemyObject());
                _copyGroupSpawn[_currentGroupIndex][groupIndex]--;
                usedRows.Add(currentRow);
            }
            if (_copyGroupSpawn[_currentGroupIndex][groupIndex] == 0)  //If current ennemy has reached count of 0
            {
                break;
            }
        }
    }
}
