private static void AddEmptyMoments(List <Moment> retVal, int time)
 {
     for (int emptyTime = retVal.Last().Time + 1; emptyTime < time; emptyTime++)
     {
         retVal.Add(Moment.Empty(emptyTime));
     }
 }
        private static IEnumerable <Moment> ParseSequence(string sequence, out int?timeOffset)
        {
            var retVal    = new List <Moment>();
            var time      = 0;
            var groupTime = 0;

            timeOffset = null;
            List <string> groupMarbles          = null;
            List <string> unorderedGroupMarbles = null;
            var           parsingState          = ParsingState.NotInGroup;
            var           marbleTime            = 0;
            var           marbleBuilder         = new StringBuilder();

            foreach (var character in sequence.Trim())
            {
                if (parsingState == ParsingState.NotInGroup)
                {
                    switch (character)
                    {
                    case ' ':
                    case '-':
                        FlushMarble(marbleBuilder, retVal, marbleTime, time);
                        retVal.Add(Moment.Empty(time));
                        break;

                    case '^':
                        FlushMarble(marbleBuilder, retVal, marbleTime, time);
                        if (timeOffset.HasValue)
                        {
                            throw new ArgumentException("Only one ^ character allowed", nameof(sequence));
                        }
                        timeOffset = -time;
                        retVal.Add(Moment.Single(time, character.ToString()));
                        break;

                    case '(':
                        FlushMarble(marbleBuilder, retVal, marbleTime, time);
                        groupTime    = time;
                        parsingState = ParsingState.InOrderedGroup;
                        groupMarbles = new List <string>();
                        break;

                    case '<':
                        FlushMarble(marbleBuilder, retVal, marbleTime, time);
                        groupTime             = time;
                        parsingState          = ParsingState.InUnorderedGroup;
                        unorderedGroupMarbles = new List <string>();
                        break;

                    case ')':
                        throw new ArgumentException("Closing parentheses without opening parentheses",
                                                    nameof(sequence));

                    case '>':
                        throw new ArgumentException("Closing brace without opening brace",
                                                    nameof(sequence));

                    case ',':
                        throw new ArgumentException("Comma should not occur outside of a group",
                                                    nameof(sequence));

                    default:
                        GrowMarble(marbleBuilder, ref marbleTime, time, character);
                        break;
                    }
                }
                else if (parsingState == ParsingState.InOrderedGroup)
                {
                    switch (character)
                    {
                    case ',':
                    case ' ':
                        FlushMarbleToGroup(marbleBuilder, groupMarbles);
                        break;

                    case '-':
                        throw new ArgumentException("Cannot use - within a group", nameof(sequence));

                    case '^':
                        if (timeOffset.HasValue)
                        {
                            throw new ArgumentException("Only one ^ character allowed", nameof(sequence));
                        }
                        FlushMarbleToGroup(marbleBuilder, groupMarbles);
                        timeOffset = -groupTime;
                        groupMarbles.Add(character.ToString());
                        break;

                    case '(':
                        throw new ArgumentException("Cannot have nested parentheses", nameof(sequence));

                    case ')':
                        FlushMarbleToGroup(marbleBuilder, groupMarbles);
                        if (groupMarbles.Count <= 1)
                        {
                            throw new ArgumentException("Only groups with multiple marbles are allowed", nameof(sequence));
                        }
                        retVal.Add(Moment.OrderedGroup(groupTime, groupMarbles.ToArray()));
                        groupMarbles = null;
                        parsingState = ParsingState.NotInGroup;
                        AddEmptyMoments(retVal, time + 1);
                        break;

                    default:
                        GrowMarble(marbleBuilder, ref marbleTime, groupTime, character);
                        break;
                    }
                }
                else if (parsingState == ParsingState.InUnorderedGroup)
                {
                    switch (character)
                    {
                    case ',':
                    case ' ':
                        FlushMarbleToGroup(marbleBuilder, unorderedGroupMarbles);
                        break;

                    case '-':
                        throw new ArgumentException("Cannot use - within a group", nameof(sequence));

                    case '^':
                        if (timeOffset.HasValue)
                        {
                            throw new ArgumentException("Only one ^ character allowed", nameof(sequence));
                        }
                        FlushMarbleToGroup(marbleBuilder, unorderedGroupMarbles);
                        timeOffset = -groupTime;
                        unorderedGroupMarbles.Add(character.ToString());
                        break;

                    case '>':
                        FlushMarbleToGroup(marbleBuilder, unorderedGroupMarbles);
                        if (unorderedGroupMarbles.Count <= 1)
                        {
                            throw new ArgumentException("Only groups with multiple marbles are allowed", nameof(sequence));
                        }
                        retVal.Add(Moment.UnorderedGroup(groupTime, unorderedGroupMarbles.ToArray()));
                        unorderedGroupMarbles = null;
                        parsingState          = ParsingState.NotInGroup;
                        AddEmptyMoments(retVal, time + 1);
                        break;

                    case '<':
                        throw new ArgumentException("Cannot have braces nested in an unordered group", nameof(sequence));

                    case '(':
                        throw new ArgumentException("Cannot have parentheses nested in an unordered group", nameof(sequence));

                    case ')':
                        throw new ArgumentException("Cannot have parentheses nested in an unordered group", nameof(sequence));

                    default:
                        GrowMarble(marbleBuilder, ref marbleTime, groupTime, character);
                        break;
                    }
                }
                time++;
            }
            FlushMarble(marbleBuilder, retVal, marbleTime, time);
            if (parsingState == ParsingState.InOrderedGroup)
            {
                throw new ArgumentException("Opening parentheses without closing parentheses", nameof(sequence));
            }
            if (parsingState == ParsingState.InUnorderedGroup)
            {
                throw new ArgumentException("Opening brace without closing brace", nameof(sequence));
            }
            return(retVal);
        }