/// <summary>
        ///     Initializes a new instance of the <see cref="CSharpInheritanceList" /> class by parsing a class inheritance list
        ///     from <paramref name="fullSignature" />
        /// </summary>
        /// <param name="fullSignature">The full signature.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="fullSignature" /> is <c>null</c>.</exception>
        /// <exception cref="System.ArgumentException">
        ///     Thrown if <see cref="CSharpInheritanceListValidator.Validate" /> fails to
        ///     successfully parse <paramref name="fullSignature" />.
        /// </exception>
        public CSharpInheritanceList(string fullSignature)
        {
            if (fullSignature == null)
            {
                throw new ArgumentNullException("fullSignature", "Inheritance list signature string cannot be null.");
            }

            _validatedSignature = CSharpInheritanceListValidator.Validate(fullSignature);

            if (!_validatedSignature.Success)
            {
                throw new ArgumentException(_validatedSignature.ErrorDescription, "fullSignature");
            }

            _fullSignature = fullSignature;
        }
        /// <summary>
        ///     Validates/parses the specified CSharp inheritance list given in <paramref name="input" />.
        ///     The parser supports 'where' type constraints on generic parameters.
        ///     The signature given should be the source content immediately after a classes declared name, without the separating
        ///     colon if there is one.
        /// </summary>
        /// <param name="input">The inheritance list signature to parse.</param>
        /// <returns>A parse/validation results object.  <see cref="CSharpInheritanceListValidationResult" /></returns>
        /// <exception cref="ArgumentNullException"><paramref name="input"/> is <c>null</c>.</exception>
        public static CSharpInheritanceListValidationResult Validate(string input)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }

            var result = new CSharpInheritanceListValidationResult();

            var compareValidatedType = new EquateValidatedTypes();

            var inheritedTypes            = new HashSet <CSharpClassNameValidationResult>(compareValidatedType);
            var constrainedTypeParameters = new HashSet <string>();
            var typeConstraints           = new GenericArray <HashSet <CSharpTypeConstraintValidationResult> >();

            result.Success = true;


            States state = States.WaitingForFirstWord;

            States stateBeforeGenericPart = 0;

            string accum = "";
            int    unmatchedGenericBraces = 0;

            for (int index = 0; index < input.Length; index++)
            {
                var c = input[index];

                bool end = index == input.Length - 1;
                accum += c;

                if (end)
                {
                    if (state == States.AccumulatingInheritedType ||
                        state == States.AfterFirstInheritedType ||
                        state == States.AccumulatingFirstWord ||
                        state == States.WaitingForFirstWord ||
                        (state == States.AccumulatingGenericPart &&
                         (stateBeforeGenericPart == States.AccumulatingInheritedType ||
                          stateBeforeGenericPart == States.AccumulatingFirstWord)))
                    {
                        var word = accum.Trim();

                        CSharpClassNameValidationResult init;
                        string err;
                        if (!IsValidInheritedType(word, out err, out init))
                        {
                            result.ErrorDescription = err;
                            result.ErrorIndex       = (index - (accum.Length - 1)) + (init == null ? 0 : init.ErrorIndex);
                            result.Success          = false;
                            return(result);
                        }

                        if (!inheritedTypes.Add(init))
                        {
                            result.ErrorDescription = string.Format("Type '{0}' cannot be inherited more than once.",
                                                                    init.FullSignature);
                            result.ErrorIndex = (index - (accum.Length - 1)) + (init.ErrorIndex);
                            result.Success    = false;
                            return(result);
                        }

                        state = States.EndOfListWithoutWhereClauses;
                        continue;
                    }
                    if (state == States.AccumulatingTypeConstraint ||
                        state == States.AfterConstraintColon ||
                        (state == States.AccumulatingGenericPart &&
                         (stateBeforeGenericPart == States.AccumulatingTypeConstraint)))
                    {
                        var word = accum.Trim();

                        string err;
                        CSharpTypeConstraintValidationResult init;
                        if (!IsValidTypeConstraint(word, out err, out init))
                        {
                            result.ErrorDescription = err;
                            result.ErrorIndex       = (index - (accum.Length - 1));
                            result.Success          = false;
                            return(result);
                        }


                        if (!typeConstraints.Last().Add(init))
                        {
                            result.ErrorDescription =
                                string.Format(
                                    "Type constraint '{0}' cannot be used more than once for generic parameter '{1}'.",
                                    init.ConstraintString, constrainedTypeParameters.Last());
                            result.ErrorIndex = (index - (accum.Length - 1));
                            result.Success    = false;
                            return(result);
                        }


                        state = States.EndOfListWithWhereClauses;

                        continue;
                    }
                }

                if (c == '<')
                {
                    if (state == States.AccumulatingFirstWord || state == States.AccumulatingTypeConstraint ||
                        state == States.AccumulatingInheritedType)
                    {
                        stateBeforeGenericPart = state;
                        state = States.AccumulatingGenericPart;
                        unmatchedGenericBraces++;
                        continue;
                    }

                    if (state == States.AccumulatingGenericPart)
                    {
                        unmatchedGenericBraces++;
                        continue;
                    }

                    result.ErrorDescription = string.Format("Unexpected character '{0}'.", c);
                    result.ErrorIndex       = index;
                    result.Success          = false;
                    return(result);
                }

                if (c == '>')
                {
                    if (state == States.AccumulatingGenericPart)
                    {
                        unmatchedGenericBraces--;

                        if (unmatchedGenericBraces == 0)
                        {
                            state = stateBeforeGenericPart;
                        }
                        continue;
                    }

                    result.ErrorDescription = string.Format("Unexpected character '{0}'.", c);
                    result.ErrorIndex       = index;
                    result.Success          = false;
                    return(result);
                }


                if (c == ',')
                {
                    if ((state == States.AfterWhereKeyword && inheritedTypes.Count > 0) ||
                        state == States.AccumulatingConstraintParam)
                    {
                        result.ErrorDescription = string.Format("Unexpected character '{0}'.", c);
                        result.ErrorIndex       = index;
                        result.Success          = false;
                        return(result);
                    }
                    if (state == States.AccumulatingTypeConstraint)
                    {
                        var word = accum.TrimEnd(',').Trim();

                        string err;
                        CSharpTypeConstraintValidationResult init;
                        if (!IsValidTypeConstraint(word, out err, out init))
                        {
                            result.ErrorDescription = err;
                            result.ErrorIndex       = (index - (accum.Length - 1));
                            result.Success          = false;
                            return(result);
                        }

                        if (!typeConstraints.Last().Add(init))
                        {
                            result.ErrorDescription =
                                string.Format(
                                    "Type constraint '{0}' cannot be used more than once for generic parameter '{1}'.",
                                    init.ConstraintString, constrainedTypeParameters.Last());
                            result.ErrorIndex = (index - (accum.Length - 1));
                            result.Success    = false;
                            return(result);
                        }

                        accum = "";
                        continue;
                    }
                    if (state == States.AccumulatingInheritedType ||
                        state == States.AccumulatingFirstWord ||
                        (state == States.AfterWhereKeyword && inheritedTypes.Count == 0))
                    {
                        var type = accum.TrimEnd(',').Trim();
                        CSharpClassNameValidationResult init;
                        string err;
                        if (!IsValidInheritedType(type, out err, out init))
                        {
                            result.ErrorDescription = err;
                            result.ErrorIndex       = (index - (accum.Length - 1));
                            result.Success          = false;
                            return(result);
                        }

                        if (!inheritedTypes.Add(init))
                        {
                            result.ErrorDescription = string.Format(
                                "Type '{0}' cannot be inherited more than once.", init.FullSignature);
                            result.ErrorIndex = (index - (accum.Length - 1));
                            result.Success    = false;
                            return(result);
                        }

                        accum = "";
                        state = States.AfterFirstInheritedType;
                        continue;
                    }
                }

                if (c == 'w')
                {
                    var ahead = index + 5;

                    if (ahead > input.Length)
                    {
                        goto pastWhereCheck;
                    }

                    var lookAheadAsertion = input.Substring(index, 5) == "where";
                    var lookBehindsertion = index == 0 || char.IsWhiteSpace(input[index - 1]);

                    if (lookAheadAsertion && lookBehindsertion)
                    {
                        if (ahead < input.Length && !char.IsWhiteSpace(input[ahead]) && input[ahead] != ',')
                        {
                            goto pastWhereCheck;
                        }

                        if (state == States.WaitingForFirstWord)
                        {
                            accum  = "";
                            index += 4;
                            state  = States.AfterWhereKeyword;

                            //there is an ambiguous case here because you can inherit a class named where, before a where clause occurs

                            if (index + 1 == input.Length)
                            {
                                inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where", false));
                                state = States.EndOfListWithoutWhereClauses;
                                continue;
                            }
                            bool haveWhitespace = false;

                            for (int i = index + 1; i < input.Length; i++)
                            {
                                var cr = input[i];
                                if (char.IsWhiteSpace(cr))
                                {
                                    haveWhitespace = true;
                                    if (i == input.Length - 1)
                                    {
                                        inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where",
                                                                                                           false));
                                        state = States.EndOfListWithoutWhereClauses;
                                        break;
                                    }
                                    continue;
                                }

                                if (cr == 'w' && haveWhitespace)
                                {
                                    ahead = i + 5;
                                    if (ahead > input.Length)
                                    {
                                        continue;
                                    }
                                    lookAheadAsertion = input.Substring(i, 5) == "where";
                                    if (lookAheadAsertion)
                                    {
                                        if (ahead < input.Length && !char.IsWhiteSpace(input[ahead]) &&
                                            input[ahead] != ',')
                                        {
                                            continue;
                                        }

                                        inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where",
                                                                                                           false));
                                        index = i + 4;
                                        state = States.AfterWhereKeyword;
                                        break;
                                    }
                                }

                                if (cr == ',')
                                {
                                    inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where", false));
                                    index = i;
                                    state = States.AccumulatingInheritedType;
                                }
                                break;
                            }
                            continue;
                        }
                        if (state == States.AccumulatingTypeConstraint)
                        {
                            var word = accum.TrimEnd('w').Trim();

                            string err;
                            CSharpTypeConstraintValidationResult init;
                            if (!IsValidTypeConstraint(word, out err, out init))
                            {
                                result.ErrorDescription = err;
                                result.ErrorIndex       = (index - (accum.Length - 1));
                                result.Success          = false;
                                return(result);
                            }

                            if (!typeConstraints.Last().Add(init))
                            {
                                result.ErrorDescription =
                                    string.Format(
                                        "Type constraint '{0}' cannot be used more than once for generic parameter '{1}'.",
                                        init.ConstraintString, constrainedTypeParameters.Last());
                                result.ErrorIndex = (index - (accum.Length - 1));
                                result.Success    = false;
                                return(result);
                            }

                            accum  = "";
                            index += 4;
                            state  = States.AfterWhereKeyword;
                            continue;
                        }
                        if (state == States.AccumulatingInheritedType || state == States.AccumulatingFirstWord)
                        {
                            var word = accum.TrimEnd('w').Trim();

                            CSharpClassNameValidationResult init;
                            string err;
                            if (!IsValidInheritedType(word, out err, out init))
                            {
                                result.ErrorDescription = err;
                                result.ErrorIndex       = (index - (accum.Length - 1)) + (init == null ? 0 : init.ErrorIndex);
                                result.Success          = false;
                                return(result);
                            }

                            if (!inheritedTypes.Add(init))
                            {
                                result.ErrorDescription =
                                    string.Format("Type '{0}' cannot be inherited more than once.",
                                                  init.FullSignature);
                                result.ErrorIndex = (index - (accum.Length - 1)) + (init.ErrorIndex);
                                result.Success    = false;
                                return(result);
                            }

                            state  = States.AfterWhereKeyword;
                            accum  = "";
                            index += 4;
                            continue;
                        }
                    }
                }

