//update various history when the system choose the next topic
        public void updateHistory(Feature nextTopic)
        {
            //update spatial constraint information
            bool spatialExist = false;
            if (topicHistory.Count() > 0)
            {
                Feature prevTopic = graph.getFeature(topicHistory[topicHistory.Count() - 1]);
                if (prevTopic.getNeighbor(nextTopic.Data) != null)
                {
                    foreach(string str in Directional_Words)
                    {
                        if (str == prevTopic.getRelationshipNeighbor(nextTopic.Data))
                        {
                            prevSpatial = str;
                            spatialExist = true;
                            break;
                        }
                    }
                }
            }
            if (!spatialExist)
            {
                prevSpatial = "";
            }

            //update temporal constraint information
            FeatureSpeaker temp = new FeatureSpeaker(this.graph, temporalConstraintList);
            List<int> temporalIndex = temp.temporalConstraint(nextTopic,turn,topicHistory);
            for (int x = 0; x < temporalIndex.Count(); x++)
            {
                temporalConstraintList[temporalIndex[x]].Satisfied = true;
            }
            //update topic's history
            topicHistory.Add(nextTopic.Data);
        }
        //Form2 calls this function
        //input is the input to be parsed.
        //messageToServer indicates whether or not we are preparing a response to the front-end.
        //forLog indicates whether or not we are preparing a response for a log output.
        //outOfTopic indicates whether or not we are continuing out-of-topic handling.
        //projectAsTopic true means we use forward projection to choose the next node to traverse to based on
        //  how well the nodes in the n-length path from the current node relate to the current node.
        public string ParseInput(string input, bool messageToServer = false, bool forLog = false, bool outOfTopic = false, bool projectAsTopic = false)
        {
            string answer = IDK;
            string noveltyInfo = "";
            double currentTopicNovelty = -1;
            // Pre-processing

            //Console.WriteLine("parse input " + input);

            //The input may be delimited by colons. Try to split it.
            String[] split_input = input.Trim().Split(':');
            //Console.WriteLine("split input " + split_input[0]);

            // Lowercase for comparisons
            input = input.Trim().ToLower();
            //Console.WriteLine("trimmed lowered input " + input);

            if (!string.IsNullOrEmpty(input))
            {
                // Check to see if the AIML Bot has anything to say
                Request request = new Request(input, this.user, this.bot);

                Result result = bot.Chat(request);
                string output = result.Output;

                if (output.Length > 0)
                {
                    if (!output.StartsWith(FORMAT))
                        return output;

                    //MessageBox.Show("Converted output reads: " + output);
                    input = output.Replace(FORMAT, "").ToLower();
                }
            }

            // Remove punctuation
            input = RemovePunctuation(input);

            // Check
            if (this.topic == null)
                this.topic = this.graph.Root;
            //Console.WriteLine("Before new feature speaker in parse input");
            FeatureSpeaker speaker = new FeatureSpeaker(this.graph, temporalConstraintList, prevSpatial, topicHistory);
            //Console.WriteLine("after new speaker in parse input");
            if (split_input.Length != 0 || messageToServer)
            {
                //Step-through command from Query window.
                if (split_input[0].Equals("STEP"))
                {
                    //Step through the program with blank inputs a certain number of times,
                    //specified by the second argument in the command
                    //Console.WriteLine("step_count " + split_input[1]);
                    int step_count = int.Parse(split_input[1]);

                    //TESTING JOINT MENTIONS
                    //If there are two more colon-separated integers in the command, they are two node IDs that should be mentioned together.
                    if (split_input.Length > 2)
                    {
                        //Since this is just a test, first, clear joint_mention_sets
                        joint_mention_sets.Clear();
                        //Get the two indices from the command
                        int index_1 = int.Parse(split_input[2]);
                        int index_2 = int.Parse(split_input[3]);
                        //Add the pair as a list of features to joint_mention_sets.
                        List<Feature> joint_set = new List<Feature>();
                        joint_set.Add(this.graph.getFeature(index_1));
                        joint_set.Add(this.graph.getFeature(index_2));
                        joint_mention_sets.Add(joint_set);
                    }//end if

                    //Create an answer by calling the ParseInput function step_count times.
                    answer = "";
                    for (int s = 0; s < step_count; s++)
                    {
                        //Get forServer and forLog responses.
                        //Treat every 5th node as topic
                        if (s % 5 == 1)
                        {
                            //Last parameter true means the current node is the topic node
                            answer += ParseInput("", true, true, false, false);
                        }//end if
                        else
                            answer += ParseInput("", true, true, false, false);
                        answer += "\n";
                    }
                    //Console.WriteLine("answer " + answer);
                    //Just return this answer by itself
                    return answer;
                }//end if

                // GET_NODE_VALUES command from Unity front-end
                if (split_input[0].Equals("GET_NODE_VALUES"))
                {
                    Console.WriteLine("In get node values");
                    //Get the node we wish to get a set of values for, by data.
                    //"data" is represented by each node's data field in the XML.
                    //In the split input string, index 1 is the data of the node we want
                    //to get values for.
                    //Index 2 is the data of the node we are getting values relative to.
                    string current_node_data = split_input[1];
                    string old_node_data = split_input[2];
                    //Get the features for these two nodes
                    Feature current_feature = this.graph.getFeature(current_node_data);
                    Feature old_feature = this.graph.getFeature(old_node_data);
                    //If EITHER feature is null, return an error message.
                    if (current_feature == null || old_feature == null)
                        return "no feature found";
                    double[] return_node_values = speaker.calculateScoreComponents(current_feature, old_feature);
                    //Turn them into a colon-separated string, headed by
                    //the key-phrase "RETURN_NODE_VALUES"
                    string return_string = return_node_values[Constant.ScoreArrayScoreIndex] + ":"
                        + return_node_values[Constant.ScoreArrayNoveltyIndex] + ":"
                        + return_node_values[Constant.ScoreArrayDiscussedAmountIndex] + ":"
                        + return_node_values[Constant.ScoreArrayExpectedDramaticIndex] + ":"
                        + return_node_values[Constant.ScoreArraySpatialIndex] + ":"
                        + return_node_values[Constant.ScoreArrayHierarchyIndex] + ":";

                    return return_string;
                }//end if
                // GET_WEIGHT command from Unity front-end
                else if (split_input[0].Equals("GET_WEIGHT"))
                {
                    //Return a colon-separated string of every weight value
                    string return_string = "Weights: ";
                    double[] weight_array = this.graph.getWeightArray();
                    for (int i = 0; i < weight_array.Length; i++)
                    {
                        if (i != 0)
                            return_string += ":";
                        return_string += weight_array[i];
                    }//end for
                    return return_string;
                }//end else if
                // SET_WEIGHT command from Unity front-end
                else if (split_input[0].Equals("SET_WEIGHT"))
                {

                    //For each pair,
                    //Index 1 is the index of the weight we wish to adjust.
                    //Index 2 is the new weight value.
                    for (int m = 1; m < split_input.Length; m += 2)
                    {
                        this.graph.setWeight(int.Parse(split_input[m]), double.Parse(split_input[m + 1]));
                    }//end for

                    //Return the new weight values right away.
                    string return_string = "Weights: ";
                    double[] weight_array = this.graph.getWeightArray();
                    for (int i = 0; i < weight_array.Length; i++)
                    {
                        if (i != 0)
                            return_string += ":";
                        return_string += weight_array[i];
                    }//end for
                    return return_string;
                }//end else if
                //GET_RELATED command from Unity front-end.
                //Returns a message containing a list of most novel and most proximal nodes
                else if (split_input[0].Equals("GET_RELATED"))
                {
                    //GET_RELATED only gets related nodes for the current topic.
                    noveltyInfo = speaker.getNovelty(this.topic, this.turn, noveltyAmount);
                    return "Novelty:" + noveltyInfo + ":Proximal:" + speaker.getProximal(this.topic, noveltyAmount);
                }//end else if
                //SET_LANGUAGE command from Unity front-end.
                else if (split_input[0].Equals("SET_LANGUAGE"))
                {
                    //Index 1 is the new language mode.
                    language_mode_display = int.Parse(split_input[1]);
                    language_mode_tts = int.Parse(split_input[2]);
                    return "Language to display set to " + language_mode_display + ": Language of TTS set to " + language_mode_tts;
                }//end else if
                //BEGIN_TTS command from Unity front-end.
                else if (split_input[0].Equals("BEGIN_TTS"))
                {
                    if (buffered_tts.Equals(""))
                    {
                        return "-1";
                    }//end if
                    else
                    {
                        string to_return = "TTS_COMPLETE##" + buffered_tts;
                        buffered_tts = "";

                        return to_return;
                    }
                }//end else if
                //GET_TTS command from Unity front-end.
                else if (split_input[0].Equals("GET_TTS"))
                {
                    if (buffered_tts.Equals(""))
                    {
                        return "-1";
                    }//end if
                    else
                    {
                        return buffered_tts;
                    }//end else
                }//end else if
            }//end else if

            // CASE: Nothing / Move on to next topic
            if (string.IsNullOrEmpty(input))
            {
                Feature nextTopic = this.topic;
                string[] newBuffer;

                // == testing forward projection
                if (false)
                {
                    Stopwatch stopWatch = new Stopwatch();
                    stopWatch.Start();

                    int forwardTurn = 20;
                    List<Feature> testingForwardP = speaker.forwardProjection(nextTopic, forwardTurn);

                    stopWatch.Stop();
                    TimeSpan ts = stopWatch.Elapsed;
                    // Format and display the TimeSpan value.
                    string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                        ts.Hours, ts.Minutes, ts.Seconds,
                        ts.Milliseconds / 10);
                    Console.WriteLine("RunTime of forward projection" + elapsedTime);
                    //print out all the topics
                    for (int i = 0; i < forwardTurn; i++)
                    {
                        Console.WriteLine(testingForwardP[i].Data);
                    }
                }

                // Can't guarantee it'll actually move on to anything...
                //If we are not projecting the current node as a topic, pick the next node normally
                if (!projectAsTopic)
                {
                    nextTopic = speaker.getNextTopic(nextTopic, "", this.turn);
                    //Console.WriteLine("Next Topic from " + this.topic.Data + " is " + nextTopic.Data);
                }//end if
                //If we are projecting the current node as a topic, pick the next node whose projected
                //path of nodes relate most to the current node (has the highest score).
                else
                {
                    Console.WriteLine("Current Topic: " + this.topic.Data);
                    //Go this many steps in the forward projection.
                    int forward_turn = 5;
                    //Get a list of all the neighbors to the current node
                    List<Tuple<Feature, double, string>> all_neighbors = this.topic.Neighbors;
                    //print out all neighbors
                    Console.WriteLine("Neighbors: ");
                    for (int i = 0; i < all_neighbors.Count; i++)
                    {
                        Console.WriteLine(all_neighbors[i].Item1.Data);
                    }//end for

                    //For each neighbor, find its projected path and sum the score of each node in the path relative to the current node.
                    double highest_score = -10000;
                    foreach (Tuple<Feature, double, string> neighbor_tuple in all_neighbors)
                    {
                        //First, check if the neighbor is a filtered node.
                        //If so, do not consider it.
                        if (filter_nodes.Contains(neighbor_tuple.Item1.Data))
                            continue;

                        List<Feature> projected_path = speaker.forwardProjection(neighbor_tuple.Item1, forward_turn);
                        //print out all the topics
                        /*Console.WriteLine("Projected Path: ");
                        for (int i = 0; i < forward_turn; i++)
                        {
                            Console.WriteLine(projected_path[i].Data);
                        }//end for*/

                        double total_score = 0;

                        //Total score calculation for topic
                        //Sum score of each path node relative to the current node
                        /*
                        foreach (Feature path_node in projected_path)
                        {
                            total_score += speaker.calculateScore(path_node, this.topic);
                        }//end foreach
                        //Console.WriteLine("Score for path: " + total_score);
                        */
                        //Total score calculation for joint mentions
                        //If a joint mention appears in the path, add an amount (currently just the joint mention weight)
                        //to the score of the neighbor (first path node) relative to the current node.
                        bool joint_mention_exists = true;
                        //For testing purposes, only check the first list in joint_mention_sets
                        foreach (Feature temp_node in joint_mention_sets[0])
                        {
                            if (!projected_path.Contains(temp_node))
                                joint_mention_exists = false;
                        }//end foreach
                        if (joint_mention_exists)
                        {
                            Console.WriteLine("Joint mention exists");
                            total_score = speaker.calculateScore(neighbor_tuple.Item1, this.topic) + this.graph.getSingleWeight(Constant.JointWeightIndex);
                        }//end if

                        if (total_score > highest_score)
                        {
                            highest_score = total_score;
                            nextTopic = neighbor_tuple.Item1;
                        }//end if
                    }//end foreach
                    //At the end of this foreach, nextTopic is set to the next node whose projected path had the highest sum score
                    //relative to the current node.
                    Console.WriteLine("Next Topic from " + this.topic.Data + " is " + nextTopic.Data + " with score " + highest_score);
                    Console.WriteLine("Path: ");
                    List<Feature> test_path = speaker.forwardProjection(nextTopic, forward_turn);
                    //print out all the topics
                    for (int i = 0; i < forward_turn; i++)
                    {
                        Console.WriteLine(test_path[i].Data);
                    }//end for
                }//end else

                /*
                //Check for filter nodes.
                if (filter_nodes.Contains(nextTopic.Data))
                {
                    //If it is a filter node, take another step.
                    Console.WriteLine("Filtering out " + nextTopic.Data);
                    ParseInput("", false, false);
                }//end if
                */

                noveltyInfo = speaker.getNovelty(nextTopic, this.turn, noveltyAmount);
                currentTopicNovelty = speaker.getCurrentTopicNovelty();
                noveltyValue = speaker.getCurrentTopicNovelty();
                newBuffer = FindStuffToSay(nextTopic);
                //MessageBox.Show("Explored " + nextTopic.Data + " with " + newBuffer.Length + " speaks.");

                nextTopic.DiscussedAmount += 1;
                this.graph.setFeatureDiscussedAmount(nextTopic.Data, nextTopic.DiscussedAmount);
                this.topic = nextTopic;
                // talk about
                this.buffer = newBuffer;
                answer = this.buffer[b++];
                if (projectAsTopic)
                    answer = "*****" + answer;
            }
            // CASE: Tell me more / Continue speaking
            else if (input.Contains("more") && input.Contains("tell"))
            {
                this.topic.DiscussedAmount += 1;
                this.graph.setFeatureDiscussedAmount(this.topic.Data, this.topic.DiscussedAmount);
                // talk about
                if (b < this.buffer.Length)
                    answer = this.buffer[b++];
                else
                {
                    answer = "I've said all I can about that topic!" + "##" + "我已经把我知道的都说完了。" + "##";
                }

                noveltyInfo = speaker.getNovelty(this.topic, this.turn, noveltyAmount);
            }
            // CASE: New topic/question
            else
            {
                Query query = BuildQuery(input);
                if (query == null)
                {
                    return ParseInput("", messageToServer, false, true);
                    //answer = "I'm sorry, I'm afraid I don't understand what you are asking. But here's something I do know about. ";
                    //answer = answer + ParseInput("", false, false);
                    //out_of_topic = true;
                }
                else
                {
                    Feature feature = query.MainTopic;
                    feature.DiscussedAmount += 1;
                    this.graph.setFeatureDiscussedAmount(feature.Data, feature.DiscussedAmount);
                    this.topic = feature;
                    this.buffer = ParseQuery(query);
                    answer = this.buffer[b++];
                    noveltyInfo = speaker.getNovelty(this.topic, this.turn, noveltyAmount);
                }
            }

            //Update
            updateHistory(this.topic);
            this.turn++;

            if (answer.Length == 0)
            {
                return IDK;
            }
            else
            {
                if (messageToServer)
                {
                    //Return message to Unity front-end with both novel and proximal nodes
                    return MessageToServer(this.topic, answer, noveltyInfo, speaker.getProximal(this.topic, noveltyAmount), forLog, outOfTopic);
                }

                if (outOfTopic)
                    answer += ParseInput("", false, false);

                if (forLog)
                    return answer;
                else
                    return answer;// +" <Novelty Info: " + noveltyInfo + " > <Proximal Info: " + speaker.getProximal(this.topic, noveltyAmount) + ">";
            }
        }