/*
 * Copyright (C) 2010 The Project Lombok Authors.
 * 
 * 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 lombok.ast.grammar;

import lombok.ast.Node;

import org.parboiled.BaseParser;
import org.parboiled.Rule;
import org.parboiled.annotations.SuppressSubnodes;

public class TypesParser extends BaseParser<Node> {
	final ParserGroup group;
	final TypesActions actions;
	
	public TypesParser(ParserGroup group) {
		actions = new TypesActions(group.getSource());
		this.group = group;
	}
	
	public Rule nonArrayType() {
		return FirstOf(primitiveType(), referenceType());
	}
	
	/**
	 * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#4.2">JLS section 4.2</a>
	 */
	public Rule type() {
		return Sequence(
				nonArrayType(),
				set(),
				ZeroOrMore(Sequence(
						Ch('['), group.basics.optWS(), Ch(']'), group.basics.optWS())),
				set(actions.setArrayDimensionsOfType(value(), texts("ZeroOrMore/Sequence")))).label("type");
	}
	
	/**
	 * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#4.2">JLS section 4.2</a>
	 */
	public Rule primitiveType() {
		return Sequence(
				rawPrimitiveType(),
				set(actions.createPrimitiveType(lastText())),
				group.basics.optWS());
	}
	
	@SuppressSubnodes
	Rule rawPrimitiveType() {
		return Sequence(
				FirstOf("boolean", "int", "long", "double", "float", "short", "char", "byte", "void"),
				group.basics.testLexBreak());
	}
	
	/**
	 * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#4.3">JLS section 4.3</a>
	 */
	public Rule referenceType() {
		return Sequence(
				referenceTypePart().label("head"),
				ZeroOrMore(dotReferenceTypePart().label("tail")),
				set(actions.createReferenceType(value("head"), values("ZeroOrMore/tail"))));
	}
	
	Rule dotReferenceTypePart() {
		return Sequence(
				Ch('.'), group.basics.optWS(),
				group.basics.identifier().label("partName"),
				Optional(typeArguments()),
				set(actions.createTypeReferencePart(node("partName"), value("Optional/typeArguments"))),
				group.basics.optWS());
	}
	
	Rule referenceTypePart() {
		return Sequence(
				group.basics.identifier().label("partName"),
				Optional(typeArguments()),
				set(actions.createTypeReferencePart(node("partName"), value("Optional/typeArguments"))),
				group.basics.optWS());
	}
	
	public Rule plainReferenceType() {
		return Sequence(
				plainReferenceTypePart().label("head"),
				ZeroOrMore(dotPlainReferenceTypePart().label("tail")),
				set(actions.createReferenceType(value("head"), values("ZeroOrMore/tail"))));
	}
	
	Rule plainReferenceTypePart() {
		return Sequence(
				group.basics.identifier().label("partName"),
				set(actions.createTypeReferencePart(node("partName"), null)),
				group.basics.optWS());
	}
	
	Rule dotPlainReferenceTypePart() {
		return Sequence(
				Ch('.'), group.basics.optWS(),
				group.basics.identifier().label("partName"),
				set(actions.createTypeReferencePart(node("partName"), null)),
				group.basics.optWS());
	}
	
	public Rule typeVariables() {
		return Optional(Sequence(
				Ch('<'),
				group.basics.optWS(),
				Optional(Sequence(
						typeVariable().label("head"),
						ZeroOrMore(Sequence(
								Ch(','),
								group.basics.optWS(),
								typeVariable().label("tail"))))),
				Ch('>'),
				set(actions.createTypeVariables(value("Optional/Sequence/head"), values("Optional/Sequence/ZeroOrMore/Sequence/tail"))),
				group.basics.optWS()));
	}
	
	Rule typeVariable() {
		return Sequence(
				group.basics.identifier(),
				Optional(Sequence(
						Sequence(
							String("extends"),
							group.basics.testLexBreak(),
							group.basics.optWS()),
						type(),
						ZeroOrMore(Sequence(
								Ch('&'), group.basics.optWS(),
								type())))),
				set(actions.createTypeVariable(value("identifier"), value("Optional/Sequence/type"), values("Optional/Sequence/ZeroOrMore/Sequence/type"))));
	}
	
	/**
	 * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#4.5">JLS section 4.5</a>
	 */
	public Rule typeArguments() {
		return Optional(Sequence(
				Ch('<'),
				group.basics.optWS(),
				Optional(Sequence(
						typeArgument().label("head"),
						ZeroOrMore(Sequence(
								Ch(','),
								group.basics.optWS(),
								typeArgument().label("tail"))))),
				Ch('>'),
				set(actions.createTypeArguments(value("Optional/Sequence/head"), values("Optional/Sequence/ZeroOrMore/Sequence/tail"))),
				group.basics.optWS())).label("typeArguments");
	}
	
	public Rule typeArgument() {
		return FirstOf(
				type(),
				Sequence(
						Ch('?').label("qmark"),
						group.basics.optWS(),
						FirstOf(String("extends"), String("super")).label("boundType"),
						group.basics.testLexBreak(),
						group.basics.optWS(),
						type(),
						set(actions.createWildcardedType(node("qmark"), node("boundType"), text("boundType"), value("type")))),
				Sequence(
						Ch('?').label("qmark"),
						set(actions.createUnboundedWildcardType(node("qmark"))),
						group.basics.optWS()));
	}
}
