/// <summary>
        /// Loaded event for the chat window.
        /// 
        /// Initialization happens here
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void ChatWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // Enumerate all currently downloaded topics to allow the user to
            // select which ones to use
            topics = (from topic in TopicChooser.Topics(order)
                      select new SelectableTopic(topic)).ToList();

            // If there are no topics, then we have to bail out here and wait for the
            // user to download some.
            if (topics.Count == 0)
                return;

            // Create and initialize the first topic
            markov = TopicChooser.GetRandomTopicAsync(order).Result;
            markov.MaxSentenceLength = sentenceLength;
            ChatView.CurrentTopic = markov.CurrentTopic;
            ChatView.CurrentSubtopic = markov.CurrentSubtopic;

            // Make the AI start the conversation so we don't start off
            // with an empty conversation box.
            Status = "ArmchairIntellectual is typing...";
            Message.IsEnabled = false;
            ChatView.AiSay(await markov.GetUnpromptedSentenceAsync());
            Message.IsEnabled = true;
            Message.Focus();
            Status = "Ready.";
        }
        /// <summary>
        /// Get the next topic from a predefined list of topic names
        /// </summary>
        /// <param name="input"></param>
        /// <param name="currentTopic"></param>
        /// <param name="topics"></param>
        /// <returns></returns>
        public static MarkovChain GetNextTopic(string input, MarkovChain currentTopic, List<string> topics)
        {
            string existingTopic = FindExistingTopicName(input, topics);
            if (existingTopic != null)
                return new MarkovChain(existingTopic, currentTopic.Order);

            return currentTopic;
        }
        /// <summary>
        /// Change the Markov order by copying the state of the current
        /// Markov chain into a new object with a different order.
        /// </summary>
        /// <param name="markov"></param>
        /// <param name="order"></param>
        /// <returns></returns>
        public static MarkovChain UpdateMarkovOrder(MarkovChain markov, int order)
        {
            var probabilityHash = TextCorpus.GetProbabilityHashAsync(markov.CurrentTopic, order).Result;
            var newMarkov = new MarkovChain(probabilityHash, markov.CurrentTopic);
            newMarkov.CurrentSubtopic = markov.CurrentSubtopic;
            newMarkov.MaxSentenceLength = markov.MaxSentenceLength;

            return newMarkov;
        }
        /// <summary>
        /// Given a current Markov chain and user input, get the next
        /// major topic (or return the current one if no switch is to be made).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="currentTopic"></param>
        /// <returns></returns>
        public static async Task<MarkovChain> GetNextTopicAsync(string input, MarkovChain currentTopic)
        {
            // Check if there is an existing topic that matches the user's input
            string existingTopic = FindExistingTopicName(input, currentTopic.Order);
            if (existingTopic != null)
                return new MarkovChain(existingTopic, currentTopic.Order);

            // If not, get the name of the new article that should be downloaded
            string newTopic = FindNewTopicName(input);
            if (newTopic != null)
            {
                try
                {
                    // Load a new Markov chain from the new topic name
                    return await MarkovChain.CreateMarkovChainAsync(newTopic, currentTopic.Order);
                }
                catch (CorpusLoadFailedException)
                {
                    return currentTopic;
                }
            }

            return currentTopic;
        }
        /// <summary>
        /// Click event for the send button.
        /// 
        /// Fires when the user sends a message to the AI
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void Send_Click(object sender, RoutedEventArgs e)
        {
            // immediately disable the send function to wait for the AI's response.
            Message.IsEnabled = false;

            // Let the user know work is happening
            Status = "ArmchairIntellectual is typing...";

            // Find the next topic of conversation, if any
            if (anyTopic == true)
                markov = await TopicChooser.GetNextTopicAsync(Message.Text, markov);
            else
                markov = TopicChooser.GetNextTopic(Message.Text, markov, (from topic in topics
                                                                          where topic.Selected == true
                                                                          select topic.TopicName).ToList());

            // Asynchronously process the user's input to find the new seed and subtopic.
            await markov.ProcessInputAsync(Message.Text);

            // Update the chatview's conversation boxes
            ChatView.CurrentTopic = markov.CurrentTopic;
            ChatView.CurrentSubtopic = markov.CurrentSubtopic;

            // Add the user's text to the new subtopic box, rather than the one that
            // was active when the message was sent
            ChatView.UserSay(Message.Text);

            // Asynchronously generate a sentence in response
            ChatView.AiSay(await markov.GetSentenceAsync());

            // Give control of the chat back to the user.
            Status = "Ready.";
            Message.Text = "";
            Message.IsEnabled = true;
            Message.Focus();
        }
        /// <summary>
        /// Updates the markov chain after a change of options has taken place
        /// </summary>
        private async Task UpdateMarkovChainAsync()
        {
            // If the program started and there were no existing topics,
            // the markov chain will be null and we should now just pick one.
            if (markov == null)
            {
                markov = await TopicChooser.GetRandomTopicAsync(order);
            }
            else
            {
                // If the user removed the topic that was currently in use,
                // the AI has to abandon that topic and pick a new one.
                if ((from t in topics
                     where t.TopicName.ToLower() == markov.CurrentTopic.ToLower()
                     select t).Count() == 0)
                    markov = await TopicChooser.GetRandomTopicAsync(order);
            }


            // Update markov settings.
            markov.MaxSentenceLength = sentenceLength;
            MarkovChain.UpdateMarkovOrder(markov, order);
        }