using System.Diagnostics;
using SoulstormReplayReader.Core.Extensions;
using SoulstormReplayReader.Core.Domain.Player;

namespace SoulstormReplayReader.Core.Utils;

// TODO: Посмотреть что происходит с наблюдателями
internal sealed class PlayersRandomizer
{
    private readonly NativeDowRandom _rand = new();

    public uint Seed
    {
        get => _rand.Seed;
        set => _rand.SetSeed(value);
    }

    public PlayersRandomizer(uint seed = 0)
    {
        Seed = seed;
        Debug.WriteLine($"The seed is: {Seed}");
    }

    public List<PlayerModel> Randomize(List<PlayerModel> players, int mapPlayersCount)
    {
        var isFfa = GetIsFfa(players);
        Debug.WriteLine(isFfa ? "FFA" : "SET TEAMS");

        Debug.WriteLine("RANDOMIZING TEAMS");
        var newPlayers = RandomizeTeams(players, isFfa);

        Debug.WriteLine("RANDOMIZING HOLES");
        RandomizeHoles(newPlayers, mapPlayersCount);

        if (!isFfa)
        {
            Debug.WriteLine("RANDOMIZING PLAYERS IN TEAMS");
            RandomizePlayersInTeams(newPlayers);
        }

        return newPlayers;
    }

    private static bool GetIsFfa(List<PlayerModel> players)
    {
        var lastTeam = 0;
        foreach (var player in players.OrderBy(static p => p.Team))
        {
            if (player.Team - lastTeam > 1)
                return true;

            lastTeam = player.Team;
        }

        return false;
    }

    private List<PlayerModel> RandomizeTeams(List<PlayerModel> players, bool isFfa)
    {
        if (isFfa)
        {
            var nPlayers = players.ToList();

            var activePlayersCount = players.Count(static p => p.IsActive);
            for (var i = 1; i < 8 && activePlayersCount > 1; i++)
            {
                if (nPlayers[i].IsActive) activePlayersCount--;

                nPlayers.Swap(i, _rand.GetMax(i + 1));
            }

            return nPlayers.Where(static p => p.IsActive).ToList();
        }

        var teams = players
            .Where(static p => p.IsActive)
            .OrderBy(static p => p.Team)
            .GroupBy(static pl => pl.Team)
            .Select(static team => team.ToList()).ToList();

        for (var i = 1; i < teams.Count; i++)
            teams.Swap(i, _rand.GetMax(i + 1));

        return teams.SelectMany(static p => p).ToList();
    }

    private void RandomizeHoles(List<PlayerModel> players, int mapPlayersCount)
    {
        var holesCount = mapPlayersCount - players.Count;
        for (var i = 1; i < players.Count && holesCount != 0; i++)
        {
            var playerHolesCount = _rand.GetMax(holesCount + 1);
            if (playerHolesCount == 0) continue;

            holesCount -= playerHolesCount;

            for (var j = 0; j < playerHolesCount; j++)
            {
                players.Insert(i, PlayerModel.Empty);
                i++;
            }
        }

        if (holesCount == 0) return;

        Debug.WriteLine($"{holesCount + 1} {holesCount}");
        while (holesCount-- != 0)
            players.Add(PlayerModel.Empty);
    }

    private void RandomizePlayersInTeams(List<PlayerModel> players)
    {
        var indexTeams = players
            .Select(static (p, i) => new KeyValuePair<int, int>(p.Team, i))
            .Where(static kv => kv.Key != 0)
            .GroupBy(static kv => kv.Key)
            .Select(static teamIndexes => teamIndexes.ToList())
            .ToList();

        foreach (var indexTeam in indexTeams)
            for (var i = 1; i < indexTeam.Count; i++)
            {
                var n = _rand.GetMax(i + 1);
                indexTeam.Swap(i, n);
                players.Swap(indexTeam[i].Value, indexTeam[n].Value);
            }
    }
}