import discord
from discord.ext.commands import Cog
import logging
from datetime import datetime
import asyncio
import typing
from geeksbot.imports import arcon
from geeksbot.imports import utils
from geeksbot.imports import checks

rcon_log = logging.getLogger('rcon')


class Rcon(Cog):
    def __init__(self, bot):
        self.bot = bot

    async def connect_rcon_server(self, *, name: str, ip: str, port: int, password: str,
                                  single_packet: bool = False, force: bool = False, monitor_chat: bool = False,
                                  server_chat_channel: int = None, server_messages_channel: int = None) \
            -> typing.Union[int, str]:

        if name.replace('_', ' ').title() in self.bot.connected_ark_servers.keys() \
                and isinstance(self.bot.connected_ark_servers[name], arcon.ARKServer) \
                and self.bot.connected_ark_servers[name].writer \
                and not force:
            rcon_log.info(f"{name.replace('_', ' ').title()} is already connected.")
            return 1

        server = arcon.ARKServer(host=ip, port=port, password=password, single_packet=single_packet,
                                 monitor_chat=monitor_chat, server_chat_channel=server_chat_channel,
                                 server_messages_channel=server_messages_channel)
        rcon_log.info(f'{name.replace("_", " ").title()} configured.')
        connected = await server.connect()
        if connected == 1:
            rcon_log.info(f'{name.replace("_", " ").title()} connected.')
            self.bot.connected_ark_servers[name.replace('_', ' ').title()] = server
            return 1
        elif connected == -1:
            rcon_log.info(f'{name.replace("_", " ").title()} timeout error.')
            return 'Timeout Error'
        elif connected == 0:
            rcon_log.info(f'{name.replace("_", " ").title()} auth error.')
            return 'Authentication Failed'
        else:
            rcon_log.info(f'{name.replace("_", " ").title()} unknown error.')
            return 'Unknown Error'

    async def get_rcon_server_by_name(self, *, guild_config: dict, name: str) \
            -> typing.Union[dict, arcon.ARKServer, None]:
        guild_servers = guild_config.get('rcon_connections')
        if guild_servers:
            if name == '*':
                server_list = {}
            for server, info in guild_servers.items():
                if server == name.replace('_', ' ').title():
                    if server not in self.bot.connected_ark_servers.keys():
                        connection_result = await self.connect_rcon_server(
                            name=server,
                            ip=info['ip'],
                            port=info['port'],
                            password=info['password'],
                            monitor_chat=info.get('monitoring_chat', None),
                            server_chat_channel=info.get('game_chat_chan_id', None),
                            server_messages_channel=info.get('msg_chan_id', None)
                        )
                        if connection_result != 1:
                            return None
                    return self.bot.connected_ark_servers[server]
                elif name == '*':
                    if server not in self.bot.connected_ark_servers.keys():
                        connection_result = await self.connect_rcon_server(
                            name=server,
                            ip=info['ip'],
                            port=info['port'],
                            password=info['password'],
                            monitor_chat=info.get('monitoring_chat', None),
                            server_chat_channel=info.get('game_chat_chan_id', None),
                            server_messages_channel=info.get('msg_chan_id', None)
                        )
                        if connection_result != 1:
                            # noinspection PyUnboundLocalVariable
                            server_list[server] = f'Cannot connect to the {server} server.\n{connection_result}'
                    try:
                        # noinspection PyUnboundLocalVariable
                        server_list[server] = self.bot.connected_ark_servers[server]
                    except KeyError:
                        rcon_log.warning(server_list[server])
                        del server_list[server]
            else:
                if name == '*':
                    return server_list
                return None
        else:
            return None

    @staticmethod
    async def admin(bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
        player = msg.split(' ||| ')[1].split(' (')[0]
        rcon_log.info(f'{player} requested admin assistance')
        admin_roles = guild_config.get('admin_roles')
        if admin_roles:
            for role in admin_roles:
                msg = '{0} {1}'.format(msg, discord.utils.get(guild.roles, id=admin_roles[role]).mention)
        await server_con.server_chat_to_player_name(player, 'GeeksBot: Admin Geeks have been notified you need '
                                                            'assistance. Please be patient.')
        return msg

    async def dinowipe(self, bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
        player = msg.split(' ||| ')[1].split(' (')[0]
        steam_ref = self.bot.fs_db.collection(f'users').where('steam_name', '==', player)
        user_info = await self.bot.loop.run_in_executor(self.bot.tpe, steam_ref.get)
        steamid = None
        user = None
        for user in user_info:
            if user:
                steamid = user.to_dict().get('steam_id')
                break
        if steamid:
            if not self.bot.dino_wipe_request.get(server_name):
                if user:
                    player = await patron.Patron.from_id(bot, steamid, discord_id=int(user.id))
                    rcon_log.info(f'{player.steam_name} requested a wild dino wipe')
                    member = guild.get_member(player.discord_id)
                    self.bot.loop.create_task(self.request_dinowipe(member, server_name, server_con))
                else:
                    await server_con.server_chat_to_player_name(player,
                                                                'Sorry, an error has occurred please try again.')
            else:
                await server_con.server_chat_to_player_name(player, 'A Wild Dino Wipe request is already in progress.')
        else:
            await server_con.server_chat_to_player_name(player, 'Sorry. You are not registered to run commands via the '
                                                                'in-game chat. Please send a chat message in-game '
                                                                '$register steamid=your_steam_id and follow the '
                                                                'instructions so I can link your character with your '
                                                                'Discord account. Thanks')
        return msg

    @staticmethod
    async def delaywipe(bot, guild: discord.Guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
        player = msg.split(' ||| ')[1].split(' (')[0]
        if bot.dino_wipe_request.get(server_name):
            bot.dino_wipe_request[server_name] = False
            await server_con.broadcast(f'Wild Dino Wipe has been delayed by {player}. '
                                       f'Please watch chat for timing updates')
        elif bot.dino_wipe_request.get(server_name) is None:
            await server_con.server_chat_to_player_name(player, 'There are no Wild Dino Wipes pending.')
        else:
            await server_con.server_chat_to_player_name(player, 'The dino wipe has already been delayed.')
        return msg

    @staticmethod
    async def register(bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
        player = msg.split(' ||| ')[1].split(' (')[0]
        steamid = msg.split(' ||| ')[1].split('steamid=')[1].split(' ')[0].replace('`', '').replace("'", '').strip()
        rcon_log.info(f'{player} - {steamid}')
        try:
            int(steamid)
        except ValueError:
            await server_con.server_chat_to_player_name(player, 'That is not a valid SteamID')
            return msg
        else:
            identifier = randint(10000, 99999)
            prefix = guild_config.get("prefixes", bot.default_prefix)
            await server_con.server_chat_to_player_name(player,
                                                        'Your SteamID has been noted, to finish registering '
                                                        'please run the following command in the discord '
                                                        f'channel. You will not be able to run commands '
                                                        f'in-game until your registration is completed... '
                                                        f'{prefix[0] if isinstance(prefix, list) else prefix}'
                                                        f'register {identifier}')
            bot.pending_registrations[identifier] = (player, steamid)
        return msg

    @commands.command(name='register')
    async def register_discord_account(self, ctx, identifier: int=None):
        user_ref = self.bot.fs_db.document(f'users/{ctx.author.id}')
        user_info = (await self.bot.loop.run_in_executor(self.bot.tpe, user_ref.get)).to_dict()
        if user_info.get('steam_id') and user_info.get('steam_name'):
            await ctx.send('You are already registered to run commands in-game')
            return

        if identifier not in self.bot.pending_registrations.keys():
            await ctx.send('That identifier is not in the pending registrations')
            return

        steam_info = self.bot.pending_registrations[identifier]

        await self.bot.loop.run_in_executor(self.bot.tpe, user_ref.update, {
            'steam_name': steam_info[0],
            'steam_id': steam_info[1]
        })
        await ctx.send('Registration complete. You are now authorized to run commands in-game on the ARK servers.')
        del self.bot.pending_registrations[identifier]

    async def create_server_chat_chan(self, guild: discord.Guild, server_name: str,
                                      server_con: arcon.ARKServer, guild_config: dict):
        rcon_log.info(f'Creating channel for {server_name}')
        category = discord.utils.get(guild.categories, name='Server Chats')
        if category is None:
            overrides = {guild.default_role: discord.PermissionOverwrite(read_messages=False)}
            category = await guild.create_category('Server Chats', overwrites=overrides)
        rcon_log.info(category)
        channels = guild.channels
        if category:
            for channel in category.channels:
                if server_con.server_chat_channel == channel.id:
                    return channel
            else:
                rcon_log.info(f'Creating {server_name}')
                chan = await guild.create_text_channel(f'{server_name}', category=category)
                server_con.server_chat_channel = chan.id
                if guild_config.get('rcon_connections'):
                    guild_config['rcon_connections'][server_name]['game_chat_chan_id'] = chan.id
                    guild_ref = self.bot.fs_db.document(f'guilds/{guild.id}')
                    await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, guild_config)
                return chan

    async def _monitor_chat(self, guild, server_name, server_con, guild_config: dict):
        # noinspection PyShadowingNames
        async def start_monitor_chat(bot, guild, *, server_name: str,
                                     server_con: arcon.ARKServer, guild_config: dict):
            while server_con.monitor_chat:
                messages = await server_con.getchat()
                rcon_log.debug('Got chat from {0}.'.format(server_name))
                for message in [msg.strip() for msg in messages.split('\n')
                                if msg.strip() != 'Server received, But no response!!']:
                    rcon_log.info(message)
                    message_out = f'```{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} ||| {message}```'
                    for command in self.bot.game_commands:
                        prefix_command = '{0}{1}'.format(self.bot.game_prefix, command)
                        if message.split('): ')[-1].startswith(prefix_command):
                            try:
                                func = getattr(self, command)
                            except AttributeError:
                                rcon_log.warning('Function not found "{0}"'.format(command))
                            else:
                                rcon_log.info(f'Sending to {command}')
                                message_out = await func(bot, guild, message_out, server_name, server_con, guild_config)

                    await guild.get_channel(server_con.server_chat_channel).send(message_out)
                await asyncio.sleep(1)
            await guild.get_channel(server_con.server_chat_channel).send('Monitoring Stopped')

        rcon_log.info(server_con.server_chat_channel)
        if server_con.server_chat_channel:
            channel = self.bot.get_channel(server_con.server_chat_channel)
            if not channel:
                channel = await self.create_server_chat_chan(guild, server_name, server_con, guild_config)
        else:
            channel = await self.create_server_chat_chan(guild, server_name, server_con, guild_config)
        rcon_log.info(channel)
        self.bot.loop.create_task(start_monitor_chat(self.bot, guild, server_name=server_name,
                                                     server_con=server_con, guild_config=guild_config))
        await channel.send('Started monitoring on the {0} server.'.format(server_name))
        rcon_log.debug('Started monitoring on the {0} server.'.format(server_name))

    @commands.command()
    @commands.guild_only()
    async def monitor_chat(self, ctx, *, server=None):
        """Begins monitoring the specified ARK server for chat messages and other events.
        The specified server must already be in the current guild\'s configuration.
        To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
        The server argument is not case sensitive and if the server name has two
            words it can be in one of the following forms:
        first last
        first_last
        "first last"
        To view all the valid ARK servers for this guild see list_ark_servers."""

        if await checks.is_rcon_admin(self.bot, ctx):
            if server is not None:
                server = server.replace('_', ' ').title()
                server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                                 name=server)
                if server_con:
                    server_con.monitor_chat = True
                    ctx.guild_config['rcon_connections'][server]["monitoring_chat"] = 1
                    guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
                    await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
                    await self._monitor_chat(ctx.guild, server, server_con, ctx.guild_config)
                    await ctx.message.add_reaction('✅')
                else:
                    await ctx.send(f'Server not found: {server}')
            else:
                await ctx.send(f'You must include a server in this command.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.command()
    @commands.guild_only()
    async def end_monitor_chat(self, ctx, *, server=None):
        """Ends chat monitoring on the specified server.
        Context is the same as monitor_chat"""
        if await checks.is_rcon_admin(self.bot, ctx):
            if server is not None:
                server = server.replace('_', ' ').title()
                server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                                 name=server)
                if server_con:
                    server_con.monitor_chat = False
                else:
                    await ctx.send(f'{server} is not connected currently.')
                if server in ctx.guild_config.get('rcon_connections', []):
                    ctx.guild_config['rcon_connections'][server]["monitoring_chat"] = 0
                    guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
                    await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
                else:
                    await ctx.send(f'Server not found in config: {server}')
            else:
                await ctx.send(f'You must include a server in this command.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.command()
    @commands.guild_only()
    async def listplayers(self, ctx, *, server_name=None):
        """Lists the players currently connected to the specified ARK server.
        The specified server must already be in the current guild\'s configuration.
        To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
        The server argument is not case sensitive and if the server name has two
            words it can be in one of the following forms:
        first last
        first_last
        "first last"
        To view all the valid ARK servers for this guild see list_ark_servers."""
        if await checks.is_rcon_admin(self.bot, ctx):

            if server_name:
                server_name = server_name.replace('_', ' ').title()
                server: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                             name=server_name)
                if server:
                    msg = await ctx.send(f'**Getting Data for the {server_name} server**')
                    await ctx.channel.trigger_typing()

                    message = await server.listplayers()
                    await ctx.channel.trigger_typing()
                    await msg.delete()
                    await ctx.send(f'**Players currently on the {server_name} server:**\n{message}')
                else:
                    await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
                                   f'"{server_name}" "ip" port "password" if you would like to get info from it.')
            else:
                futures = []
                guild_servers: dict = await self.get_rcon_server_by_name(
                    guild_config=ctx.guild_config, name='*'
                )
                if guild_servers is not None:
                    for name, server in guild_servers.items():
                        if not isinstance(server, arcon.ARKServer):
                            await ctx.send(f'__**{server}**__')
                        else:
                            msg = await ctx.send(f'**Getting Data for the {name} server**')

                            # noinspection PyShadowingNames
                            async def _listplayers(*, name, server_con: arcon.ARKServer, mess: discord.Message):
                                name = name.replace('_', ' ').title()

                                response = await server_con.listplayers()
                                await mess.edit(content=f'**Players currently on the {name} server:**\n{response}')

                            futures.append(_listplayers(mess=msg, server_con=server, name=name))
                    if futures:
                        asyncio.ensure_future(asyncio.gather(*futures))
                    else:
                        await ctx.send('There are no available servers for this guild.')
                else:
                    await ctx.send('There are no available servers for this guild.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.command()
    @commands.guild_only()
    async def add_rcon_server(self, ctx, server, ip, port, password):
        """Adds the specified server to the current guild\'s rcon config.
        All multi-word strings (<server>, <ip>, <password>) must be contained inside double quotes."""
        if await checks.is_rcon_admin(self.bot, ctx):
            server = server.replace('_', ' ').title()
            if ctx.guild_config.get('rcon_connections'):
                if server not in ctx.guild_config['rcon_connections']:
                    ctx.guild_config['rcon_connections'][server] = {
                        'ip': ip,
                        'port': port,
                        'password': password,
                        'game_chat_chan_id': 0,
                        'msg_chan_id': 0,
                        'monitoring_chat': 0
                    }
                    guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
                    await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
                    await ctx.send('{0} server has been added to my configuration.'.format(server))
                else:
                    await ctx.send('This server name is already in my configuration. Please choose another.')
            else:
                ctx.guild_config['rcon_connections'] = {server: {
                    'ip': ip,
                    'port': port,
                    'password': password,
                    'game_chat_chan_id': 0,
                    'msg_chan_id': 0,
                    'monitoring_chat': 0
                }}
                guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
                await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
                await ctx.send('{0} server has been added to my configuration.'.format(server))
        else:
            await ctx.send(f'You are not authorized to run this command.')
        await ctx.message.delete()
        await ctx.send('Command deleted to prevent password leak.')

    @commands.command()
    @commands.guild_only()
    async def remove_rcon_server(self, ctx, *, server: str):
        """removes the specified server from the current guild\'s rcon config."""
        if await checks.is_rcon_admin(self.bot, ctx):
            server = server.replace('_', ' ').title()
            if server in ctx.guild_config.get('rcon_connections', []):
                del ctx.guild_config['rcon_connections'][server]
                guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
                await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
                await ctx.send('{0} has been removed from my configuration.'.format(server))
            else:
                await ctx.send('{0} is not in my configuration.'.format(server))
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @staticmethod
    async def _whitelist(*, server_name: str, server_con: arcon.ARKServer,
                         player: patron.Patron, message: discord.Message, message_lock: asyncio.Lock):
        result = await server_con.whitelist(player.steam_id)
        if result == f'{player.steam_id} Allow Player To Join No Check':
            with await message_lock:
                message = await message.channel.get_message(message.id)
                await message.edit(content=f'{message.content}\n{server_name.replace("_", " ").title()}'
                                           f' Done!')
        else:
            with await message_lock:
                message = await message.channel.get_message(message.id)
                await message.edit(content=f'{message.content}\n{server_name.replace("_", " ").title()}'
                                           f' Failed!')

    @commands.command(name='add_whitelist')
    @commands.guild_only()
    async def add_whitelist(self, ctx, *, members: str=None):
        if await checks.is_rcon_admin(self.bot, ctx):
            if members:
                futures = []
                members = members.replace(', ', '').split(',')
                converter = commands.MemberConverter()
                for member in members:
                    try:
                        member = await converter.convert(ctx, member)
                    except commands.errors.BadArgument:
                        try:
                            member = int(member)
                        except ValueError:
                            raise ValueError(f'Member {member} can\'t be found and is not a valid Steam64 ID.')

                    if isinstance(member, discord.Member):
                        player = await patron.Patron.from_name(self.bot, discord_name=member)
                    else:
                        player = await patron.Patron.from_id(self.bot, steam_id=member)

                    if player == -1:
                        await ctx.send(f'{ctx.author.mention} I Cannot find a player with a discord name/steam id of '
                                       f'{member} in the current whitelist sheet. Did you forget to '
                                       f'move them to the correct sheet?')
                    else:
                        rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                                    name='*')
                        if rcon_connections:
                            msg = await ctx.send(f'**Whitelisting {player.discord_name} on all servers**')
                            lock = asyncio.Lock()
                            for server_name, server_con in rcon_connections.items():
                                futures.append(self._whitelist(server_name=server_name, server_con=server_con,
                                                               player=player, message=msg, message_lock=lock))

                if futures:
                    asyncio.ensure_future(asyncio.gather(*futures), loop=self.bot.loop)
                else:
                    await ctx.send('Nothing for me to do')
                    return

            else:
                await ctx.send('I need a list of members to whitelist.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.command(name='new_patron')
    @commands.guild_only()
    async def register_new_patron(self, ctx, *, members: str=None):
        """Adds the included Steam 64 IDs to the running whitelist on all the ARK servers in the current guild\'s rcon config.
        Steam 64 IDs should be a comma separated list of IDs.
        Example: 76561198024193239,76561198024193239,76561198024193239"""
        if await checks.is_rcon_admin(self.bot, ctx):
            if members is not None:
                async with ctx.typing():
                    members = members.replace(', ', ',').split(',')
                    converter = commands.MemberConverter()
                    members = [await converter.convert(ctx, m) for m in members]
                    futures = []
                    patrons = []
                    rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                                name='*')

                    if not isinstance(members, list):
                        members = [members, ]
                    for member in members:
                        member: discord.Member
                        player = await patron.Patron.from_name(self.bot, discord_name=member)
                        if player == -1:
                            await ctx.send(f'{ctx.author.mention} I Cannot find a player with a discord name of '
                                           f'{member.display_name} in the current whitelist sheet. Did you forget to '
                                           f'move them to the correct sheet?')
                        else:
                            if rcon_connections:
                                msg = await ctx.send(f'**Whitelisting {player.discord_name} on all servers**')
                                lock = asyncio.Lock()
                                for server_name, server_con in rcon_connections.items():
                                    futures.append(self._whitelist(server_name=server_name, server_con=server_con,
                                                                   player=player, message=msg, message_lock=lock))

                            roles = []
                            patron_roles = ctx.guild_config.get('patreon_tiers')
                            creator_roles = ctx.guild_config.get('patreon_creators')
                            if creator_roles:
                                rcon_log.info(f'patron_of {player.patron_of}')
                                if 'both' in player.patron_of.casefold():
                                    rcon_log.info('found both')
                                    for role_id in creator_roles.values():

                                        roles.append(ctx.guild.get_role(int(role_id)))
                                else:
                                    roles.append(ctx.guild.get_role(
                                        int(creator_roles[player.patron_of + '_Patron'])
                                    ))
                            if patron_roles:
                                roles.append(ctx.guild.get_role(
                                    int(patron_roles[player.patreon_tier.strip().title()])
                                ))
                            if roles:
                                role_str = '\n'.join([role.name for role in roles])
                                await ctx.send(f'Adding {player.discord_name} to the following roles:\n'
                                               f'{role_str}')
                                await ctx.guild.get_member(int(player.discord_id)).add_roles(*roles)

                            patrons.append(player)

                    if futures:
                        asyncio.ensure_future(asyncio.gather(*futures), loop=self.bot.loop)
                    else:
                        await ctx.send('Nothing for me to do')
                        return

                    if patrons:
                        new_patron_message = ctx.guild_config.get('new_patron_message')
                        new_patron_channel = ctx.guild_config.get('new_patron_channel')
                        if new_patron_message and new_patron_channel:
                            channel = ctx.guild.get_channel(int(new_patron_channel))
                            if channel:
                                patron_mentions = []
                                for p in patrons:
                                    member = ctx.guild.get_member(int(p.discord_id))
                                    patron_mentions.append(member.mention)
                                prelude = new_patron_message.get('prelude')
                                message = ''
                                if prelude:
                                    if len(patron_mentions) > 1:
                                        message = prelude.get('plural')
                                    else:
                                        message = prelude.get('singular')
                                body = new_patron_message.get('body')
                                if body:
                                    server_info_channel = ctx.guild.get_channel(int(
                                        ctx.guild_config.get('server_info')
                                    ))
                                    message += ' ' + body.format(users=', '.join(patron_mentions),
                                                                 server_info=server_info_channel.mention if
                                                                 server_info_channel else "server_info")
                                await channel.send(message)
            else:
                await ctx.send('I need a list of members to add roles to and whitelist.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.command()
    @commands.guild_only()
    async def saveworld(self, ctx, *, server=None):
        """Runs SaveWorld on the specified ARK server.
        If a server is not specified it will default to running saveworld on all servers in the guild\'s config.
        Will print out "World Saved" for each server when the command completes successfully."""
        if await checks.is_rcon_admin(self.bot, ctx):

            # noinspection PyShadowingNames
            async def _saveworld(ctx, server_name: str, server_con: arcon.ARKServer):
                response = await server_con.saveworld()
                if response == 'World Saved':
                    await ctx.send(f'{server_name} Saved')
                else:
                    await ctx.send(f'Failed to save {server_name}')

            futures = []
            async with ctx.typing():
                if server is None:
                    rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                                name='*')
                    if rcon_connections:
                        for server_name, server_con in rcon_connections.items():
                            futures.append(_saveworld(ctx, server_name, server_con))
                        self.bot.loop.create_task(asyncio.gather(*futures))
                    else:
                        await ctx.send('There are no available servers for this guild.')
                else:
                    server = server.replace('_', ' ').title()
                    server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
                        guild_config=ctx.guild_config, name=server
                    )
                    if server_con:
                        # noinspection PyTypeChecker
                        await _saveworld(ctx, server, server_con)
                    else:
                        await ctx.send(f'{server} is not currently in the configuration for this guild.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.group(case_insensitive=True)
    async def broadcast(self, ctx):
        """Run help broadcast for more info"""
        pass

    @broadcast.command(name='all', aliases=['a'])
    @commands.guild_only()
    async def broadcast_all(self, ctx, *, message=None):
        """Sends a broadcast message to all servers in the guild config.
        The message will be prefixed with the Discord name of the person running the command.
        Will print "Success" for each server once the broadcast is sent."""
        if await checks.is_rcon_admin(self.bot, ctx):
            if message is not None:

                # noinspection PyShadowingNames
                async def _broadcast(*, message: str, server_con: arcon.ARKServer, server_name: str,
                                     msg: discord.Message, message_lock: asyncio.Lock):
                    print(server_con.host, server_con.port)
                    response = await server_con.broadcast(message)
                    if response == 'Server received, But no response!!':
                        with await message_lock:
                            msg = await msg.channel.get_message(msg.id)
                            await msg.edit(content=f'{msg.content}\n{server_name} Success')
                    else:
                        with await message_lock:
                            msg = await msg.channel.get_message(msg.id)
                            await msg.edit(content=f'{msg.content}\n{server_name} Failed')
                futures = []
                rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                            name='*')
                if rcon_connections:
                    message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
                    msg = await ctx.send(f'Broadcasting "{message}" to all servers.')
                    lock = asyncio.Lock()
                    for server_name, server_con in rcon_connections.items():
                        futures.append(_broadcast(message=message, server_con=server_con, server_name=server_name,
                                                  msg=msg, message_lock=lock))
                    self.bot.loop.create_task(asyncio.gather(*futures))
                    await ctx.message.add_reaction('✅')
                else:
                    await ctx.send('There are no available servers for this guild.')
            else:
                await ctx.send('You must include a message with this command.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @broadcast.command(name='server')
    @commands.guild_only()
    async def broadcast_server(self, ctx, server, *, message=None):
        """Sends a broadcast message to the specified server that is in the guild's config.
        The message will be prefixed with the Discord name of the person running the command.
        If <server> has more than one word in it's name it will either need to be surrounded
        by double quotes or the words separated by _"""
        if await checks.is_rcon_admin(self.bot, ctx):
            if server is not None:
                server = server.replace('_', ' ').title()
                if message is not None:
                    message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
                    server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
                        guild_config=ctx.guild_config, name=server
                    )
                    if server_con:
                        msg = await ctx.send(f'Broadcasting "{message}" to {server}.')
                        response = await server_con.broadcast(message)
                        if response == 'Server received, But no response!!':
                            await msg.add_reaction(self.bot.unicode_emojis['y'])
                        else:
                            await msg.add_reaction(self.bot.unicode_emojis['x'])
                    else:
                        await ctx.send(f'{server} is not in the config for this guild')
                else:
                    await ctx.send('You must include a message with this command.')
            else:
                await ctx.send('You must include a server with this command')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.command(aliases=['servers', 'list_servers'])
    @commands.guild_only()
    @commands.check(checks.is_restricted_chan)
    async def list_ark_servers(self, ctx):
        """Returns a list of all the ARK servers in the current guild\'s config."""
        servers = ctx.guild_config.get('rcon_connections', [])
        em = discord.Embed(style='rich',
                           title=f'__**There are currently {len(servers)} ARK servers in my config:**__',
                           color=discord.Colour.green()
                           )
        if ctx.guild.icon:
            em.set_thumbnail(url=f'{ctx.guild.icon_url}')
        for server in servers:
            description = f"""
            ឵          **IP:** {servers[server]['ip']}:{servers[server]['port']}
            ឵          **Steam Connect:** [steam://connect/{servers[server]['ip']}:{servers[server]['port']}]\
            (steam://connect/{servers[server]['ip']}:{servers[server]['port']})"""
            em.add_field(name=f'__***{server}***__', value=description, inline=False)
        await ctx.send(embed=em)

    @commands.command(name='server_chat')
    @commands.guild_only()
    async def send_chat_to_server(self, ctx, server: str=None, *, message: str=None):
        if await checks.is_rcon_admin(self.bot, ctx):
            if server is not None:
                server = server.replace('_', ' ').title()
                server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
                                                                                 name=server)
                if server_con:
                    if message is not None:
                        message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
                        msg = await ctx.send(f'Sending "{message}" to {server}\'s chat')
                        response = await server_con.serverchat(message)
                        if response == 'Server received, But no response!!':
                            await msg.add_reaction(self.bot.unicode_emojis['y'])
                        else:
                            await msg.add_reaction(self.bot.unicode_emojis['x'])
                    else:
                        await ctx.send('You must include a message with this command.')
                else:
                    await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
                                   f'"{server}" "ip" port "password" if you would like to get info from it.')
            else:
                await ctx.send('You must include a server with this command')
        else:
            await ctx.send('You are not authorized to run this command.')

    @commands.command(name='restart_server', aliases=['restart'])
    @commands.guild_only()
    async def restart_rcon_server(self, ctx, message: str, server_name: str=None,
                                  delay_time: int=15, sleep_time: int=60):
        if await checks.is_rcon_admin(self.bot, ctx):

            if server_name is None or server_name == 'all':
                futures = []
                rcon_servers: dict = await self.get_rcon_server_by_name(
                    guild_config=ctx.guild_config, name='*'
                )
                for server_name, server_con in rcon_servers.items():
                    message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
                    futures.append(utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
                                                             message=message, delay=delay_time, sleep=sleep_time))
                self.bot.loop.create_task(asyncio.gather(*futures))

            elif server_name.startswith('exclude='):
                futures = []
                exclude_servers = server_name.split('exclude=')[1].replace('_', ' ').title().split(',')
                rcon_servers: dict = await self.get_rcon_server_by_name(
                    guild_config=ctx.guild_config, name='*'
                )
                for server_name, server_con in rcon_servers.items():
                    if server_name not in exclude_servers:
                        message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
                        futures.append(utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
                                                                 message=message, delay=delay_time, sleep=sleep_time))
                self.bot.loop.create_task(asyncio.gather(*futures))

            else:
                async with ctx.typing():
                    server_name = server_name.replace('_', ' ').title()
                    server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
                        guild_config=ctx.guild_config, name=server_name
                    )
                    if server_con:
                        message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
                        await utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
                                                        message=message, delay=delay_time, sleep=sleep_time)
                    else:
                        await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
                                       f'"{server_name}" "ip" port "password" if you would like to get info from it.')
        else:
            await ctx.send('You are not authorized to run this command.')

    # noinspection PyShadowingNames
    @staticmethod
    async def start_dinowipe(channel, server_name: str, server_con: arcon.ARKServer):
        msg = await channel.send(f'Wild dinos will be wiped on {server_name} in 2 minutes.')
        await server_con.broadcast(f'Wild dino wipe incoming in 2 minutes, expect a small amount of lag while '
                                   f'the dinos repopulate.')
        await asyncio.sleep(90)
        await server_con.serverchat(f'Wild Dino Wipe in 30 seconds')
        await asyncio.sleep(26)
        await server_con.serverchat(f'Wild Dino Wipe in 3')
        await asyncio.sleep(1)
        await server_con.serverchat(f'Wild Dino Wipe in 2')
        await asyncio.sleep(1)
        await server_con.serverchat(f'Wild Dino Wipe in 1')
        await asyncio.sleep(1)
        response = await server_con.destroy_wild_dinos()
        if response == 'All Wild Dinos Destroyed':
            await msg.edit(content=f'Wild Dinos Wiped on {server_name}')
            await server_con.serverchat(f'Wild dinos have been wiped.')
        else:
            await msg.edit(content=f'Failed to wipe wild dinos on {server_name}')
            await server_con.serverchat(f'Wild Dino Wipe failed, Please let the Admin know of this issue')

    # noinspection PyShadowingNames,PyUnresolvedReferences
    async def request_dinowipe(self, requester, server_name: str, server_con: arcon.ARKServer):
        self.bot.dino_wipe_request[server_name] = True
        if server_con.server_messages_channel and self.bot.get_channel(int(server_con.server_messages_channel)):
            channel = self.bot.get_channel(int(server_con.server_messages_channel))
        else:
            channel = self.bot.get_channel(server_con.server_chat_channel)
        msg = False
        if channel:
            msg = await channel.send(f'@here {requester.mention} has requested wild dinos be wiped on the '
                                     f'{server_name} server\n'
                                     f'If you are in the middle of taming or have some other reason to '
                                     f'delay the wipe please react to this message with '
                                     f'{self.bot.unicode_emojis["x"]}.\n'
                                     f'Doing so will delay the wipe for 10 minutes.')
        await server_con.broadcast(f'{requester.display_name} has requested wild dinos be wiped.\n'
                                   f'if you are in the middle of taming or have some other reason to delay the wipe '
                                   f'please send a chat message containing "$delaywipe" here in-game.\n'
                                   f'Doing so will delay the wipe for 10 minutes.')
        await asyncio.sleep(5)
        await server_con.serverchat('The wipe will continue in 2 minutes if there are no requests to delay. '
                                    'Send a message containing $delaywipe to delay for 10 minutes.')
        rcon_log.info('Dino Wipe messages sent')
        if msg:
            def check(reaction, user):
                return str(reaction.emoji) == self.bot.unicode_emojis['x'] and reaction.message.id == msg.id and \
                       user != self.bot.user

            user = None
            await msg.add_reaction(self.bot.unicode_emojis['x'])
            try:
                reaction, user = await self.bot.wait_for('reaction_add', check=check, timeout=120)
            except asyncio.TimeoutError:
                rcon_log.info('Message timed out...')
                if self.bot.dino_wipe_request.get(server_name):
                    rcon_log.info('Starting dino wipe')
                    await self.start_dinowipe(channel, server_name, server_con)
                    rcon_log.info('Wild Dino Wipe Completed')
                    return
            else:
                rcon_log.info('Dino Wipe Delayed.')
                await server_con.broadcast(f'Wild Dino Wipe has been delayed by {user.display_name}. '
                                           f'Please watch chat for timing updates')
            await channel.send(f'Dino wipe has been delayed by {user.mention if user else "Game Chat"}...')
            for i in range(10):
                await asyncio.sleep(60)
                if i >= 5 or i == 0:
                    await server_con.serverchat(f'The Wild Dino Wipe process will continue in {9 - i} '
                                                f'{"minute" if 9 - i == 1 else "minutes"}')
            await self.request_dinowipe(requester, server_name, server_con)
        else:
            rcon_log.info('No channels configured, running wipe.')
            await asyncio.sleep(120)
            if not self.bot.dino_wipe_request.get(server_name):
                rcon_log.info('Wipe Delay requested')
                for i in range(10):
                    await asyncio.sleep(60)
                    await server_con.serverchat(f'The Wino Dino Wipe process will continue in {9 - i} '
                                                f'{"minute" if 9 - i == 1 else "minutes"}')
                await self.request_dinowipe(requester, server_name, server_con)

    @commands.command(name='dinowipe')
    @commands.guild_only()
    async def run_dino_wipe(self, ctx, *, server=None):
        """Runs DestroyWildDinos on the specified ARK server.
        If a server is not specified it will default to wiping the wild dinos on all servers in the guild\'s config.
        Will print out "Wild Dinos Wiped on <server name>" for each server when the command completes successfully."""
        if await checks.is_rcon_admin(self.bot, ctx):
            futures = []
            async with ctx.typing():
                if server is None:
                    rcon_connections: dict = await self.get_rcon_server_by_name(
                        guild_config=ctx.guild_config, name='*'
                    )
                    if rcon_connections:
                        for server_name, server_con in rcon_connections.items():
                            futures.append(self.start_dinowipe(ctx.channel, server_name, server_con))
                        self.bot.loop.create_task(asyncio.gather(*futures))
                    else:
                        await ctx.send('There are no available servers for this guild.')
                else:
                    server = server.replace('_', ' ').title()
                    server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
                        guild_config=ctx.guild_config, name=server
                    )
                    if server_con:
                        # noinspection PyTypeChecker
                        await self.start_dinowipe(ctx.channel, server, server_con)
                    else:
                        await ctx.send(f'{server} is not currently in the configuration for this guild.')
        else:
            await ctx.send(f'You are not authorized to run this command.')

    @commands.command(name='run_command', aliases=['run'])
    @commands.guild_only()
    async def run_rcon_command(self, ctx, server=None, *, command=None):
        if await checks.is_rcon_admin(self.bot, ctx):
            if not server:
                await ctx.send('You must include a server when running this command.')
                return
            async with ctx.typing():
                server = server.replace('_', ' ').title()
                server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
                    guild_config=ctx.guild_config, name=server
                )
                if server_con:
                    if not command:
                        await ctx.send('You must include a command to run on the server.')
                        return

                    command = command.split(' ')

                    if ctx.guild_config.get('allowed_rcon_commands'):
                        pass

                    response = await server_con.run_command(command=' '.join(command), multi_packet=True)
                    if isinstance(response, str):
                        await ctx.send(response)
                    else:
                        body = response.body
                        pag = utils.Paginator(bot=self.bot)
                        pag.add(body)
                        book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
                        await book.create_book()
                else:
                    await ctx.send('That server was not found in the config for this guild.')

def setup(bot):
    bot.add_cog(Rcon(bot))
