public void disconnect()
 {
     this.doNotReconnect = true;
     this.ceres?.shutdown();
     this.ceres = null;
     this.twitchClient.Disconnect();
     this.twitchClient = null;
 }
        public void ConfigureCeres(CeresConfiguration newSettings)
        {
            lock (this.commands)
            {
                this.channelDetails.ceresConfiguration = newSettings;

                // Complete reset:
                this.commands.RemoveAll(x => x.commandType == Constants.CommandType.StartCeres ||
                                        x.commandType == Constants.CommandType.StartCeres ||
                                        x.commandType == Constants.CommandType.EndCeres ||
                                        x.commandType == Constants.CommandType.CancelCeres ||
                                        x.commandType == Constants.CommandType.Guess);

                if (this.channelDetails.ceresConfiguration != null && this.channelDetails.ceresConfiguration.ceresEnabled)
                {
                    // only use the one ceres object. If the user turns it off and then on again we want to keep this round going
                    if (this.ceres == null)
                    {
                        this.ceres = new CeresGuessingGame(
                            logger,
                            channelDetails.channelName,
                            newSettings.numberOfSecondsToGuess,
                            () =>
                        {
                            // On begin guessing
                            lock (this.commands)
                            {
                                this.sendMessage($"Ceres round started. Type {(this.commands.FirstOrDefault(x => x.commandType == Constants.CommandType.Guess).commandText)} xxxx to register your Ceres time guess. You have {this.channelDetails.ceresConfiguration.numberOfSecondsToGuess} seconds to place your guess.");
                            }
                        },

                            () =>
                        {
                            // On round canceled
                            this.sendMessage("Ceres round cancelled.");
                        },

                            (endTime, guesses) =>
                        {
                            // On round finished

                            var staticWinners = new List <Tuple <CeresGuess, int, string> >();
                            foreach (var a in guesses)
                            {
                                string closeness;
                                var points = this.channelDetails.ceresConfiguration.GetStaticPointsAwarded(endTime, a.guess, out closeness);
                                if (points != null && points > 0)
                                {
                                    staticWinners.Add(new Tuple <CeresGuess, int, string>(a, points.Value, closeness));
                                }
                            }

                            var anyWinners  = staticWinners.Any();
                            var rankWinners = new List <Tuple <CeresGuess, int, string> >();
                            if (this.channelDetails.ceresConfiguration.closestRewards.Any())
                            {
                                foreach (var a in this.channelDetails.ceresConfiguration.closestRewards)
                                {
                                    if (anyWinners && !a.awardEvenIfOtherWinners)
                                    {
                                        continue;
                                    }

                                    var newWinners = guesses.Where(x => x.rank == a.rankAwarded).ToList();
                                    foreach (var g in newWinners)
                                    {
                                        var str = g.rank.ToString();
                                        switch (g.rank)
                                        {
                                        case 1:
                                            str += "st";
                                            break;

                                        case 2:
                                            str += "nd";
                                            break;

                                        case 3:
                                            str += "rd";
                                            break;

                                        default:
                                            str += "th";
                                            break;
                                        }


                                        rankWinners.Add(new Tuple <CeresGuess, int, string>(g, a.pointsAwarded, str));
                                    }
                                }
                            }

                            var anybodyWon = false;
                            var messages   = new List <string>();

                            foreach (var a in this.channelDetails.ceresConfiguration.magicTimes)
                            {
                                if (a.ceresTime == endTime)
                                {
                                    var curString = (a.pointsAwarded == 1 ? this.channelDetails.pointsManager.CurrencySingular : this.channelDetails.pointsManager.CurrencyPlural);

                                    var timeStr = endTime.ToString().Insert(2, ".");

                                    if (!guesses.Any())
                                    {
                                        messages.Add($"Nobody guessed. You all missed out on {a.pointsAwarded} {curString} from a Ceres time of {timeStr}! :(");
                                        break;
                                    }

                                    if (guesses.Count > 4)
                                    {
                                        messages.Add($"Awarding {a.pointsAwarded} {curString} to {guesses.Count} beautiful people for a Ceres time of {timeStr}!");
                                    }
                                    else
                                    {
                                        messages.Add($"Awarding {a.pointsAwarded} {curString} to {(string.Join(", ", guesses.Select(x => x.userID).ToArray()))} for a Ceres time of {timeStr}!");
                                    }

                                    foreach (var g in guesses)
                                    {
                                        anybodyWon = true;
                                        this.channelDetails.pointsManager.GivePlayerPoints(g.userID, a.pointsAwarded, out long?newPoints);
                                    }

                                    break;
                                }
                            }

                            foreach (var a in staticWinners)
                            {
                                anybodyWon = true;

                                long?newPoints;

                                this.channelDetails.pointsManager.GivePlayerPoints(a.Item1.userID, a.Item2, out newPoints);

                                var curString = (a.Item2 == 1 ? this.channelDetails.pointsManager.CurrencySingular : this.channelDetails.pointsManager.CurrencyPlural);

                                messages.Add(
                                    $"{a.Item1.userID} guessed {a.Item3}, and wins {a.Item2} {curString}{(newPoints.HasValue ? ($" ({newPoints} total)") : "")}!"
                                    );
                            }

                            foreach (var a in rankWinners)
                            {
                                anybodyWon = true;

                                long?newPoints;

                                this.channelDetails.pointsManager.GivePlayerPoints(a.Item1.userID, a.Item2, out newPoints);

                                var curString = (a.Item2 == 1 ? this.channelDetails.pointsManager.CurrencySingular : this.channelDetails.pointsManager.CurrencyPlural);

                                messages.Add(
                                    $"{a.Item1.userID} came in {a.Item3}, and wins {a.Item2} {curString}{(newPoints.HasValue ? ($" ({newPoints} total)") : "")}!"
                                    );
                            }

                            if (!anybodyWon)
                            {
                                messages.Add("Ceres round ended. Nobody won. :(");
                            }


                            this.SendMessagesTogether(messages);
                        },

                            () =>
                        {
                            // On guessing time finished
                            this.sendMessage($"Ceres time guessing has ended. Good luck!");
                        });
                    }

                    this.commands.Add(new Command(
                                          Constants.CommandType.StartCeres,
                                          new Action <ChatMessage, Command>((message, command) =>
                    {
                        this.ceres.beginGuessing();
                    }),
                                          Constants.AccessLevel.Moderator,
                                          false,
                                          true,
                                          "!startceres",
                                          false,
                                          5,
                                          null
                                          ));

                    this.commands.Add(new Command(
                                          Constants.CommandType.EndCeres,
                                          new Action <ChatMessage, Command>((message, command) =>
                    {
                        string endtime = new string(message.Message.Where(Char.IsDigit).ToArray());     // linq magic to extract any leading/trailing chars

                        if (endtime.Length != 4)
                        {
                            this.logger.LogWarning("Invalid endtime (" + endtime + ")", true);
                            return;
                        }

                        var time = int.Parse(endtime);

                        this.ceres.completeGuessingGame(time);
                    }),
                                          Constants.AccessLevel.Moderator,
                                          true,
                                          false,
                                          "!endceres",
                                          true, // does have a param
                                          5,
                                          null
                                          ));

                    this.commands.Add(new Command(
                                          Constants.CommandType.CancelCeres,
                                          new Action <ChatMessage, Command>((message, command) =>
                    {
                        this.ceres.cancelGuessing();
                    }),
                                          Constants.AccessLevel.Moderator,
                                          true,
                                          false,
                                          "!cancelceres",
                                          false,
                                          5,
                                          null
                                          ));


                    this.commands.Add(new Command(
                                          Constants.CommandType.Guess,
                                          new Action <ChatMessage, Command>((message, command) =>
                    {
                        string guess = new string(message.Message.Where(Char.IsDigit).ToArray());     // linq magic to extract any leading/trailing chars

                        if (guess.Length != 4)
                        {
                            lock (this.commands)
                            {
                                sendMessage($"I'm not sure what guess you meant, @{message.Username} . Please enter a new guess with {(this.commands.FirstOrDefault(x => x.commandType == Constants.CommandType.Guess).commandText)} xxxx");
                            }
                            return;
                        }

                        var time = int.Parse(guess);

                        this.ceres.makeGuess(message.Username, time);
                    }),
                                          Constants.AccessLevel.Public,
                                          true,
                                          false,
                                          "!guess",
                                          true, // does have parameters
                                          null,
                                          null
                                          ));
                }
                else if (this.ceres != null)
                {
                    this.ceres.shutdown();
                    this.ceres = null;
                }

                updateHelpString();
            }
        }