context: using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Globalization;
  using System.Linq;
  
  namespace DotnetSpider.HtmlAgilityPack
  {
      #region Imports
  
  	using TokenSpec = Either<TokenKind, Token>;
  
      #endregion
  
      /// <summary>
      /// Semantic parser for CSS selector grammar.
      /// </summary>
      public sealed class Parser
      {
          private readonly Reader<Token> _reader;
          private readonly ISelectorGenerator _generator;
          private readonly bool _expectEoi;
  
          private Parser(Reader<Token> reader, ISelectorGenerator generator)
              : this(reader, generator, true)
          {
          }
  
  
          private Parser(Reader<Token> reader, ISelectorGenerator generator, bool expectEoi)
          {
              Debug.Assert(reader != null);
              Debug.Assert(generator != null);
              _reader = reader;
              _generator = generator;
              _expectEoi = expectEoi;
          }
  
          /// <summary>
          /// Parses a CSS selector group and generates its implementation.
          /// </summary>
          public static TGenerator Parse<TGenerator>(string selectors, TGenerator generator)
                  where TGenerator : ISelectorGenerator
          {
              return Parse(selectors, generator, g => g);
          }
  
          /// <summary>
          /// Parses a CSS selector group and generates its implementation.
          /// </summary>
          public static T Parse<TGenerator, T>(string selectors, TGenerator generator, Func<TGenerator, T> resultor)
              where TGenerator : ISelectorGenerator
          {
              if (selectors == null) throw new ArgumentNullException("selectors");
              if (selectors.Length == 0) throw new ArgumentException(null, "selectors");
  
              return Parse(Tokener.Tokenize(selectors), generator, resultor);
          }
  
          /// <summary>
          /// Parses a tokenized stream representing a CSS selector group and 
          /// generates its implementation.
          /// </summary>
          public static TGenerator Parse<TGenerator>(IEnumerable<Token> tokens, TGenerator generator)
                  where TGenerator : ISelectorGenerator
          {
              return Parse(tokens, generator, g => g);
          }
  
          /// <summary>
          /// Parses a tokenized stream representing a CSS selector group and 
          /// generates its implementation.
          /// </summary>
          public static T Parse<TGenerator, T>(IEnumerable<Token> tokens, TGenerator generator, Func<TGenerator, T> resultor)
              where TGenerator : ISelectorGenerator
          {
              if (tokens == null) throw new ArgumentNullException("tokens");
              if (resultor == null) throw new ArgumentNullException("resultor");
  
              new Parser(new Reader<Token>(tokens.GetEnumerator()), generator).Parse();
              return resultor(generator);
          }
  
          private void Parse()
          {
              _generator.OnInit();
              if (TryRead(ToTokenSpec(Token.Slash())) != null)
              {
                  _generator.AnchorToRoot();
                  TryRead(ToTokenSpec(TokenKind.WhiteSpace));
              }
  
              SelectorGroup();
              _generator.OnClose();
          }
  
          private void SelectorGroup()
          {
              //selectors_group
              //  : selector [ COMMA S* selector ]*
              //  ;
  
              Selector();
              while (TryRead(ToTokenSpec(Token.Comma())) != null)
              {
                  TryRead(ToTokenSpec(TokenKind.WhiteSpace));
                  Selector();
              }
  
              if (_expectEoi)
                  Read(ToTokenSpec(TokenKind.Eoi));
          }
  
          private void Selector()
          {
              _generator.OnSelector();
  
              //selector
              //  : simple_selector_sequence [ combinator simple_selector_sequence ]*
              //  ;
  
              SimpleSelectorSequence();
              while (TryCombinator())
                  SimpleSelectorSequence();
          }
  
          private bool TryCombinator()
          {
              //combinator
              //  /* combinators can be surrounded by whitespace */
              //  : PLUS S* | GREATER S* | TILDE S* | S+
              //  ;
  
              var token = TryRead(ToTokenSpec(TokenKind.Plus), ToTokenSpec(TokenKind.Greater), ToTokenSpec(TokenKind.Tilde), ToTokenSpec(TokenKind.WhiteSpace));
  
              if (token == null)
                  return false;
  
              if (token.Value.Kind == TokenKind.WhiteSpace)
              {
                  _generator.Descendant();
              }
              else
              {
                  switch (token.Value.Kind)
                  {
                      case TokenKind.Tilde: _generator.GeneralSibling(); break;
                      case TokenKind.Greater: _generator.Child(); break;
                      case TokenKind.Plus: _generator.Adjacent(); break;
                  }
  
                  TryRead(ToTokenSpec(TokenKind.WhiteSpace));
              }
  
              return true;
          }
  
          private void SimpleSelectorSequence()
          {
              //simple_selector_sequence
              //  : [ type_selector | universal ]
              //    [ HASH | class | attrib | pseudo | negation ]*
              //  | [ HASH | class | attrib | pseudo | negation ]+
              //  ;
  
              var named = false;
              for (var modifiers = 0; ; modifiers++)
              {
                  var token = TryRead(ToTokenSpec(TokenKind.Hash), ToTokenSpec(Token.Dot()), ToTokenSpec(Token.LeftBracket()), ToTokenSpec(Token.Colon()));
  
                  if (token == null)
                  {
                      if (named || modifiers > 0)
                          break;
                      TypeSelectorOrUniversal();
                      named = true;
                  }
                  else
                  {
                      if (modifiers == 0 && !named)
                          _generator.Universal(NamespacePrefix.None); // implied
  
                      if (token.Value.Kind == TokenKind.Hash)
                      {
                          _generator.Id(token.Value.Text);
                      }
                      else
                      {
                          Unread(token.Value);
                          switch (token.Value.Text[0])
                          {
                              case '.': Class(); break;
                              case '[': Attrib(); break;
                              case ':': Pseudo(); break;
                              default: throw new Exception("Internal error.");
                          }
                      }
                  }
              }
          }
  
          private void Pseudo()
          {
              //pseudo
              //  /* '::' starts a pseudo-element, ':' a pseudo-class */
              //  /* Exceptions: :first-line, :first-letter, :before and :after. */
              //  /* Note that pseudo-elements are restricted to one per selector and */
              //  /* occur only in the last simple_selector_sequence. */
              //  : ':' ':'? [ IDENT | functional_pseudo ]
              //  ;
  
              PseudoClass(); // We do pseudo-class only for now
          }
  
          private void PseudoClass()
          {
              //pseudo
              //  : ':' [ IDENT | functional_pseudo ]
              //  ;
  
              Read(ToTokenSpec(Token.Colon()));
              if (!TryFunctionalPseudo())
              {
                  var clazz = Read(ToTokenSpec(TokenKind.Ident)).Text;
                  switch (clazz)
                  {
                      case "first-child": _generator.FirstChild(); break;
                      case "last-child": _generator.LastChild(); break;
                      case "only-child": _generator.OnlyChild(); break;
                      case "empty": _generator.Empty(); break;
                      case "last": _generator.Last(); break;
                      case "select-parent": _generator.SelectParent(); break;
                      default: CustomSelector(clazz, false); break;
                  }
              }
          }
  
          private void CustomSelector(string name, bool hasArguments)
          {
              DelegateInfo deleg;
              if (!CustomSelectors.TryGetValue(name, out deleg))
                  throw new FormatException(string.Format("Unknown pseudo-selector '{0}'.", name));
              var formalParameters = deleg.ParameterTypes;
              var actualParameters = new object[formalParameters.Count];
  
              for (int i = 0; i < formalParameters.Count; i++)
              {
                  if (i != 0)
                  {
                      Read(ToTokenSpec(Token.Semicolon()));
                      Read(ToTokenSpec(TokenKind.WhiteSpace));
                  }
                  Type type = (Type)formalParameters[i];
                  var typeName = type.Name;
                  if (typeName == TypeCode.String.ToString()) actualParameters[i] = Read(ToTokenSpec(TokenKind.String)).Text;
                  else if (
                      typeName == TypeCode.Byte.ToString() ||
                      typeName == TypeCode.UInt16.ToString() ||
                      typeName == TypeCode.UInt32.ToString() ||
                      typeName == TypeCode.UInt64.ToString() ||
                      typeName == TypeCode.SByte.ToString() ||
                      typeName == TypeCode.Int16.ToString() ||
                      typeName == TypeCode.Int32.ToString() ||
                      typeName == TypeCode.Int64.ToString()
                      ) actualParameters[i] = int.Parse(Read(ToTokenSpec(TokenKind.Integer)).Text, CultureInfo.InvariantCulture);
  
                  else if (type.GetGenericTypeDefinition() == typeof(Selector<>)) actualParameters[i] = ParseSubGenerator().Selector;
                  else throw new ArgumentException(string.Format("Unsupported parameter type for custom selector '{0}'", name));
              }
              var selector = deleg.Method.DynamicInvoke(actualParameters);
              _generator.CustomSelector(selector);
          }
  
          private bool TryFunctionalPseudo()
          {
              //functional_pseudo
              //  : FUNCTION S* expression ')'
              //  ;
  
              var token = TryRead(ToTokenSpec(TokenKind.Function));
              if (token == null)
                  return false;
  
              TryRead(ToTokenSpec(TokenKind.WhiteSpace));
  
              var func = token.Value.Text;
              switch (func)
              {
                  case "eq": Eq(); break;
                  case "nth-child": Nth(); break;
                  case "nth-last-child": NthLast(); break;
                  case "has": Has(); break;
  
gt:                 case "split-after": SplitAfter(); break;