/*
 * Copyright (c) 2012 International Digital Publishing Forum
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
 *  this software and associated documentation files (the "Software"), to deal in
 *  the Software without restriction, including without limitation the rights to
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 *  the Software, and to permit persons to whom the Software is furnished to do so,
 *  subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package org.idpf.epubcheck.util.css;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.idpf.epubcheck.util.css.CssExceptions.CssErrorCode.GRAMMAR_UNEXPECTED_TOKEN;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_ATTRIBUTE_SELECTOR_MATCHERS;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_CLOSEPAREN;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_CLOSESQUAREBRACKET;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_COLON;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_COMBINATOR_CHAR;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_COMMA;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_OPENBRACE;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_OPENPAREN;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_OPENSQUAREBRACKET;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_PIPE;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_STAR;
import static org.idpf.epubcheck.util.css.CssToken.Matchers.MATCH_STAR_PIPE;
import static org.idpf.epubcheck.util.css.CssTokenList.Filters.FILTER_NONE;

import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.idpf.epubcheck.util.css.CssExceptions.CssErrorCode;
import org.idpf.epubcheck.util.css.CssExceptions.CssException;
import org.idpf.epubcheck.util.css.CssExceptions.CssGrammarException;
import org.idpf.epubcheck.util.css.CssParser.ContextRestrictions;
import org.idpf.epubcheck.util.css.CssTokenList.CssTokenIterator;

import com.adobe.epubcheck.util.Messages;
import com.google.common.base.Ascii;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

/**
 * CSS grammar components.
 *
 * @author mgylling
 */
public class CssGrammar
{

  /**
   * Abstract base for all CssConstructs.
   */
  public static abstract class CssConstruct
  {
    final CssLocation location;
    final Type type;

    public CssConstruct(final Type type, final CssLocation location)
    {
      this.location = checkNotNull(location);
      this.type = checkNotNull(type);
    }

    public final CssLocation getLocation()
    {
      return location;
    }

    public final Type getType()
    {
      return type;
    }

    public abstract String toCssString();

    public enum Type
    {
      //atomics
      STRING,
      KEYWORD,
      COMBINATOR,         //space, plus, gt, tilde
      ATTRIBUTE_MATCH,       // "~=", "|=", "^=","$=" "*="
      HASHNAME,          //#ident
      CLASSNAME,          //.ident
      QUANTITY,
      URANGE,
      URI,
      SYMBOL,            //a single char, eg operators
      TYPE_SELECTOR,        //ns|E, *|E, |E, E, *
      PSEUDO,            //element and class

      //composed
      ATRULE,
      FUNCTION,
      DECLARATION,
      SELECTOR,
      SIMPLE_SELECTOR_SEQ,
      ATTRIBUTE_SELECTOR,      //[...]
      SCOPEDGROUP,        //(...) and [...], the latter when not an attr selector segment


    }
  }

  /*********************************************
   *
   *    atomics
   *
   ********************************************/

  /**
   * A CssConstruct that is composed by a single token.
   */
  static abstract class CssAtomicConstruct extends CssConstruct
  {
    final String value;

    public CssAtomicConstruct(final Type type, final String value, final CssLocation location)
    {
      super(type, location);
      this.value = checkNotNull(value);
    }

    @Override
    public String toString()
    {
      return MoreObjects.toStringHelper(this).addValue(value).toString();
    }

    @Override
    public String toCssString()
    {
      return value;
    }

  }

  /**
   * A CSS string. The returned value does not include start and end quotes.
   */
  public final static class CssString extends CssAtomicConstruct
  {
    public CssString(final String value, final CssLocation location)
    {
      super(Type.STRING, value, location);
    }

    @Override
    public final String toCssString()
    {
      return "'" + value + "'";
    }
  }

  /**
   * A CSS keyword.
   */
  public final static class CssKeyword extends CssAtomicConstruct
  {
    public CssKeyword(final String value, final CssLocation location)
    {
      super(Type.KEYWORD, value, location);
    }
  }

  /**
   * A CSS hash name
   */
  public final static class CssHashName extends CssAtomicConstruct
  {
    public CssHashName(final String value, final CssLocation location)
    {
      super(Type.HASHNAME, value, location);
    }
  }

  /**
   * A CSS class name
   */
  public final static class CssClassName extends CssAtomicConstruct
  {
    public CssClassName(final String value, final CssLocation location)
    {
      super(Type.CLASSNAME, value, location);
    }
  }

  /**
   * A CSS unicode range
   */
  public final static class CssUnicodeRange extends CssAtomicConstruct
  {
    public CssUnicodeRange(final String value, final CssLocation location)
    {
      super(Type.URANGE, value, location);
    }
  }

