/*
 * 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.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.google.common.base.Ascii;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
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 Objects.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) {
							continue;	
						} else {
							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 Objects.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;		
		}
		
		@Override
		public String toString() {			
			return Objects.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 Objects.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
			}
			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 Objects.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 Objects.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();
		}
								
	}
		
	/*********************************************
	 * 
	 *    helpers
	 * 
	 ********************************************/
	
	static final class CssSelectorConstructFactory {
		
		/**
		 * Create a simple selector sequence. If creation fails,
		 * errors are issued, and null is returned.
		 * @throws CssException 
		 */
		public static 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.
		 */
		static 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, 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
		 */
		static 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);
		}
							
		static CssPseudoSelector createPseudoSelector(final CssToken start, 
				final CssTokenIterator iter, final CssErrorHandler err) throws CssException {
			
			CssPseudoSelector.Type type = null;
			CssPseudoSelector cps = null; 
								
			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, iter.last.chars, 
							next.getChars()));
					return null;
				}				
				cps.function = (CssFunction)func;				
			}
			return cps;
		}
		
		static final CssConstruct createFunctionalPseudo(final CssToken start, 
				final CssTokenIterator iter,final Predicate<CssToken> limit, 
				final CssErrorHandler err) throws CssException {
							
				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;			
		}
		
		static final 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 = CssSelectorConstructFactory.createSimpleSelector(tk, iter, err);
				if (cc == null || !ContextRestrictions.PSEUDO_NEGATION.apply(cc)) {
					return null;
				} else {
					negation.components.add(cc);
					iter.next();
				}								
				return negation;			
		}
		
		static 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, next.chars, 
								 Messages.get("a_string_or_dentifier")));
						return null;
					}
					iter.next(); // ']'
				} else {
					err.error(new CssGrammarException(
							CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, next.chars, 
							Messages.get("an_attribute_value_matcher")));
					return null;
				}
			}	
			return cas;
		}
		
		static CssAttributeMatchSelector createAttributeMatchSelector(final CssToken tk, 
				final CssTokenIterator iter, final CssErrorHandler err) throws CssException {
			CssAttributeMatchSelector.Type type = null;
			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);			
		}
		
		private static final 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, 
						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,  
						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,  
						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 final 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 = null;
									
				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 = null;
				
				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_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_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);
		}
	}
}