pastWhereCheck:


                if (c == ':')
                {
                    if (state == States.AfterWhereKeyword)
                    {
                        result.ErrorDescription = string.Format("Unexpected character '{0}'.", c);
                        result.ErrorIndex       = index;
                        result.Success          = false;
                        return(result);
                    }
                    if (state == States.AccumulatingConstraintParam)
                    {
                        var constrainedType = accum.TrimEnd(':').Trim();

                        if (!CSharpIDValidator.IsValidIdentifier(constrainedType))
                        {
                            result.ErrorDescription = string.Format("Invalid generic type constraint name '{0}'.",
                                                                    constrainedType);
                            result.ErrorIndex = (index - (accum.Length - 1));
                            result.Success    = false;
                            return(result);
                        }

                        if (!constrainedTypeParameters.Add(constrainedType))
                        {
                            result.ErrorDescription =
                                string.Format(
                                    "Generic parameter '{0}' cannot have more than one type constraint list.",
                                    constrainedType);
                            result.ErrorIndex = (index - (accum.Length - 1));
                            result.Success    = false;
                            return(result);
                        }

                        typeConstraints.Add(new HashSet <CSharpTypeConstraintValidationResult>());

                        accum = "";
                        state = States.AfterConstraintColon;
                        continue;
                    }
                }

                if (!char.IsWhiteSpace(c))
                {
                    switch (state)
                    {
                    case States.AfterWhereKeyword:
                        accum = "" + c;
                        state = States.AccumulatingConstraintParam;
                        continue;

                    case States.WaitingForFirstWord:
                        accum = "" + c;
                        state = States.AccumulatingFirstWord;
                        continue;

                    case States.AfterFirstInheritedType:
                        accum = "" + c;
                        state = States.AccumulatingInheritedType;
                        continue;

                    case States.AfterConstraintColon:
                        accum = "" + c;
                        state = States.AccumulatingTypeConstraint;
                        continue;
                    }
                }

                if (!char.IsWhiteSpace(c))
                {
                    continue;
                }

                switch (state)
                {
                case States.AfterConstraintColon:
                    accum = "";
                    continue;

                case States.WaitingForFirstWord:
                    accum = "";
                    continue;

                case States.AfterFirstInheritedType:
                    accum = "";
                    continue;

                case States.AfterWhereKeyword:
                    accum = "";
                    break;
                }
            }

            if (state != States.EndOfListWithoutWhereClauses &&
                state != States.EndOfListWithWhereClauses &&
                state != States.WaitingForFirstWord)
            {
                result.Success          = false;
                result.ErrorDescription = "Class inheritance list is incomplete.";
                result.ErrorIndex       = input.Length;
                return(result);
            }

            result.ConstrainedTypeParameters = constrainedTypeParameters.ToGenericArray();
            result.InheritedTypes            = inheritedTypes.ToGenericArray();
            result.ParameterConstraints      = typeConstraints.Select(x => x.ToGenericArray()).ToGenericArray();

            result.Fullsignature = "";
            if (result.InheritedTypes.Count > 0)
            {
                result.Fullsignature += string.Join(", ", result.InheritedTypes.Select(x => x.FullSignature));
                if (result.ConstrainedTypeParameters.Count > 0)
                {
                    result.Fullsignature += " ";
                }
            }


            result.Fullsignature += string.Join(" ", result.ConstrainedTypeParameters.Select(
                                                    (x, i) =>
                                                    "where " + result.ConstrainedTypeParameters[i] + " : " +
                                                    string.Join(", ", result.ParameterConstraints[i].Select(c => c.ConstraintString))));


            return(result);
        }