  /**
   * A CSS URI.
   */
  public final static class CssURI extends CssAtomicConstruct
  {
    public CssURI(final String value, final CssLocation location)
    {
      super(Type.URI, value, location);
    }

    /**
     * The URI string itself (leading/trailing whitespace+quotes stripped, url function removed)
     */
    public String toUriString()
    {
      StringBuilder builder = new StringBuilder();
      boolean inStart = false;

      for (int i = 0; i < value.length(); i++)
      {
        if (i > 3 && i < value.length() - 1)
        {
          char ch = value.charAt(i);
          if (CssScanner.QUOTES.matches(ch)
              || CssScanner.WHITESPACE.matches(ch))
          {
            if (inStart)
            {
              builder.append(ch);
            }
          }
          else
          {
            inStart = true;
            builder.append(ch);
          }
        }
      }

      while (true)
      {
        if (builder.length() == 0)
        {
          break;
        }
        int index = builder.length() - 1;
        char ch = builder.charAt(index);
        if (CssScanner.QUOTES.matches(ch)
            || CssScanner.WHITESPACE.matches(ch))
        {
          builder.deleteCharAt(index);
        }
        else
        {
          break;
        }
      }

      return builder.toString();
    }
  }

  /**
   * A CSS single-character symbol (e.g. operator) that is not
   * in the range of IDENT/KEYWORD and not '{', '}' or ';'. The value
   * is interned.
   */
  public final static class CssSymbol extends CssAtomicConstruct
  {
    public CssSymbol(final String value, final CssLocation location)
    {
      super(Type.SYMBOL, value.intern(), location);
      checkArgument(value.length() == 1);
    }
  }

  /**
   * A CSS selector combinator (space, plus, greater or tilde). The value is interned.
   */
  public final static class CssSelectorCombinator extends CssAtomicConstruct
  {
    final CssSelectorCombinator.Type subType;

    public CssSelectorCombinator(final char value, final CssLocation location)
    {
      super(CssConstruct.Type.COMBINATOR, String.valueOf(value).intern(), location);

      switch (value)
      {
        case ' ':
          this.subType = Type.DESCENDANT;
          break;
        case '>':
          this.subType = Type.CHILD;
          break;
        case '+':
          this.subType = Type.ADJACENT_SIBLING;
          break;
        case '~':
          this.subType = Type.GENERAL_SIBLING;
          break;
        default:
          throw new IllegalStateException();
      }
    }

    public final CssSelectorCombinator.Type getCombinatorType()
    {
      return subType;
    }

    public enum Type
    {
      DESCENDANT,     //S
      CHILD,        //>
      ADJACENT_SIBLING,  //+
      GENERAL_SIBLING  //~
    }
  }

  /**
   * An attribute match selector ('=', '~=', '|=', '^=', '$=', '*=')
   */
  public final static class CssAttributeMatchSelector extends CssAtomicConstruct
  {
    final CssAttributeMatchSelector.Type subType;

    public CssAttributeMatchSelector(final String value,
        final CssAttributeMatchSelector.Type type, final CssLocation location)
    {
      super(CssConstruct.Type.ATTRIBUTE_MATCH, value, location);
      this.subType = type;
    }

    public final CssAttributeMatchSelector.Type getAttributeMatchType()
    {
      return subType;
    }

    public enum Type
    {
      EQUALS,       //"="
      INCLUDES,       //"~="
      DASHMATCH,       //"|="
      PREFIXMATCH,     //"^="
      SUFFIXMATCH,     //"$="
      SUBSTRINGMATCH   //"*="
    }

    @Override
    public String toString()
    {
      return MoreObjects.toStringHelper(this).addValue(subType.name()).addValue(value).toString();
    }
  }

  /**
   * A type or universal ('*') selector, possibly with namespace bindings.
   */
  public static class CssTypeSelector extends CssAtomicConstruct
  {
    public CssTypeSelector(final String value, final CssLocation location)
    {
      super(Type.TYPE_SELECTOR, value, location);
    }
  }

  /**
   * A CSS quantity.
   */
  public final static class CssQuantity extends CssAtomicConstruct
  {
    final CssQuantity.Unit subType;

    public CssQuantity(final String value, final CssQuantity.Unit subtype, final CssLocation location)
    {
      super(CssConstruct.Type.QUANTITY, value, location);
      this.subType = checkNotNull(subtype);
    }

    public final CssQuantity.Unit getUnit()
    {
      return subType;
    }

    public enum Unit
    {
      DIMEN,
      PERCENTAGE,
      LENGTH,
      EMS,
      EXS,
      ANGLE,
      TIME,
      FREQ,
      RESOLUTION,
      NUMBER,
      INTEGER,
      REMS
    }

    @Override
    public String toString()
    {
      return MoreObjects.toStringHelper(this).addValue(subType.name()).addValue(value).toString();
    }
  }


