/*
* author: NearHuscarl
* description: Randomizes the skins and names of bots for more varied bot play.
* gamemodes: versus, custom
*/

        private static readonly string WIN_STATUS_NOBODY = "Nobody wins";
        private static readonly string WIN_STATUS_NO_HUMAN = "No human players remaining!";
        private static readonly string WIN_STATUS_TEAM = "Team {0} wins";
        private static readonly string WIN_STATUS_PLAYER_WIN = "{0} wins";
        private static readonly uint GAME_OVER_DELAY = 3000;
        private static bool _allowOnlyBots = false;
        public void OnStartup() { Initialize(); }
        private static void Initialize()
        {
            RandomHelper.Initialize();
            Events.PlayerDeathCallback.Start(OnPlayerDealth);
            Events.UserMessageCallback.Start(Command.OnUserMessage);
            _allowOnlyBots = Storage.GetAllowOnlyBots();
            var spawners = RandomHelper.Shuffle(ScriptHelper.GetPlayerSpawners(emptyOnly: true));
            if (spawners.Count > 0)
            {
                var isRandom = Storage.GetIsRandom();
                var index = 0;
                Storage.GetBotsData().ForEach(botData =>
                {
                    if (index > spawners.Count - 1) return;
                    var bot = Game.CreatePlayer(spawners[index].GetWorldPosition());
                    bot.SetProfile(isRandom ? RandomHelper.RandomizeProfile() : botData.Profile);
                    bot.SetBotName(isRandom ? RandomHelper.RandomizeName() : botData.Name);
                    bot.SetTeam(botData.Team);
                    bot.SetBotBehaviorSet(BotBehaviorSet.GetBotBehaviorPredefinedSet(botData.AI));
                    bot.SetBotBehaviorActive(true);
                    index++;
                });
            }
        }
        private static void OnPlayerDealth(IPlayer player)
        {
            CheckGameoverStatus();
        }
        private static void CheckGameoverStatus()
        {
            if (Game.IsGameOver) return;
            Events.UpdateCallback.Start(_ =>
            {
                var alivePlayers = new List<IPlayer>();
                var aliveBots = new List<IPlayer>();
                var lastPlayerSurvive = (IPlayer)null;
                foreach (var p in Game.GetPlayers())
                {
                    if (p.IsDead) continue;
                    if (p.IsBot) aliveBots.Add(p);
                    else alivePlayers.Add(p);
                    lastPlayerSurvive = p;
                }
                if (alivePlayers.Count > 0 || aliveBots.Count > 0)
                {
                    if (alivePlayers.Count == 0 && aliveBots.Count > 1 && !_allowOnlyBots)
                    {
                        Game.SetGameOver(WIN_STATUS_NO_HUMAN);
                        return;
                    }
                    var teamsLeft = ScriptHelper.GetAliveTeams();
                    if (teamsLeft.Count == 1)
                    {
                        var teamLeft = teamsLeft[0];
                        if (teamLeft == PlayerTeam.Independent)
                        {
                            if (alivePlayers.Count + aliveBots.Count == 1)
                                Game.SetGameOver(string.Format(WIN_STATUS_PLAYER_WIN, lastPlayerSurvive.Name));
                        }
                        else
                            Game.SetGameOver(string.Format(WIN_STATUS_TEAM, ScriptHelper.GetTeamNumber(teamLeft)));
                    }
                }
                else
                {
                    Game.SetGameOver(WIN_STATUS_NOBODY);
                }
            }, GAME_OVER_DELAY - 100, 1);
        }

    public class BotData
    {
        public IProfile Profile { get; set; }
        public PlayerTeam Team { get; set; }
        public string Name { get; set; }
        public PredefinedAIType AI { get; set; }
        private static string ReadProfileItem(IProfileClothingItem clothingItem)
        {
            var str = "";
            if (clothingItem == null)
                return "||||";
            str += clothingItem.Name + "|";
            str += clothingItem.Color1 + "|";
            str += clothingItem.Color2 + "|";
            str += clothingItem.Color3 + "|";
            return str;
        }
        private static string ReadAI(PredefinedAIType AI)
        {
            switch (AI)
            {
                case PredefinedAIType.BotA: return "expert";
                case PredefinedAIType.BotB: return "hard";
                case PredefinedAIType.BotC: return "medium";
                case PredefinedAIType.BotD: return "easy";
                default: return "hard";
            }
        }
        private static string ReadTeam(PlayerTeam team)
        {
            switch (team)
            {
                case PlayerTeam.Team1: return "1";
                case PlayerTeam.Team2: return "2";
                case PlayerTeam.Team3: return "3";
                case PlayerTeam.Team4: return "4";
                default: return "_";
            }
        }
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append(Name + "|");
            sb.Append(ReadAI(AI) + "|");
            sb.Append(ReadTeam(Team) + "|");
            sb.Append(ReadProfileItem(Profile.Skin));
            sb.Append(ReadProfileItem(Profile.Head));
            sb.Append(ReadProfileItem(Profile.ChestOver));
            sb.Append(ReadProfileItem(Profile.ChestUnder));
            sb.Append(ReadProfileItem(Profile.Hands));
            sb.Append(ReadProfileItem(Profile.Waist));
            sb.Append(ReadProfileItem(Profile.Legs));
            sb.Append(ReadProfileItem(Profile.Accessory));
            return sb.ToString();
        }
        private static IProfileClothingItem ParseProfileItem(List<string> parts, int startIndex)
        {
            return new IProfileClothingItem(parts[startIndex], parts[startIndex + 1], parts[startIndex + 2], parts[startIndex + 3]);
        }
        public static PredefinedAIType ParseAI(string str)
        {
            switch (str)
            {
                case "expert": return PredefinedAIType.BotA;
                case "hard": return PredefinedAIType.BotB;
                case "medium": return PredefinedAIType.BotC;
                case "easy": return PredefinedAIType.BotD;
                default: return PredefinedAIType.BotB;
            }
        }
        public static PlayerTeam ParseTeam(string str)
        {
            switch (str)
            {
                case "1": return PlayerTeam.Team1;
                case "2": return PlayerTeam.Team2;
                case "3": return PlayerTeam.Team3;
                case "4": return PlayerTeam.Team4;
                default: return PlayerTeam.Independent;
            }
        }
        public static BotData FromString(string str, bool parseProfile = true)
        {
            var parts = str.Split('|').ToList();
            var name = parts[0];
            var ai = ParseAI(parts[1]);
            var team = ParseTeam(parts[2]);
            var profile = new IProfile();
            if (parseProfile)
            {
                profile.Skin = ParseProfileItem(parts, 3);
                profile.Head = ParseProfileItem(parts, 7);
                profile.ChestOver = ParseProfileItem(parts, 11);
                profile.ChestUnder = ParseProfileItem(parts, 15);
                profile.Hands = ParseProfileItem(parts, 19);
                profile.Waist = ParseProfileItem(parts, 23);
                profile.Legs = ParseProfileItem(parts, 27);
                profile.Accessory = ParseProfileItem(parts, 31);
            }
            return new BotData { Name = name, Team = team, Profile = profile, AI = ai };
        }
    }
    public static class Command
    {
        public static readonly Color MESSAGE_COLOR = new Color(24, 238, 200);
        public static readonly Color ERROR_COLOR = new Color(244, 77, 77);
        public static readonly Color WARNING_COLOR = new Color(249, 191, 11);
        public static void OnUserMessage(UserMessageCallbackArgs args)
        {
            if (!args.User.IsHost || !args.IsCommand || (args.Command != "RANDOMBOTS" && args.Command != "RB"))
            {
                return;
            }
            var message = args.CommandArguments.ToLowerInvariant();
            var words = message.Split(' ');
            var command = words.FirstOrDefault();
            var arguments = words.Skip(1);
            switch (command)
            {
                case "?":
                case "h":
                case "help":
                    PrintHelp();
                    break;
                case "settings":
                case "s":
                    PrintSettings(arguments);
                    break;
                case "setbots":
                case "sb":
                    SetBots(arguments);
                    break;
                case "clearbots":
                case "cb":
                    ClearBots(arguments);
                    break;
                case "random":
                case "r":
                    ToggleRandom(arguments);
                    break;
                case "allowonlybots":
                case "aob":
                    ToggleAllowOnlyBot(arguments);
                    break;
            }
        }
        private static void PrintSettings(IEnumerable<string> arguments)
        {
            var isRandom = Storage.GetIsRandom();
            var allowOnlyBots = Storage.GetAllowOnlyBots();
            Game.ShowChatMessage("--RandomBots Settings--", ERROR_COLOR);
            Game.ShowChatMessage("isRandom: " + isRandom, MESSAGE_COLOR);
            Game.ShowChatMessage("allowOnlyBots: " + allowOnlyBots, MESSAGE_COLOR);
        }
        private static void ToggleAllowOnlyBot(IEnumerable<string> arguments)
        {
            var value = Storage.GetAllowOnlyBots();
            var newValue = !value;
            Storage.SaveAllowOnlyBots(newValue);
            Game.ShowChatMessage("Set AllowOnlyBots to " + newValue, MESSAGE_COLOR);
        }
        private static void ToggleRandom(IEnumerable<string> arguments)
        {
            var isRandom = Storage.GetIsRandom();
            var newValue = !isRandom;
            Storage.SaveIsRandom(newValue);
            Game.ShowChatMessage("Set isRandom to " + newValue, MESSAGE_COLOR);
        }
        private static void PrintHelp()
        {
            Game.ShowChatMessage("--RandomBots Help--", ERROR_COLOR);
            Game.ShowChatMessage("/<RandomBots|rb> [Help|h|?]: Print this panel.", MESSAGE_COLOR);
            Game.ShowChatMessage("/<RandomBots|rb> [Settings|s]: Show script settings.", MESSAGE_COLOR);
            Game.ShowChatMessage("/<RandomBots|rb> [SetBots|sb] <Team> <AI-COUNT>: Set script bots to play with, e.g. /rb sb 0 expert-4.", MESSAGE_COLOR);
            Game.ShowChatMessage("/<RandomBots|rb> [ClearBots|cb]: Remove script bots.", MESSAGE_COLOR);
            Game.ShowChatMessage("/<RandomBots|rb> [Random|r]: Toggle randomized bot profiles every match on or off.", MESSAGE_COLOR);
            Game.ShowChatMessage("/<RandomBots|rb> [AllowOnlyBot|aob]: Toggle bot-exclusive gameover on or off.", MESSAGE_COLOR);
}
        private static void SetBots(IEnumerable<string> arguments)
        {
            var team = arguments.First();
            arguments = arguments.Skip(1);
            var botsData = Storage.GetBotsData();
            foreach (var arg in arguments)
            {
                var parts = arg.Split('-');
                var count = int.Parse(parts[1]);
                for (var i = 0; i < count; i++)
                {
                    botsData.Add(new BotData
                    {
                        Name = RandomHelper.RandomizeName(),
                        Team = BotData.ParseTeam(team),
                        AI = BotData.ParseAI(parts[0]),
                        Profile = RandomHelper.RandomizeProfile(),
                    });
                }
            }
            foreach (var botData in botsData)
            {
                var spawners = ScriptHelper.GetPlayerSpawners();
                var p = Game.CreatePlayer(RandomHelper.GetRandomItem(spawners).GetWorldPosition());
                p.SetProfile(botData.Profile);
                p.SetBotName(botData.Name);
                p.SetTeam(botData.Team);
                p.SetBotBehaviorSet(BotBehaviorSet.GetBotBehaviorPredefinedSet(botData.AI));
                p.SetBotBehaviorActive(true);
            }
            Storage.SaveBotsData(botsData);
        }
        private static void ClearBots(IEnumerable<string> arguments)
        {
            Storage.SaveBotsData(new List<BotData>());
            Game.ShowChatMessage("Bots are cleared in the next match.", MESSAGE_COLOR);
        }
    }
    public static class Helpers
    {
        public static readonly IGame Game;
    }
    public static class RandomHelper
    {
        public static void Initialize() { Rnd = new Random(); }
        public static Random Rnd { get; set; }
        public static T GetRandomItem<T>(List<T> list)
        {
            if (list.Count == 0)
                throw new Exception("list is empty");
            var rndIndex = Rnd.Next(list.Count);
            return list[rndIndex];
        }
        public static IList<T> Shuffle<T>(IList<T> list)  
        {
            var n = list.Count;
            while (n > 1)
            {
                n--;
                int k = Rnd.Next(n + 1);
                T value = list[k];
                list[k] = list[n];
                list[n] = value;
            }
            return list;
        }
        public static bool Boolean()
        {
            return Rnd.NextDouble() >= 0.5;
        }
        public static string RandomizePackage(List<string> packageNames)
        {
            if (packageNames.Count == 0) return null;
            return GetRandomItem(packageNames);
        }
        public static string[] RandomizePalette(string paletteName)
        {
            var palette = Game.GetColorPalette(paletteName);
            var color1 = RandomizePackage(palette.PrimaryColorPackages.ToList());
            var color2 = RandomizePackage(palette.SecondaryColorPackages.ToList());
            var color3 = RandomizePackage(palette.TertiaryColorPackages.ToList());
            return new string[] { color1, color2, color3 };
        }
        public static IProfileClothingItem RandomizeItemColor(string clothingItemName)
        {
            var palette = Game.GetClothingItemColorPaletteName(clothingItemName);
            var colors = RandomizePalette(palette);
            return new IProfileClothingItem(clothingItemName, colors[0], colors[1], colors[2]);
        }
        private static void SetRandomProfileItem(IProfile profile, int layer, string itemName)
        {
            if (itemName == null)
                ScriptHelper.SetProfileItem(profile, layer, null);
            else
                ScriptHelper.SetProfileItem(profile, layer, RandomizeItemColor(itemName));
        }
        public enum ClothingType
        {
            Accesory,
            ChestOver,
            ChestUnder,
            Feet,
            Hands,
            Head,
            Legs,
            Waist,
        }
        public static ClothingType[] EquipmentLayers = ScriptHelper.EnumToArray<ClothingType>();
        public static IProfile RandomizeProfile()
        {
            var gender = Boolean() ? Gender.Male : Gender.Female;
            var profile = new IProfile() { Gender = gender };
            for (int equipmentLayer = 0; equipmentLayer < EquipmentLayers.Length; ++equipmentLayer)
            {
                var items = ScriptHelper.GetProfileItems(equipmentLayer, profile.Gender).ToList();
                var itemName = items.Count == 0 ? null : GetRandomItem(items);
                float num;
                switch (equipmentLayer)
                {
                    case 0:
                        num = 0f;
                        break;
                    case 8:
                        num = 0.4f;
                        break;
                    default:
                        num = 0.2f;
                        break;
                }
                if (Rnd.NextDouble() >= num)
                {
                    SetRandomProfileItem(profile, equipmentLayer, itemName);
                }
            }
            return profile;
        }
        public static string RandomizeName()
        {
            var strArray = new string[28]
         {
        "Jeff",
        "Bob",
        "Mac",
        "Gerald",
        "Guffin",
        "Martin",
        "Steven",
        "Wreck",
        "Neo",
        "Angelica",
        "Sara",
        "Rain",
        "Snowstorm",
        "Punch",
        "Train",
        "Macho",
        "Superfighter",
        "Geronimo",
        "Ship",
        "Christmas",
        "Santa",
        "Building",
        "American",
        "Hexlex",
        "Luthor",
        "Thor",
        "Hulk",
        "ng"
         };
            var stringList1 = new List<string>();
            var stringList2 = new List<string>();
            var stringList3 = new List<string>();
            var stringList4 = new List<string>();
            var stringList5 = new List<string>();
            stringList5.Add("rf");
            stringList5.Add("rt");
            stringList5.Add("ph");
            stringList5.Add("ff");
            stringList5.Add("ng");
            foreach (var str in strArray)
            {
                for (var index = 1; index < str.Length; ++index)
                {
                    var lowerInvariant = (str[index - 1].ToString() + str[index].ToString()).ToLowerInvariant();
                    stringList4.Add(lowerInvariant);
                    if ("aoueiy".Contains(lowerInvariant[0].ToString()))
                        stringList1.Add(lowerInvariant);
                    else
                        stringList3.Add(lowerInvariant);
                    if ("aoueiy".Contains(lowerInvariant[1].ToString()))
                        stringList2.Add(lowerInvariant);
                    if (index == str.Length - 1)
                        stringList5.Add(lowerInvariant);
                }
            }
            var rndName = "";
            var flag1 = true;
            var flag2 = false;
            var num = Rnd.Next(2, 5);
            for (var index = 0; index < num; ++index)
            {
                var stringList6 = stringList4;
                if (!flag1 && !flag2)
                    stringList6 = stringList1;
                if (flag2)
                    stringList6 = stringList3;
                string str = stringList6[Rnd.Next(0, stringList6.Count)];
                flag1 = stringList1.Contains(str);
                flag2 = stringList2.Contains(str);
                rndName += str;
                if (rndName.Length == 2 && !"aoueiy".Contains(rndName[0].ToString()) && !"aoueiy".Contains(rndName[1].ToString()))
                    rndName = rndName[0].ToString();
            }
            Func<string> func1 = () => rndName.Length >= 2 ? rndName[rndName.Length - 2].ToString() + rndName[rndName.Length - 1].ToString() : "";
            Func<bool> func2 = () => rndName.Length > 2 && !"aoueiy".Contains(rndName[rndName.Length - 1].ToString()) && !"aoueiy".Contains(rndName[rndName.Length - 2].ToString());
            rndName = rndName.Replace("bef", "beef").Replace("ufi", "uffi").Replace("sex", "six").Replace("nxl", "xly").Replace("ic", "ice").Replace("fip", "flip").Replace("ee", "e");
            while (func2())
            {
                var str = func1();
                if (!stringList5.Contains(str))
                    rndName = rndName.Substring(0, rndName.Length - 1);
                else
                    break;
            }
            rndName = rndName[0].ToString().ToUpperInvariant()[0].ToString() + rndName.Substring(1);
            return rndName;
        }
    }
    public static class ScriptHelper
    {
        public static T[] EnumToArray<T>()
        {
            return (T[])Enum.GetValues(typeof(T));
        }
        public static List<IObject> GetPlayerSpawners(bool emptyOnly = false)
        {
            if (!emptyOnly)
                return Game.GetObjectsByName("SpawnPlayer").ToList();
            var players = Game.GetPlayers();
            return Game.GetObjectsByName("SpawnPlayer").Where(o => !players.Any(x => x.GetAABB().Intersects(o.GetAABB()))).ToList();
        }
        public static List<PlayerTeam> GetAliveTeams()
        {
            var teamCount = new Dictionary<PlayerTeam, int>
            {
                { PlayerTeam.Team1, 0 },
                { PlayerTeam.Team2, 0 },
                { PlayerTeam.Team3, 0 },
                { PlayerTeam.Team4, 0 },
                { PlayerTeam.Independent, 0 },
            };
            foreach (var p in Game.GetPlayers())
            {
                if (!p.IsDead)
                {
                    teamCount[p.GetTeam()]++;
                }
            }
            return teamCount.Where(t => t.Value > 0).Select(t => t.Key).ToList();
        }
        public static int GetTeamNumber(PlayerTeam team)
        {
            switch (team)
            {
                case PlayerTeam.Team1: return 1;
                case PlayerTeam.Team2: return 2;
                case PlayerTeam.Team3: return 3;
                case PlayerTeam.Team4: return 4;
                default: return 0;
            }
        }
        public static string[] GetProfileItems(int layer, Gender gender)
        {
            switch (layer)
            {
                case 0:
                    return Game.GetClothingItemNamesSkin(gender);
                case 1:
                    return Game.GetClothingItemNamesChestUnder(gender);
                case 2:
                    return Game.GetClothingItemNamesLegs(gender);
                case 3:
                    return Game.GetClothingItemNamesWaist(gender);
                case 4:
                    return Game.GetClothingItemNamesFeet(gender);
                case 5:
                    return Game.GetClothingItemNamesChestOver(gender);
                case 6:
                    return Game.GetClothingItemNamesAccessory(gender);
                case 7:
                    return Game.GetClothingItemNamesHands(gender);
                case 8:
                    return Game.GetClothingItemNamesHead(gender);
                default:
                    throw new ArgumentException("Invalid layer index: " + layer);
            }
        }
        public static void SetProfileItem(IProfile profile, int layer, IProfileClothingItem item)
        {
            switch (layer)
            {
                case 0:
                    profile.Skin = item; return;
                case 1:
                    profile.ChestUnder = item; return;
                case 2:
                    profile.Legs = item; return;
                case 3:
                    profile.Waist = item; return;
                case 4:
                    profile.Feet = item; return;
                case 5:
                    profile.ChestOver = item; return;
                case 6:
                    profile.Accesory = item; return;
                case 7:
                    profile.Hands = item; return;
                case 8:
                    profile.Head = item; return;
                default:
                    throw new ArgumentException("Invalid layer index: " + layer);
            }
        }
    }
    public static class Storage
    {
        private static IScriptStorage _storage = Game.GetSharedStorage("RandomBots");
        public static List<BotData> GetBotsData()
        {
            var botsValue = _storage.GetItem("RB_BOTS") as string[];
            if (botsValue != null)
            {
                return botsValue.Select(str => BotData.FromString(str)).ToList();
            }
            return new List<BotData>();
        }
        public static void SaveBotsData(IEnumerable<BotData> botsData)
        {
            _storage.SetItem("RB_BOTS", botsData.Select(b => b.ToString()).ToArray());
        }
        public static bool GetIsRandom()
        {
            var value = _storage.GetItem("RB_IS_RANDOM");
            return bool.Parse(value == null ? "false" : value.ToString());
        }
        public static void SaveIsRandom(bool value)
        {
            _storage.SetItem("RB_IS_RANDOM", value);
        }
        public static bool GetAllowOnlyBots()
        {
            var value = _storage.GetItem("RB_ALLOW_ONLY_BOTS");
            return bool.Parse(value == null ? "false" : value.ToString());
        }
        public static void SaveAllowOnlyBots(bool value)
        {
            _storage.SetItem("RB_ALLOW_ONLY_BOTS", value);
        }
    }