/// <summary>
        /// Automatically creates <see cref="Client"/> and connects it, creates <see cref="VoiceClient"/> (if requested), instantiates all bot contexts automatically, starts the <see cref="XanBotConsoleCore"/> update loop, and binds the windows console CTRL+C/CTRL+BREAK to <see cref="Exit(int)>"/>
        /// </summary>
        /// <param name="botToken">The token of this discord bot.</param>
        /// <param name="createVoiceNextClient">If true, <see cref="VoiceClient"/> will be instantiated and allow this bot to connect to voice channels.</param>
        /// <param name="isTargettingWindows7">Whether or not this bot is running on Windows 7. This is necessary for some init code.</param>
        /// <param name="yieldUntilGuildsDownloaded">If true, this task will yield until <see cref="DiscordClient.GuildDownloadCompleted"/> is fired.</param>
        /// <returns></returns>
        public static async Task InitializeBotAsync(string botToken, bool createVoiceNextClient = false, bool isTargettingWindows7 = false, bool yieldUntilGuildsDownloaded = false)
        {
            Console.ForegroundColor = ConsoleColor.Green;

            XanBotLogger.IsPathLocked = true;
            if (IsDebugMode)
            {
                XanBotLogger.WriteLine("§eInitializing in Debug Mode...");
            }
            else
            {
                XanBotLogger.WriteLine("§2Initializing...");
            }
            if (isTargettingWindows7)
            {
                Client = new DiscordClient(new DiscordConfiguration
                {
                    Token     = botToken,
                    TokenType = TokenType.Bot,
                    WebSocketClientFactory = new WebSocketClientFactoryDelegate(WebSocket4NetClient.CreateNew), // Remove if this bot is running on machine with Windows 10.
                });
            }
            else
            {
                Client = new DiscordClient(new DiscordConfiguration
                {
                    Token     = botToken,
                    TokenType = TokenType.Bot
                });
            }
            XanBotLogger.WriteDebugLine("Created DiscordClient.");

            if (createVoiceNextClient)
            {
                VoiceClient = Client.UseVoiceNext(new VoiceNextConfiguration
                {
                    AudioFormat = new AudioFormat(48000, 2, VoiceApplication.LowLatency)
                });
                XanBotLogger.WriteDebugLine("Created VoiceNextClient.");
            }

            XanBotLogger.WriteLine("§2Connecting to Discord...");
            await Client.ConnectAsync();

            // Client is connected. Create contexts!
            XanBotLogger.WriteLine("§2Initializing User-Defined Bot Contexts...");
            BotContextRegistry.InitializeAllContexts();

            XanBotLogger.WriteLine("§2Connecting CommandMarshaller to chat events...");
            Client.MessageCreated += async evt =>
            {
                //XanBotLogger.WriteLine("Hey the message created event fired");
                if (evt.Author == Client.CurrentUser)
                {
                    return;
                }
                if (evt.Author.IsBot)
                {
                    return;
                }
                if (CommandMarshaller.IsCommand(evt.Message.Content))
                {
                    XanBotLogger.WriteDebugLine("Message was sent and was detected as a command.");
                    await CommandMarshaller.HandleMessageCommand(evt.Message);
                }
                else
                {
                    XanBotLogger.WriteDebugLine("Message was sent but was not a command. Throwing it into the passive handlers.");
                    await CommandMarshaller.RunPassiveHandlersForMessage(evt.Message);
                }
            };

#pragma warning disable CS1998
            if (yieldUntilGuildsDownloaded)
            {
                XanBotLogger.WriteLine("§2Downloading server data from Discord...");
                ManualResetEvent completedSignal = new ManualResetEvent(false);
                Client.GuildDownloadCompleted += async evt =>
                {
                    HasFinishedGettingGuildData = true;
                    completedSignal?.Set();
                };
                completedSignal.WaitOne();
                completedSignal.Dispose();
                completedSignal = null;
            }
            else
            {
                Client.GuildDownloadCompleted += async evt =>
                {
                    HasFinishedGettingGuildData = true;
                };
            }
#pragma warning restore CS1998

            XanBotLogger.WriteLine("§2Setting up frontend console...");
            Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCtrlCExit);
            XanBotConsoleCore.StartUpdateConsoleTask();

            Created = true;
            XanBotLogger.WriteLine("§aInitialization complete!"); // This fixes an occasional formatting issue where it renders in the typing text color.
        }
        /// <summary>
        /// Should be called from a while loop in the bot's core thread.<para/>
        /// This runs an update cycle on the console, which listens for operator input and updates display based on said input, and initiates commands if the user presses return in the console.
        /// </summary>
        private static void UpdateConsole()
        {
            CurrentlyTypedConsoleText = "";
            Console.ForegroundColor   = ConsoleColor.Cyan;

            ConsoleKeyInfo kInfo = Console.ReadKey(false);
            char           c     = kInfo.KeyChar;

            while (c != '\n' && c != '\r')
            {
                // Didn't press enter.

                // Up arrow. Go up one in the command cache if possible.
                if (kInfo.Key == ConsoleKey.UpArrow)
                {
                    CommandCachePosition      = Math.Max(CommandCachePosition - 1, 0);
                    CurrentlyTypedConsoleText = CommandCache[CommandCachePosition];
                    Console.CursorLeft        = 0;
                    Console.Write(new string(' ', Console.BufferWidth - 1));
                    Console.CursorLeft = 0;
                    Console.Write(CurrentlyTypedConsoleText);
                }
                // Down arrow. Go down one in the command cache if possible, or display empty text. To do: Make it display whatever was there if we typed something, hit up, then hit down again.
                else if (kInfo.Key == ConsoleKey.DownArrow)
                {
                    if (CommandCachePosition + 1 == CommandCache.Count)
                    {
                        //We'd be going after the last index.
                        CurrentlyTypedConsoleText = "";
                    }
                    else
                    {
                        CurrentlyTypedConsoleText = CommandCache[CommandCachePosition + 1];
                    }
                    CommandCachePosition = Math.Min(CommandCachePosition + 1, CommandCache.Count - 1);

                    Console.CursorLeft = 0;
                    Console.Write(new string(' ', Console.BufferWidth - 1));
                    Console.CursorLeft = 0;
                    Console.Write(CurrentlyTypedConsoleText);
                }

                // Backspace. Erase text.
                if (c == '\b')
                {
                    if (CurrentlyTypedConsoleText.Length > 0)
                    {
                        //Backspace. Remove the char.
                        Console.SetOut(CONSOLE_WRITER);
                        Console.CursorLeft = Math.Max(Console.CursorLeft - 1, 0);
                        Console.Write(' ');
                        Console.CursorLeft        = 0;
                        CurrentlyTypedConsoleText = CurrentlyTypedConsoleText.Substring(0, CurrentlyTypedConsoleText.Length - 1);
                        Console.Write(new string(' ', Console.BufferWidth - 1));
                        Console.CursorLeft = 0;
                        Console.Write(CurrentlyTypedConsoleText);
                    }
                    // Used to beep if you tried to erase without anything typed. Got annoying. Disabled it.
                    //else {
                    //	Console.Beep();
                    //}
                }

                // If we're trying to type and there's enough space...
                if (CurrentlyTypedConsoleText.Length < Console.BufferWidth - 1)
                {
                    if (c != '\b' && (kInfo.Modifiers & ConsoleModifiers.Control) == 0)
                    {
                        // Ctrl isn't behind held and it's not a backspace. Append the currently typed text.
                        CurrentlyTypedConsoleText += c;
                        if (CurrentlyTypedConsoleText.Length == Console.BufferWidth - 1)
                        {
                            // If the length of the current text is now the buffer width, don't let it type anymore.
                            // I would allow multi-line editing but this was quite problematic and I just didn't bother.
                            Console.SetOut(NULL_WRITER);
                        }
                    }
                }
                else
                {
                    // Beep if we can't type any more text.
                    Console.Beep();
                }


                // Holding Ctrl...
                if ((kInfo.Modifiers & ConsoleModifiers.Control) != 0)
                {
                    if (c == 0x16)
                    { //0x16 SYN aka CTRL+V was input. Paste.
                        string clipboard = GetClipboard();
                        if (clipboard != null)
                        {
                            // Try to add the text to the current typed stuff.
                            string resultText = CurrentlyTypedConsoleText + clipboard;
                            if (resultText.Length < Console.BufferWidth - 1)
                            {
                                // Append the text to the currently typed text.
                                CurrentlyTypedConsoleText += clipboard;
                                Console.CursorLeft         = 0;
                                Console.Write(new string(' ', Console.BufferWidth - 1));
                                Console.CursorLeft = 0;
                                Console.Write(CurrentlyTypedConsoleText);
                            }
                            else
                            {
                                // If the text is too long after adding the pasted content, beep and don't add it.
                                Console.Beep();
                            }
                            // Old clipboard test. Failed.
                            //Stream consoleOut = Console.OpenStandardOutput();
                            //consoleOut.Write(Encoding.Default.GetBytes(clipboard), 0, clipboard.Length);
                            //Console.CursorLeft--;
                            //Console.Out.Write(clipboard);
                        }
                        else
                        {
                            // Beep if nothing is on the clipboard that can be pasted.
                            Console.Beep();
                        }
                    }
                }

                // Prep for loop repeat.
                kInfo = Console.ReadKey(false);
                c     = kInfo.KeyChar;
            }

            // If we make it here, we've pressed enter. Log the input text in the console.
            Console.ForegroundColor = ConsoleColor.DarkYellow;
            Console.Write("CONSOLE ISSUED >> ");
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.Write(CurrentlyTypedConsoleText);
            Console.ForegroundColor = ConsoleColor.Green;

            string cmd = CurrentlyTypedConsoleText;

            CurrentlyTypedConsoleText = "";
            Console.CursorLeft        = 0;
            Console.CursorTop++;
            try
            {
                XanBotLogger.LogMessage("CONSOLE ISSUED >> " + cmd);
                CommandMarshaller.HandleCommand(cmd).GetAwaiter().GetResult();
                CommandCache.Add(cmd);
                CommandCachePosition = CommandCache.Count;
            }
            catch (Exception ex)
            {
                // In this case, the command failed. We should clear the typed console text.
                // Write the exception to the console.
                XanBotLogger.WriteException(ex);
            }
        }