  /*********************************************
   *
   *    composed
   *
   ********************************************/

  /**
   * A CssConstruct that is composed a list of atomic and/or composed CssConstructs,
   * and optionally a name.
   */
  static abstract class CssComposedConstruct extends CssConstruct
  {
    final List<CssConstruct> components;
    final Optional<String> name;

    public CssComposedConstruct(final Type type, final String name, final CssLocation location)
    {
      super(type, location);
      this.components = Lists.newArrayList();
      this.name = name != null ? Optional.of(name) : absent;
    }

    public CssComposedConstruct(final Type type, final CssLocation location)
    {
      this(type, null, location);
    }

    /**
     * Get the components. The list may be empty but is never null.
     */
    public List<CssConstruct> getComponents()
    {
      return components;
    }

    public Optional<String> getName()
    {
      return name;
    }

    @Override
    public String toString()
    {
      return MoreObjects.toStringHelper(this)
          .addValue(type)
          .addValue(name.isPresent() ? name.get() : "")
          .addValue(components.isEmpty() ? "" : Joiner.on(" ").join(components))
          .toString();
    }

    final Optional<String> absent = Optional.absent();
  }

  /**
   * An attribute selector ([name] or [name, match, ident/string] )
   */
  public final static class CssAttributeSelector extends CssComposedConstruct
  {

    public CssAttributeSelector(final CssLocation location)
    {
      super(Type.ATTRIBUTE_SELECTOR, location);
    }

    @Override
    public String toCssString()
    {
      StringBuilder sb = new StringBuilder().append('[');
      for (CssConstruct cc : components)
      {
        sb.append(cc.toCssString());
      }
      return sb.append(']').toString();
    }

  }

  /**
   * A CSS function. The function name is interned, and ASCII characters in the name
   * are guaranteed to be lower case.
   */
  public final static class CssFunction extends CssComposedConstruct
  {
    public CssFunction(final String name, final CssLocation location)
    {
      super(Type.FUNCTION, Ascii.toLowerCase(checkNotNull(name)).intern(), location);
    }

    @Override
    public String toCssString()
    {
      StringBuilder sb = new StringBuilder().append(name.get()).append('(');
      for (CssConstruct cc : components)
      {
        sb.append(cc.toCssString());
      }
      return sb.append(')').toString();
    }

  }

  /**
   * A CSS at-rule. The at-rule name is interned, and ASCII characters in the name
   * are guaranteed to be lowercase.
   */
  public final static class CssAtRule extends CssComposedConstruct
  {
    boolean hasBlock;

    public CssAtRule(final String name, final CssLocation location)
    {
      super(Type.ATRULE, checkNotNull(
          Ascii.toLowerCase(name).intern()), location);
    }

    public final boolean hasBlock()
    {
      return hasBlock;
    }

    @Override
    public String toCssString()
    {
      // Note that semi or braceblock is not included in the return
      StringBuilder sb = new StringBuilder().append(name.get()).append(' ');
      for (CssConstruct cc : components)
      {
        sb.append(cc.toCssString()).append(' '); //TODO get smarter re space, joint helper function
      }
      return sb.deleteCharAt(sb.length() - 1).toString();
    }

  }

  /**
   * A CSS declaration. The property name is interned, and ASCII characters in the name
   * are guaranteed to be lowercase.
   */
  public final static class CssDeclaration extends CssComposedConstruct
  {
    boolean important = false;

    public CssDeclaration(final String name, final CssLocation location)
    {
      super(Type.DECLARATION, Ascii.toLowerCase(name).intern(), location);
    }

    /**
     * Get the state of the important flag
     */
    public final boolean getImportant()
    {
      return important;
    }

    @Override
    public String toCssString()
    {
      StringBuilder sb = new StringBuilder().append(name.get()).append(" : ");
      for (CssConstruct cc : components)
      {
        sb.append(cc.toCssString()).append(' '); //TODO get smarter re space, joint helper function
      }
      if (getImportant())
      {
        sb.append("!important ");
      }
      return sb.deleteCharAt(sb.length() - 1).append(" ;").toString();
    }
  }

  /**
   * A scoped group, aka (...) and [...].
   */
  public final static class CssScopedGroup extends CssComposedConstruct
  {
    final CssScopedGroup.Type subType;

    public CssScopedGroup(final CssScopedGroup.Type type, final CssLocation location)
    {
      super(CssConstruct.Type.SCOPEDGROUP, location);
      this.subType = type;
    }

    @Override
    public String toString()
    {
      return MoreObjects.toStringHelper(this)
          .addValue(type)
          .addValue(subType)
          .addValue(Joiner.on(" ").join(components))
          .toString();
    }

    public final CssScopedGroup.Type getGroupType()
    {
      return subType;
    }

