/* Function: GetParametersIndex
         * If a plain text string ends in parameters, returns the index of the opening brace character.  Returns -1 otherwise.
         */
        public static int GetParametersIndex(string input)
        {
            if (input == null)
            {
                return(-1);
            }

            input = input.TrimEnd();
            int index = input.Length - 1;

            if (input.Length >= 2 && IsClosingBrace(input[index]))
            {
                // We have to count the braces so it correctly returns "(paren2)" from "text (paren) text2 (paren2)" and not
                // "(paren) text2 (paren2)".  We also want to handle nested braces.

                Collections.SafeStack <char> braces = new Collections.SafeStack <char>();
                braces.Push(input[index]);

                while (index > 0)
                {
                    // The start position LastIndexOfAny() takes is the character at the end of the string to be examined first.
                    // The count is the number of characters to examine, so it's one higher as index 4 goes for 5 characters: indexes 0, 1, 2,
                    // 3, and 4.  The character at the start position is examined, it's not a limit.
                    index = input.LastIndexOfAny(AllBraces, index - 1, index);

                    if (index == -1)
                    {
                        break;
                    }

                    if (IsClosingBrace(input[index]))
                    {
                        braces.Push(input[index]);
                    }
                    else                     // IsOpeningBrace(input[index])
                    {
                        if (BracesMatch(input[index], braces.Peek()))
                        {
                            braces.Pop();

                            if (braces.Count == 0)
                            {
                                break;
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                }

                // We don't want to count the angle brackets in "operator<string>" as parameters since this is the distinguishing part of
                // the name.
                if (index >= 0 && input[index] == '<')
                {
                    int lookbehind = index - 1;

                    while (lookbehind > 0 && input[lookbehind] == ' ')
                    {
                        lookbehind--;
                    }

                    if (lookbehind >= 7 && string.Compare(input, lookbehind - 7, "operator", 0, 8, true) == 0)
                    {
                        return(-1);
                    }
                }

                // We want index to be greater than zero so we don't include cases where the entire title is surrounded
                // by braces.
                if (braces.Count == 0 && index > 0)
                {
                    return(index);
                }
            }

            return(-1);
        }
        /* Function: FromPlainText
         * Creates a ParameterString from plain text such as "(int, int)".  You can extract them from a plain text identifier with
         * <GetParametersIndex()>.
         */
        public static ParameterString FromPlainText(string input)
        {
            if (input == null)
            {
                throw new NullReferenceException();
            }

            input = input.Trim();

                        #if DEBUG
            if ((input.Length < 2 || IsOpeningBrace(input[0]) == false || IsClosingBrace(input[input.Length - 1]) == false) &&
                GetParametersIndex(input) != -1)
            {
                throw new Exception("Passed a full identifier to ParameterString.FromPlainText().  Separate the parameters from the identifier first.");
            }
                        #endif

            if (input.Length < 2 || IsOpeningBrace(input[0]) == false || IsClosingBrace(input[input.Length - 1]) == false)
            {
                throw new FormatException();
            }

            input = input.Substring(1, input.Length - 2);              // Strip surrounding braces.
            input = input.Trim();

            if (input == "")
            {
                return(new ParameterString());
            }

            System.Text.StringBuilder output = new System.Text.StringBuilder(input.Length);

            // Ignore separators appearing within braces.  We've already filtered out the surrounding braces and we shouldn't have to
            // worry about quotes because we should only have types, not default values.
            Collections.SafeStack <char> braces = new Collections.SafeStack <char>();

            int startParam = 0;
            int index      = input.IndexOfAny(AllBracesAndParamSeparators);

            while (index != -1)
            {
                char character = input[index];

                if (IsOpeningBrace(character))
                {
                    braces.Push(character);
                }
                else if (BracesMatch(braces.Peek(), character))
                {
                    braces.Pop();
                }
                else if ((character == ',' || character == ';') && braces.Count == 0)
                {
                    NormalizeAndAppend(input.Substring(startParam, index - startParam), output);
                    output.Append(SeparatorChar);
                    startParam = index + 1;
                }

                index = input.IndexOfAny(AllBracesAndParamSeparators, index + 1);
            }

            NormalizeAndAppend(input.Substring(startParam), output);

            return(new ParameterString(output.ToString()));
        }