/// <summary>
        /// Formats a trigger for the regular expression engine.
        /// </summary>
        /// <param name="user">The user ID of the caller.</param>
        /// <param name="profile">Client profile</param>
        /// <param name="trigger">The raw trigger text.</param>
        /// <returns></returns>
        private string triggerRegexp(string user, Client profile, string trigger)
        {
            // If the trigger is simply '*', it needs to become (.*?) so it catches the empty string.
            var regexp = trigger.ReplaceRegex("^\\*$", "<zerowidthstar>");

            // Simple regexps are simple.
            regexp = regexp.ReplaceRegex("\\*", "(.+?)");             // *  ->  (.+?)
            regexp = regexp.ReplaceRegex("#", "(\\d+?)");         // #  ->  (\d+?)
            regexp = regexp.ReplaceRegex("_", "(\\w+?)");     // _  ->  ([A-Za-z ]+?)
            regexp = regexp.ReplaceRegex("\\{weight=\\d+\\}", "");    // Remove {weight} tags
            regexp = regexp.ReplaceRegex("<zerowidthstar>", "(.*?)"); // *  ->  (.*?)

            // Handle optionals.
            if (regexp.IndexOf("[") > -1)
            {
                Regex reOpts = new Regex("\\s*\\[(.+?)\\]\\s*");

                foreach (Match mOpts in reOpts.Matches(regexp))
                {
                    var optional = mOpts.Groups[0].Value;
                    var contents = mOpts.Groups[1].Value;

                    // Split them at the pipes.
                    string[] parts = contents.SplitRegex("\\|");

                    // Construct a regexp part.
                    StringBuilder re = new StringBuilder();
                    for (int i = 0; i < parts.Length; i++)
                    {
                        // We want: \s*part\s*
                        re.Append("\\s*" + parts[i] + "\\s*");
                        if (i < parts.Length - 1)
                        {
                            re.Append("|");
                        }
                    }
                    string pipes = re.ToString();

                    // If this optional had a star or anything in it, e.g. [*],
                    // make it non-matching.
                    pipes = pipes.ReplaceRegex("\\(.+?\\)", "(?:.+?)");
                    pipes = pipes.ReplaceRegex("\\(\\d+?\\)", "(?:\\\\d+?)");
                    pipes = pipes.ReplaceRegex("\\(\\w+?\\)", "(?:\\\\w+?)");

                    // Put the new text in.
                    pipes = "(?:" + pipes + "|\\s*)";
                    regexp = regexp.Replace(optional, pipes);

                }
            }

            // Make \w more accurate for our purposes.
            regexp = regexp.Replace("\\w", "[A-Za-z ]");

            // Filter in arrays.
            if (regexp.IndexOf("@") > -1)
            {
                // Match the array's name.
                Regex reArray = new Regex("\\@(.+?)\\b");

                foreach (Match mArray in reArray.Matches(regexp))
                {
                    string array = mArray.Groups[0].Value;
                    string name = mArray.Groups[1].Value;

                    // Do we have an array by this name?
                    if (arrays.ContainsKey(name))
                    {
                        string[] values = arrays[name].ToArray();
                        StringBuilder joined = new StringBuilder();

                        // Join the array.
                        for (int i = 0; i < values.Length; i++)
                        {
                            joined.Append(values[i]);
                            if (i < values.Length - 1)
                            {
                                joined.Append("|");
                            }
                        }

                        // Final contents...
                        string rep = "(?:" + joined.ToString() + ")";
                        regexp = regexp.Replace(array, rep);
                    }
                    else
                    {
                        // No array by this name.
                        regexp = regexp.Replace(array, "");
                    }
                }
            }

            // Filter in bot variables.
            if (regexp.IndexOf("<bot") > -1)
            {
                Regex reBot = new Regex("<bot (.+?)>");

                foreach (Match mBot in reBot.Matches(regexp))
                {
                    string tag = mBot.Groups[0].Value;
                    string var = mBot.Groups[1].Value;
                    //string value = vars[var].ToLower().ReplaceRegex("[^a-z0-9 ]+", "");
                    string value = Util.StripNasties(vars[var].ToLower(), utf8);

                    // Have this?
                    if (vars.ContainsKey(var))
                    {
                        regexp = regexp.Replace(tag, value);
                    }
                    else
                    {
                        regexp = regexp.Replace(tag, "undefined");
                    }
                }
            }

            // Filter in user variables.
            if (regexp.IndexOf("<get") > -1)
            {
                Regex reGet = new Regex("<get (.+?)>");

                foreach (Match mGet in reGet.Matches(regexp))
                {
                    string tag = mGet.Groups[0].Value;
                    string var = mGet.Groups[1].Value;
                    //string value = profile.get(var).ToLower().ReplaceRegex("[^a-z0-9 ]+", "");
                    string value = Util.StripNasties(profile.get(var).ToLower(), utf8);

                    // Have this?
                    regexp = regexp.Replace(tag, value);
                }
            }

            // Input and reply tags.
            regexp = regexp.ReplaceRegex("<input>", "<input1>");
            regexp = regexp.ReplaceRegex("<reply>", "<reply1>");

            if (regexp.IndexOf("<input") > -1)
            {
                Regex reInput = new Regex("<input([0-9])>");

                foreach (Match mInput in reInput.Matches(regexp))
                {
                    string tag = mInput.Groups[0].Value;
                    int index = int.Parse(mInput.Groups[1].Value);
                    //string text = profile.getInput(index).ToLower().ReplaceRegex("[^a-z0-9 ]+", "");
                    string text = Util.StripNasties(profile.getInput(index).ToLower(), utf8);
                    regexp = regexp.Replace(tag, text);
                }
            }

            if (regexp.IndexOf("<reply") > -1)
            {
                Regex reReply = new Regex("<reply([0-9])>");
                foreach (Match mReply in reReply.Matches(regexp))
                {
                    string tag = mReply.Groups[0].Value;
                    int index = int.Parse(mReply.Groups[1].Value);
                    //string text = profile.getReply(index).ToLower().ReplaceRegex("[^a-z0-9 ]+", "");
                    string text = Util.StripNasties(profile.getReply(index).ToLower(), utf8);
                    regexp = regexp.Replace(tag, text);
                }
            }

            return regexp;
        }
        /// <summary>
        /// Process reply tags.
        /// </summary>
        /// <param name="user">The name of the end user.</param>
        /// <param name="profile">The RiveScript client object holding the user's profile</param>
        /// <param name="message">The message sent by the user.</param>
        /// <param name="reply">The bot's original reply including tags.</param>
        /// <param name="vstars"> The vector of wildcards the user's message matched.</param>
        /// <param name="vbotstars">The vector of wildcards in any %Previous.</param>
        /// <param name="step">The current recursion depth limit.</param>
        /// <returns></returns>
        private string processTags(string user, Client profile, string message, string reply,
                                   List<string> vstars, List<string> vbotstars, int step)
        {
            // Pad the stars.
            vstars.Insert(0, "");
            vbotstars.Insert(0, "");

            // Set a default first star.
            if (vstars.Count == 1)
            {
                vstars.Add("undefined");
            }
            if (vbotstars.Count == 1)
            {
                vbotstars.Add("undefined");
            }

            // Convert the stars into simple arrays.
            string[] stars = vstars.ToArray();
            string[] botstars = vbotstars.ToArray();

            // Shortcut tags.
            reply = reply.ReplaceRegex("<person>", "{person}<star>{/person}");
            reply = reply.ReplaceRegex("<@>", "{@<star>}");
            reply = reply.ReplaceRegex("<formal>", "{formal}<star>{/formal}");
            reply = reply.ReplaceRegex("<sentence>", "{sentence}<star>{/sentence}");
            reply = reply.ReplaceRegex("<uppercase>", "{uppercase}<star>{/uppercase}");
            reply = reply.ReplaceRegex("<lowercase>", "{lowercase}<star>{/lowercase}");

            // Quick tags.
            reply = reply.ReplaceRegex("\\{weight=\\d+\\}", ""); // Remove {weight}s
            reply = reply.ReplaceRegex("<input>", "<input1>");
            reply = reply.ReplaceRegex("<reply>", "<reply1>");
            reply = reply.ReplaceRegex("<id>", user);
            reply = reply.ReplaceRegex("\\\\s", " ");
            reply = reply.ReplaceRegex("\\\\n", "\n");
            reply = reply.ReplaceRegex("\\\\", "\\");
            reply = reply.ReplaceRegex("\\#", "#");

            // Stars
            reply = reply.ReplaceRegex("<star>", stars[1]);
            reply = reply.ReplaceRegex("<botstar>", botstars[1]);
            for (int i = 1; i < stars.Length; i++)
            {
                reply = reply.ReplaceRegex("<star" + i + ">", stars[i]);
            }
            for (int i = 1; i < botstars.Length; i++)
            {
                reply = reply.ReplaceRegex("<botstar" + i + ">", botstars[i]);
            }
            reply = reply.ReplaceRegex("<(star|botstar)\\d+>", "");

            // Input and reply tags.
            if (reply.IndexOf("<input") > -1)
            {
                Regex reInput = new Regex("<input([0-9])>");
                foreach (Match mInput in reInput.Matches(reply))
                {
                    string tag = mInput.Groups[0].Value;
                    int index = int.Parse(mInput.Groups[1].Value);
                    //string text = profile.getInput(index).ToLower().ReplaceRegex("[^a-z0-9 ]+", "");
                    string text = Util.StripNasties(profile.getInput(index).ToLower(), utf8);
                    reply = reply.Replace(tag, text);
                }
            }
            if (reply.IndexOf("<reply") > -1)
            {
                Regex reReply = new Regex("<reply([0-9])>");
                foreach (Match mReply in reReply.Matches(reply))
                {
                    string tag = mReply.Groups[0].Value;
                    int index = int.Parse(mReply.Groups[1].Value);
                    //string text = profile.getReply(index).ToLower().ReplaceRegex("[^a-z0-9 ]+", "");
                    string text = Util.StripNasties(profile.getReply(index).ToLower(), utf8);
                    reply = reply.Replace(tag, text);
                }
            }

            // {random} tag
            if (reply.IndexOf("{random}") > -1)
            {
                Regex reRandom = new Regex("\\{random\\}(.+?)\\{\\/random\\}");
                foreach (Match mRandom in reRandom.Matches(reply))
                {
                    string tag = mRandom.Groups[0].Value;
                    string[] candidates = mRandom.Groups[1].Value.SplitRegex("\\|");
                    string chosen = candidates[rand.Next(candidates.Length)];
                    reply = reply.Replace(tag, chosen);
                }
            }

            // <bot> tag
            if (reply.IndexOf("<bot") > -1)
            {
                Regex reBot = new Regex("<bot (.+?)>");
                foreach (Match mBot in reBot.Matches(reply))
                {
                    string tag = mBot.Groups[0].Value;
                    string var = mBot.Groups[1].Value;

                    // Have this?
                    if (vars.ContainsKey(var))
                    {
                        reply = reply.Replace(tag, vars[var]);
                    }
                    else
                    {
                        reply = reply.Replace(tag, "undefined");
                    }
                }
            }

            // <env> tag
            if (reply.IndexOf("<env") > -1)
            {
                Regex reEnv = new Regex("<env (.+?)>");
                foreach (Match mEnv in reEnv.Matches(reply))
                {
                    string tag = mEnv.Groups[0].Value;
                    string var = mEnv.Groups[1].Value;

                    // Have this?
                    if (globals.ContainsKey(var))
                    {
                        reply = reply.Replace(tag, globals[var]);
                    }
                    else
                    {
                        reply = reply.Replace(tag, "undefined");
                    }
                }
            }

            // {!stream} tag
            if (reply.IndexOf("{!") > -1)
            {
                Regex reStream = new Regex("\\{\\!(.+?)\\}");
                foreach (Match mStream in reStream.Matches(reply))
                {
                    string tag = mStream.Groups[0].Value;
                    string code = mStream.Groups[1].Value;
                    say("Stream new code in: " + code);

                    // Stream it.
                    this.stream(code);
                    reply = reply.Replace(tag, "");
                }
            }

            // {person}
            if (reply.IndexOf("{person}") > -1)
            {
                Regex rePerson = new Regex("\\{person\\}(.+?)\\{\\/person\\}");
                foreach (Match mPerson in rePerson.Matches(reply))
                {
                    string tag = mPerson.Groups[0].Value;
                    string text = mPerson.Groups[1].Value;

                    // Run person substitutions.
                    say("Run person substitutions: before: " + text);
                    text = Util.Substitute(person_s, person, text);
                    say("After: " + text);
                    reply = reply.Replace(tag, text);
                }
            }

            // {formal,uppercase,lowercase,sentence} tags
            if (reply.IndexOf("{formal}") > -1 || reply.IndexOf("{sentence}") > -1 ||
            reply.IndexOf("{uppercase}") > -1 || reply.IndexOf("{lowercase}") > -1)
            {
                string[] tags = { "formal", "sentence", "uppercase", "lowercase" };
                for (int i = 0; i < tags.Length; i++)
                {
                    Regex reTag = new Regex("\\{" + tags[i] + "\\}(.+?)\\{\\/" + tags[i] + "\\}");
                    foreach (Match mTag in reTag.Matches(reply))
                    {
                        string tag = mTag.Groups[0].Value;
                        string text = mTag.Groups[1].Value;

                        // string transform.
                        text = stringTransform(tags[i], text);
                        reply = reply.Replace(tag, text);
                    }
                }
            }

            // <set> tag
            if (reply.IndexOf("<set") > -1)
            {
                Regex reSet = new Regex("<set (.+?)=(.+?)>");
                foreach (Match mSet in reSet.Matches(reply))
                {
                    string tag = mSet.Groups[0].Value;
                    string var = mSet.Groups[1].Value;
                    string value = mSet.Groups[2].Value;

                    // Set the uservar.
                    profile.set(var, value);
                    reply = reply.Replace(tag, "");
                    say("Set user var " + var + "=" + value);
                }
            }

            // <add, sub, mult, div> tags
            if (reply.IndexOf("<add") > -1 || reply.IndexOf("<sub") > -1 ||
            reply.IndexOf("<mult") > -1 || reply.IndexOf("<div") > -1)
            {
                string[] tags = { "add", "sub", "mult", "div" };
                for (int i = 0; i < tags.Length; i++)
                {
                    Regex reTag = new Regex("<" + tags[i] + " (.+?)=(.+?)>");
                    foreach (Match mTag in reTag.Matches(reply))
                    {
                        string tag = mTag.Groups[0].Value;
                        string var = mTag.Groups[1].Value;
                        string value = mTag.Groups[2].Value;

                        // Get the user var.
                        string curvalue = profile.get(var);
                        int current = 0;
                        if (!curvalue.Equals("undefined"))
                        {
                            // Convert it to a int.
                            try
                            {
                                current = int.Parse(curvalue);
                            }
                            catch (FormatException)
                            {
                                // Current value isn't a number!
                                reply = reply.Replace(tag, "[ERR: Can't \"" + tags[i] + "\" non-numeric variable " + var + "]");
                                continue;
                            }
                        }

                        // Value must be a number too.
                        int modifier = 0;
                        try
                        {
                            modifier = int.Parse(value);
                        }
                        catch (FormatException)
                        {
                            reply = reply.Replace(tag, "[ERR: Can't \"" + tags[i] + "\" non-numeric value " + value + "]");
                            continue;
                        }

                        // Run the operation.
                        if (tags[i].Equals("add"))
                        {
                            current += modifier;
                        }
                        else if (tags[i].Equals("sub"))
                        {
                            current -= modifier;
                        }
                        else if (tags[i].Equals("mult"))
                        {
                            current *= modifier;
                        }
                        else
                        {
                            // Don't divide by zero.
                            if (modifier == 0)
                            {
                                reply = reply.Replace(tag, "[ERR: Can't divide by zero!]");
                                continue;
                            }
                            current /= modifier;
                        }

                        // Store the new value.
                        profile.set(var, current.ToString());
                        reply = reply.Replace(tag, "");
                    }
                }
            }

            // <get> tag
            if (reply.IndexOf("<get") > -1)
            {
                Regex reGet = new Regex("<get (.+?)>");
                foreach (Match mGet in reGet.Matches(reply))
                {
                    string tag = mGet.Groups[0].Value;
                    string var = mGet.Groups[1].Value;

                    // Get the user var.
                    reply = reply.Replace(tag, profile.get(var));
                }
            }

            // {topic} tag
            if (reply.IndexOf("{topic=") > -1)
            {
                Regex reTopic = new Regex("\\{topic=(.+?)\\}");
                foreach (Match mTopic in reTopic.Matches(reply))
                {
                    string tag = mTopic.Groups[0].Value;
                    string topic = mTopic.Groups[1].Value;
                    say("Set user's topic to: " + topic);
                    profile.set("topic", topic);
                    reply = reply.Replace(tag, "");
                }
            }

            // {@redirect} tag
            if (reply.IndexOf("{@") > -1)
            {
                Regex reRed = new Regex("\\{@(.+?)\\}");
                foreach (Match mRed in reRed.Matches(reply))
                {
                    string tag = mRed.Groups[0].Value;
                    string target = mRed.Groups[1].Value.Trim();

                    // Do the reply redirect.
                    string subreply = this.reply(user, target, false, step + 1);
                    reply = reply.Replace(tag, subreply);
                }
            }

            // <call> tag
            if (reply.IndexOf("<call>") > -1)
            {
                Regex reCall = new Regex("<call>(.+?)<\\/call>");
                foreach (Match mCall in reCall.Matches(reply))
                {
                    string tag = mCall.Groups[0].Value;
                    string data = mCall.Groups[1].Value;
                    string[] parts = data.Split(" ");
                    string name = parts[0];
                    List<String> args = new List<String>();
                    for (int i = 1; i < parts.Length; i++)
                    {
                        args.Add(parts[i]);
                    }

                    // See if we know of this object.
                    if (objects.ContainsKey(name))
                    {
                        // What language handles it?
                        string lang = objects[name];
                        string result = handlers[lang].onCall(name, this, args.ToArray());
                        reply = reply.Replace(tag, result);
                    }
                    else
                    {
                        reply = reply.Replace(tag, "[ERR: Object Not Found]");
                    }
                }
            }

            return reply;
        }