    @Override
    public String toCssString()
    {
      StringBuilder sb = new StringBuilder();
      sb.append(subType == Type.PAREN ? '(' : '[');
      for (CssConstruct cc : components)
      {
        sb.append(cc.toCssString()).append(' '); //TODO get smarter re space, joint helper function
      }
      sb.deleteCharAt(sb.length() - 1);
      sb.append(subType == Type.PAREN ? ')' : ']');
      return sb.toString();
    }

    public enum Type
    {
      PAREN,
      BRACKET
    }

  }

  /**
   * A CSS pseudo selector (pseudo-element and pseudo-class).
   */
  public final static class CssPseudoSelector extends CssComposedConstruct
  {
    final CssPseudoSelector.Type subType;
    CssFunction function = null; //may remain null
    String name = null;      //may remain null

    public CssPseudoSelector(final CssPseudoSelector.Type type, final CssLocation location)
    {
      super(CssConstruct.Type.PSEUDO, location);
      this.subType = type;
    }

    /*
       * If this pseudo selector is a functional_pseudo,
       * return the function name, else :(:)ident.
       */
    @Override
    public final Optional<String> getName()
    {
      if (function != null)
      {
        return function.getName();
      }
      return Optional.of(name);
    }

    @Override
    public String toString()
    {
      return MoreObjects.toStringHelper(this)
          .addValue(getSubType().name())
          .addValue(getName().get())
          .addValue(function != null ? Joiner.on(" ").join(function.components) : "")
          .toString();
    }

    /**
     * If this pseudo selector is a functional_pseudo,
     * return it as a function, else return null.
     */
    public final CssFunction getFunction()
    {
      return function;
    }

    public final CssPseudoSelector.Type getSubType()
    {
      return subType;
    }

    @Override
    public String toCssString()
    {
      if (function != null)
      {
        return function.toCssString();
      }
      return name;
    }

    public enum Type
    {
      PSEUDO_ELEMENT,
      PSEUDO_CLASS,
    }

  }

  /**
   * A CSS selector
   */
  public final static class CssSelector extends CssComposedConstruct
  {

    public CssSelector(final CssLocation location)
    {
      super(Type.SELECTOR, location);
    }

    /**
     * Get the list of selector constructs, consisting of at least one
     * CssSimpleSelectorSequence, possibly followed by (CssSelectorCombinator,
     * CssSimpleSelectorSequence)*
     */
    public final List<CssConstruct> getComponents()
    {
      return super.getComponents();
    }

    @Override
    public String toCssString()
    {
      StringBuilder sb = new StringBuilder();
      for (CssConstruct cc : components)
      {
        sb.append(cc.toCssString());
      }
      return sb.toString();
    }

  }

  /**
   * A CSS simple selector sequence
   * <q>A sequence of simple selectors is a chain of simple selectors that are not
   * separated by a combinator. It always begins with a type selector or a universal
   * selector. No other type selector or universal selector is allowed in the sequence.
   * A simple selector is either a type selector, universal selector, attribute selector,
   * class selector, ID selector, or pseudo-class.</q>
   */
  public final static class CssSimpleSelectorSequence extends CssComposedConstruct
  {

    public CssSimpleSelectorSequence(final CssLocation location)
    {
      super(Type.SIMPLE_SELECTOR_SEQ, location);
    }

    @Override
    public String toCssString()
    {
      StringBuilder sb = new StringBuilder();
      for (CssConstruct cc : components)
      {
        sb.append(cc.toCssString());
      }
      return sb.toString();
    }

  }

  /**
   * ******************************************
   * <p/>
   * helpers
   * <p/>
   * ******************************************
   */

  static final class CssSelectorConstructFactory
  {

    private final Locale locale;
    private final Messages messages;
    
    public CssSelectorConstructFactory(Locale locale) {
      this.locale = locale;
      this.messages = Messages.getInstance(locale, CssGrammar.class);
    }

    /**
     * Create a simple selector sequence. If creation fails,
     * errors are issued, and null is returned.
     *
     * @throws CssException
     */
    public CssSimpleSelectorSequence createSimpleSelectorSequence(final CssToken start,
        final CssTokenIterator iter, final CssErrorHandler err) throws
        CssException
    {

      CssSimpleSelectorSequence seq = new CssSimpleSelectorSequence(start.location);
      CssConstruct seqItem = createSimpleSelector(start, iter, err);
      if (seqItem == null)
      {
        //errors already issued
        return null;
      }

      seq.components.add(seqItem);

      CssToken next = iter.peek(FILTER_NONE);
      while (next.type != CssToken.Type.S
          && !MATCH_COMMA.apply(next)
          && !MATCH_OPENBRACE.apply(next)
          && !MATCH_COMBINATOR_CHAR.apply(next))
      {
        seqItem = createSimpleSelector(iter.next(FILTER_NONE), iter, err);
        if (seqItem == null)
        {
          //errors already issued
          return null;
        }
        seq.components.add(seqItem);
        next = iter.peek(FILTER_NONE);
      }
      return seq;
    }

    /**
     * Create one item in a simple selector sequence. If creation fails,
     * errors are issued, and null is returned. On return, the iterator
     * will return the next token after the constructs last token.
     */
    CssConstruct createSimpleSelector(final CssToken start, final CssTokenIterator iter,
        final CssErrorHandler err) throws
        CssException
    {

      // type and universal selector; ns|E, *|E, |E, E
      if (start.type == CssToken.Type.IDENT || MATCH_STAR_PIPE.apply(start))
      {
        //note, we bundle universal selector in CssTypeSelector
        return createTypeSelector(start, iter, err);

        // hashname #{name}
      }
      else if (start.type == CssToken.Type.HASHNAME)
      {
        return new CssHashName(start.getChars(), start.location);

        // classname .{name}
      }
      else if (start.type == CssToken.Type.CLASSNAME)
      {
        return new CssClassName(start.getChars(), start.location);

        //attribute selector [...]
      }
      else if (MATCH_OPENSQUAREBRACKET.apply(start))
      {
        return createAttributeSelector(iter.next(), iter, err);

        //pseudo selector
      }
      else if (MATCH_COLON.apply(start))
      {
        return createPseudoSelector(start, iter, err);

			//keyframes percentage 
			} else if(start.type == CssToken.Type.QNTY_PERCENTAGE) {	
				//note, for now, "from" and "to" keywords become type selectors above, 
				//this handles only the percentage TODO FIX				
				CssSelector sel = new CssSelector(start.location);
				sel.components.add(new CssQuantity(start.chars, CssQuantity.Unit.PERCENTAGE, start.location));
				return sel;
      }
      else
      {
				
        err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, start.location, locale, start.chars));
        return null;
      }

    }

    /**
     * Create a combinator. Note that this method does not support the S combinator.
     * This method also returns null without issuing errors
     */
    CssSelectorCombinator createCombinator(final CssToken start,
        final CssTokenIterator iter, final CssErrorHandler err)
    {
      char symbol;
      if (start.type == CssToken.Type.CHAR)
      {
        char ch = start.getChar();
        if (ch == '>')
        {
          symbol = ch;
        }
        else if (ch == '+')
        {
          symbol = ch;
        }
        else if (ch == '~')
        {
          symbol = ch;
        }
        else
        {
          return null;
        }
      }
      else
      {
        return null;

      }
      return new CssSelectorCombinator(symbol, start.location);
    }

    CssPseudoSelector createPseudoSelector(final CssToken start,
        final CssTokenIterator iter, final CssErrorHandler err) throws
        CssException
    {

      CssPseudoSelector.Type type;
      CssPseudoSelector cps;

      StringBuilder sb = new StringBuilder();
      sb.append(start.getChars());
      CssToken next = iter.next(FILTER_NONE);
      if (MATCH_COLON.apply(next))
      {
        type = CssPseudoSelector.Type.PSEUDO_ELEMENT;
        sb.append(next.getChars());
        next = iter.next(FILTER_NONE);
      }
      else
      {
        type = CssPseudoSelector.Type.PSEUDO_CLASS;
      }
      cps = new CssPseudoSelector(type, start.location);

      if (next.type == CssToken.Type.IDENT)
      {
        sb.append(next.getChars());
        cps.name = sb.toString();

      }
      else if (next.type == CssToken.Type.FUNCTION)
      {

        //need to get the colons into the name so clone and mod the token
        CssToken tk = new CssToken(next.type, next.location,
            sb.toString() + next.chars, next.errors.isPresent() ? next.errors.get() : null);

        //general functional pseudos and negation pseudos have different contentmodels
        //functional: [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
        //pseudo: type_selector | universal | HASH | class | attrib | pseudo

        CssConstruct func;
        if (Ascii.toLowerCase(next.getChars()).startsWith("not"))
        {
          func = createNegationPseudo(tk, iter, err);
        }
        else
        {
          func = createFunctionalPseudo(tk, iter, MATCH_OPENBRACE, err);
        }

        if (func == null)
        {
          err.error(new CssGrammarException(
              CssErrorCode.GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, locale, iter.last.chars,
              next.getChars()));
          return null;
        }
        cps.function = (CssFunction) func;
      }
      return cps;
    }

    CssConstruct createFunctionalPseudo(final CssToken start,
        final CssTokenIterator iter, final Predicate<CssToken> limit,
        final CssErrorHandler err)
    {

      String name = start.getChars().substring(0, start.getChars().length() - 1);
      CssFunction function = new CssFunction(name, start.location);

      CssToken tk = iter.next();
      while (!MATCH_CLOSEPAREN.apply(tk))
      {
        if (limit.apply(tk))
        {
          return null;
        }
        CssConstruct cc = CssConstructFactory.create(tk, iter, limit, ContextRestrictions.PSEUDO_FUNCTIONAL);
        if (cc == null)
        {
          return null;
        }
        else
        {
          function.components.add(cc);
        }
        tk = iter.next();
      }
      return function;
    }

    CssConstruct createNegationPseudo(final CssToken start,
        final CssTokenIterator iter, final CssErrorHandler err) throws
        CssException
    {

      String name = start.getChars().substring(0, start.getChars().length() - 1);

      CssFunction negation = new CssFunction(name, start.location);

      CssToken tk = iter.next();
      CssConstruct cc = createSimpleSelector(tk, iter, err);
      if (cc == null || !ContextRestrictions.PSEUDO_NEGATION.apply(cc))
      {
        return null;
      }
      else
      {
        negation.components.add(cc);
        iter.next();
      }
      return negation;
    }

    CssAttributeSelector createAttributeSelector(final CssToken start,
        final CssTokenIterator iter, final CssErrorHandler err) throws
        CssException
    {

      CssAttributeSelector cas = new CssAttributeSelector(start.location);

      CssTypeSelector cts = createTypeSelector(start, iter, err);
      if (cts == null)
      {
        //factory method has issued errors
        return null;
      }
      cas.components.add(cts);

      CssToken next = iter.next(); // ']' or string matcher
      if (!MATCH_CLOSESQUAREBRACKET.apply(next))
      {
        if (MATCH_ATTRIBUTE_SELECTOR_MATCHERS.apply(next))
        {
          CssAttributeMatchSelector casm = createAttributeMatchSelector(next, iter, err);
          cas.components.add(casm);

          next = iter.next();
          CssConstruct val = CssConstructFactory.create(next, iter, MATCH_CLOSESQUAREBRACKET,
              ContextRestrictions.ATTRIBUTE_SELECTOR_VALUE);
          if (val != null)
          {
            cas.components.add(val);
          }
          else
          {
            err.error(new CssGrammarException(
                CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, locale, next.chars,
                messages.get("a_string_or_dentifier")));
            return null;
          }
          iter.next(); // ']'
        }
        else
        {
          err.error(new CssGrammarException(
              CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, locale, next.chars,
              messages.get("an_attribute_value_matcher")));
          return null;
        }
      }
      return cas;
    }

    CssAttributeMatchSelector createAttributeMatchSelector(final CssToken tk,
        final CssTokenIterator iter, final CssErrorHandler err)
    {
      CssAttributeMatchSelector.Type type;
      switch (tk.type)
      {
        case INCLUDES:
          type = CssAttributeMatchSelector.Type.INCLUDES;
          break;
        case DASHMATCH:
          type = CssAttributeMatchSelector.Type.DASHMATCH;
          break;
        case PREFIXMATCH:
          type = CssAttributeMatchSelector.Type.PREFIXMATCH;
          break;
        case SUFFIXMATCH:
          type = CssAttributeMatchSelector.Type.SUFFIXMATCH;
          break;
        case SUBSTRINGMATCH:
          type = CssAttributeMatchSelector.Type.SUBSTRINGMATCH;
          break;
        default:
          type = CssAttributeMatchSelector.Type.EQUALS;
          break;
      }
      return new CssAttributeMatchSelector(tk.getChars(), type, tk.location);
    }

    CssTypeSelector createTypeSelector(final CssToken start,
        final CssTokenIterator iter, final CssErrorHandler err) throws
        CssException
    {

      if (start.type != CssToken.Type.IDENT && !MATCH_STAR_PIPE.apply(start))
      {
        err.error(new CssGrammarException(
            CssErrorCode.GRAMMAR_EXPECTING_TOKEN, start.location, locale,
            start.getChars(), messages.get("a_type_or_universal_selector")));
        return null;
      }

      StringBuilder sb = new StringBuilder();
      sb.append(start.getChars());

      if (MATCH_PIPE.apply(start))
      {
        //|E
        CssToken next = iter.peek(FILTER_NONE);
        if (next.type != CssToken.Type.IDENT)
        {
          err.error(new CssGrammarException(
              CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, locale,
              next.getChars(), messages.get("a_type_or_universal_selector")));
          return null;
        }
        else
        {
          sb.append(iter.next().getChars());
        }
      }
      else if (MATCH_PIPE.apply(iter.peek(FILTER_NONE)))
      {
        //ns|E, *|E,
        sb.append(iter.next().getChars());
        CssToken next = iter.next(FILTER_NONE);
        if (next.type != CssToken.Type.IDENT && !MATCH_STAR.apply(next))
        {
          err.error(new CssGrammarException(
              CssErrorCode.GRAMMAR_EXPECTING_TOKEN, start.location, locale,
              next.getChars(), messages.get("a_type_or_universal_selector")));
          return null;
        }
        else
        {
          sb.append(next.getChars());
        }
      }
      else if (iter.peek(FILTER_NONE).type == CssToken.Type.IDENT)
      {
        sb.append(iter.next().getChars());
      }

      return new CssTypeSelector(sb.toString(), start.location);
    }
  }

  static final class CssConstructFactory
  {

    static CssConstruct create(final CssToken start, final CssTokenIterator iter,
        final Predicate<CssToken> limit, final Predicate<CssConstruct> permitted)
    {

      CssTokenTransform transform = transformerMappings.get(start.type);
      return transform == null ? null : transform.build(start, iter, limit, permitted);

    }


    static final CssTokenTransform BUILDER_FUNCTION = new CssTokenTransform()
    {
      public CssFunction build(CssToken start, CssTokenIterator iter,
          Predicate<CssToken> limit, Predicate<CssConstruct> permitted)
      {

        String name = start.getChars().substring(0, start.getChars().length() - 1);
        CssFunction function = new CssFunction(name, start.location);

        CssToken tk = iter.next();
        while (!MATCH_CLOSEPAREN.apply(tk))
        {
          if (limit.apply(tk))
          {
            return null;
          }
          CssConstruct cc = CssConstructFactory.create(tk, iter, limit,
              ContextRestrictions.FUNCTION);
          if (cc == null || !permitted.apply(cc))
          {
            return null;
          }
          else
          {
            function.components.add(cc);
          }
          tk = iter.next();
        }
        return function;
      }

    };

    private static final CssTokenTransform BUILDER_ATOMIC = new CssTokenTransform()
    {
      public CssConstruct build(final CssToken start, final CssTokenIterator iter,
          final Predicate<CssToken> limit, final Predicate<CssConstruct> permitted)
      {

        CssConstruct.Type type = genericTypeMappings.get(start.type);
        CssConstruct cc;

        switch (type)
        {
          case KEYWORD:
            cc = new CssKeyword(start.getChars(), start.location);
            break;
          case URI:
            cc = new CssURI(start.getChars(), start.location);
            break;
          case STRING:
            cc = new CssString(start.getChars(), start.location);
            break;
          case URANGE:
            cc = new CssUnicodeRange(start.getChars(), start.location);
            break;
          case HASHNAME:
            cc = new CssHashName(start.getChars(), start.location);
            break;
          case CLASSNAME:
            cc = new CssClassName(start.getChars(), start.location);
            break;
          default:
            throw new IllegalStateException("CssTokenTransform BUILDER_ATOMIC");
        }

        return permitted.apply(cc) ? cc : null;

      }
    };

    private static final CssTokenTransform BUILDER_CHAR = new CssTokenTransform()
    {
      public CssConstruct build(CssToken start, CssTokenIterator iter,
          Predicate<CssToken> limit, Predicate<CssConstruct> permitted)
      {
        CssConstruct ret;

        char chr = start.getChar();
        if (chr == '{' || chr == '}' || chr == ';')
        {
          return null;
        }
        else if (chr == '(' || chr == '[')
        {
          ret = BUILDER_SCOPEDGROUP.build(start, iter, limit, permitted);
        }
        else
        {
          ret = new CssSymbol(start.chars, start.location);
        }

        if (ret == null)
        {
          return null;
        }
        return permitted.apply(ret) ? ret : null;
      }
    };

    /**
     * Builder for general scoped groups aka (...) and [...]
     */
    static final CssTokenTransform BUILDER_SCOPEDGROUP = new CssTokenTransform()
    {
      public CssConstruct build(CssToken start, CssTokenIterator iter,
          Predicate<CssToken> limit, Predicate<CssConstruct> permitted)
      {

        CssScopedGroup.Type type;
        Predicate<CssToken> end;


        if (MATCH_OPENPAREN.apply(start))
        {
          type = CssScopedGroup.Type.PAREN;
          end = MATCH_CLOSEPAREN;
        }
        else if (MATCH_OPENSQUAREBRACKET.apply(start))
        {
          type = CssScopedGroup.Type.BRACKET;
          end = MATCH_CLOSESQUAREBRACKET;
        }
        else
        {
          throw new IllegalStateException();
        }

        CssScopedGroup group = new CssScopedGroup(type, start.location);

        CssToken tk = iter.next();
        while (!end.apply(tk))
        {
          if (limit.apply(tk))
          {
            return null;
          }
          CssConstruct cc = CssConstructFactory.create(tk, iter, limit,
              ContextRestrictions.FUNCTION);
          if (cc == null || !permitted.apply(cc))
          {
            return null;
          }
          else
          {
            group.components.add(cc);
          }
          tk = iter.next();
        }
        return group;
      }

    };

    private static final CssTokenTransform BUILDER_QNTY = new CssTokenTransform()
    {
      public CssConstruct build(final CssToken start, final CssTokenIterator iter,
          final Predicate<CssToken> limit, final Predicate<CssConstruct> permitted)
      {

        CssQuantity cq = new CssQuantity(start.chars, quantityMappings.get(start.type),
            start.location);

        return permitted.apply(cq) ? cq : null;

      }
    };

    private static final Map<CssToken.Type, CssTokenTransform> transformerMappings
        = new ImmutableMap.Builder<CssToken.Type, CssTokenTransform>()
        .put(CssToken.Type.FUNCTION, BUILDER_FUNCTION)
        .put(CssToken.Type.CHAR, BUILDER_CHAR)
        .put(CssToken.Type.IDENT, BUILDER_ATOMIC)
        .put(CssToken.Type.URI, BUILDER_ATOMIC)
        .put(CssToken.Type.STRING, BUILDER_ATOMIC)
        .put(CssToken.Type.AND, BUILDER_ATOMIC)
        .put(CssToken.Type.NOT, BUILDER_ATOMIC)
        .put(CssToken.Type.ONLY, BUILDER_ATOMIC)
        .put(CssToken.Type.URANGE, BUILDER_ATOMIC)
        .put(CssToken.Type.HASHNAME, BUILDER_ATOMIC)
        .put(CssToken.Type.CLASSNAME, BUILDER_ATOMIC)
        .put(CssToken.Type.QNTY_ANGLE, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_DIMEN, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_REMS, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_EMS, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_EXS, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_FREQ, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_LENGTH, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_PERCENTAGE, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_RESOLUTION, BUILDER_QNTY)
        .put(CssToken.Type.QNTY_TIME, BUILDER_QNTY)
        .put(CssToken.Type.NUMBER, BUILDER_QNTY)
        .put(CssToken.Type.INTEGER, BUILDER_QNTY)
        .build();

    /* Map used by BUILDER_GENERIC to get type */
    private static final Map<CssToken.Type, CssConstruct.Type> genericTypeMappings
        = new ImmutableMap.Builder<CssToken.Type, CssConstruct.Type>()
        .put(CssToken.Type.IDENT, CssConstruct.Type.KEYWORD)
        .put(CssToken.Type.URI, CssConstruct.Type.URI)
        .put(CssToken.Type.STRING, CssConstruct.Type.STRING)
        .put(CssToken.Type.AND, CssConstruct.Type.KEYWORD)
        .put(CssToken.Type.NOT, CssConstruct.Type.KEYWORD)
        .put(CssToken.Type.ONLY, CssConstruct.Type.KEYWORD)
        .put(CssToken.Type.URANGE, CssConstruct.Type.URANGE)
        .put(CssToken.Type.HASHNAME, CssConstruct.Type.HASHNAME)
        .put(CssToken.Type.CLASSNAME, CssConstruct.Type.CLASSNAME)
        .build();

    /* Map used by BUILDER_QNTY to get subtype */
    private static final Map<CssToken.Type, CssQuantity.Unit> quantityMappings
        = new ImmutableMap.Builder<CssToken.Type, CssQuantity.Unit>()
        .put(CssToken.Type.QNTY_ANGLE, CssQuantity.Unit.ANGLE)
        .put(CssToken.Type.QNTY_DIMEN, CssQuantity.Unit.DIMEN)
        .put(CssToken.Type.QNTY_REMS, CssQuantity.Unit.REMS)
        .put(CssToken.Type.QNTY_EMS, CssQuantity.Unit.EMS)
        .put(CssToken.Type.QNTY_EXS, CssQuantity.Unit.EXS)
        .put(CssToken.Type.QNTY_FREQ, CssQuantity.Unit.FREQ)
        .put(CssToken.Type.QNTY_LENGTH, CssQuantity.Unit.LENGTH)
        .put(CssToken.Type.QNTY_PERCENTAGE, CssQuantity.Unit.PERCENTAGE)
        .put(CssToken.Type.QNTY_RESOLUTION, CssQuantity.Unit.RESOLUTION)
        .put(CssToken.Type.QNTY_TIME, CssQuantity.Unit.TIME)
        .put(CssToken.Type.NUMBER, CssQuantity.Unit.NUMBER)
        .put(CssToken.Type.INTEGER, CssQuantity.Unit.INTEGER)
        .build();

    interface CssTokenTransform
    {
      CssConstruct build(CssToken start, CssTokenIterator iter, Predicate<CssToken> limit,
          Predicate<CssConstruct> permitted);
    }
  }
}
