/*
 * 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 java.util.List;

import lombok.ast.ArrayAccess;
import lombok.ast.ArrayCreation;
import lombok.ast.ArrayDimension;
import lombok.ast.ArrayInitializer;
import lombok.ast.BinaryExpression;
import lombok.ast.Cast;
import lombok.ast.ClassLiteral;
import lombok.ast.ConstructorInvocation;
import lombok.ast.DanglingNodes;
import lombok.ast.Expression;
import lombok.ast.Identifier;
import lombok.ast.InlineIfExpression;
import lombok.ast.InstanceOf;
import lombok.ast.MethodInvocation;
import lombok.ast.Node;
import lombok.ast.Position;
import lombok.ast.Select;
import lombok.ast.Super;
import lombok.ast.This;
import lombok.ast.TypeReference;
import lombok.ast.TypeReferencePart;
import lombok.ast.UnaryExpression;
import lombok.ast.UnaryOperator;
import lombok.ast.VariableReference;

public class ExpressionsActions extends SourceActions {
	public ExpressionsActions(Source source) {
		super(source);
	}
	
	public Node createLeftAssociativeBinaryExpression(
			org.parboiled.Node<Node> head,
			List<org.parboiled.Node<Node>> operatorsNodes, 
			List<String> operators,
			List<org.parboiled.Node<Node>> tail) {
		
		Node currentLeft = head.getValue();
		
		for (int i = 0; i < operators.size(); i++) {
			currentLeft = new BinaryExpression()
					.rawLeft(currentLeft)
					.rawRight(tail.get(i).getValue()).rawOperator(operators.get(i));
			source.registerStructure(currentLeft, operatorsNodes.get(i));
			positionSpan(currentLeft, head, tail.get(i));
		}
		
		return currentLeft;
	}
	
	public Node createAssignmentExpression(Node lhs, String operator, Node rhs) {
		if (rhs == null && operator == null) return lhs;
		return posify(new BinaryExpression().rawLeft(lhs).rawRight(rhs).rawOperator(operator));
	}
	
	public Node createInlineIfExpression(
			Node head, org.parboiled.Node<Node> operator1Node,
			org.parboiled.Node<Node> operator2Node, Node tail1, Node tail2) {
		
		if (tail1 == null || tail2 == null) return head;
		
		InlineIfExpression result = new InlineIfExpression()
				.rawCondition(head).rawIfTrue(tail1).rawIfFalse(tail2);
		source.registerStructure(result, operator1Node);
		source.registerStructure(result, operator2Node);
		return posify(result);
	}
	
	public Node createUnaryPrefixExpression(Node operand, org.parboiled.Node<Node> opNode, String symbol) {
		if (opNode == null) return operand;
		
		if (!opNode.getChildren().isEmpty() && "cast".equals(opNode.getChildren().get(0).getLabel())) {
			return posify(new Cast().rawOperand(operand).rawTypeReference(opNode.getValue()));
		} else {
			if (symbol != null) symbol = symbol.trim();
			if (!symbol.isEmpty()) {
				UnaryOperator op = UnaryOperator.fromSymbol(symbol, false);
				UnaryExpression expr = new UnaryExpression().rawOperand(operand);
				if (op != null) expr.astOperator(op);
				return posify(expr);
			}
		}
		
		return operand;
	}
	
	public Node createUnaryPrefixExpressions(
			org.parboiled.Node<Node> operand,
			List<org.parboiled.Node<Node>> operators,
			List<String> operatorTexts) {
		
		if (operators == null || operators.isEmpty()) return operand.getValue();
		
		Node current = operand.getValue();
		for (int i = operators.size() - 1; i >= 0; i--) {
			org.parboiled.Node<Node> operator = operators.get(i);
			Node prev = current;
			if (operator == null) continue;
			if (!operator.getChildren().isEmpty() && "cast".equals(operator.getChildren().get(0).getLabel())) {
				current = new Cast().rawOperand(current).rawTypeReference(operator.getValue());
			} else {
				String symbol = operatorTexts.get(i);
				if (symbol == null) continue;
				symbol = symbol.trim();
				if (symbol.isEmpty()) continue;
				
				UnaryOperator op = UnaryOperator.fromSymbol(symbol, false);
				UnaryExpression expr = new UnaryExpression().rawOperand(current);
				if (op != null) expr.astOperator(op);
				current = expr;
			}
			
			if (prev != null && !prev.getPosition().isUnplaced() && prev != current && current != null) {
				positionSpan(current, operator, operand);
			}
		}
		
		return current;
	}
	
	public Node createUnaryPostfixExpression(Node operand, List<org.parboiled.Node<Node>> nodes, List<String> operators) {
		if (operators == null) return operand;
		
		Node current = operand;
		for (int i = 0; i < operators.size(); i++) {
			String op = operators.get(i);
			if (op == null) continue;
			op = op.trim();
			Node prev = current;
			if (op.equals("++")) current = new UnaryExpression().rawOperand(current).astOperator(UnaryOperator.POSTFIX_INCREMENT);
			else if (op.equals("--")) current = new UnaryExpression().rawOperand(current).astOperator(UnaryOperator.POSTFIX_DECREMENT);
			org.parboiled.Node<Node> p = nodes.get(i);
			if (prev != null && !prev.getPosition().isUnplaced() && p != null) {
				current.setPosition(new Position(prev.getPosition().getStart(), p.getEndIndex()));
			}
		}
		return current;
	}
	
	public Node createInstanceOfExpression(Node operand, Node type) {
		if (type == null) return operand;
		return posify(new InstanceOf().rawObjectReference(operand).rawTypeReference(type));
	}
	
	public Node createQualifiedConstructorInvocation(
			Node constructorTypeArgs,
			org.parboiled.Node<Node> identifier, org.parboiled.Node<Node> classTypeArgs,
			Node methodArguments, Node classBody) {
		
		TypeReferencePart classTypeArgs0;
		boolean classTypeArgsCorrect = false;
		Node identifierNode = identifier == null ? null : identifier.getValue();
		if (classTypeArgs != null && classTypeArgs.getValue() instanceof TypeReferencePart) {
			classTypeArgs0 = (TypeReferencePart)classTypeArgs.getValue();
			classTypeArgsCorrect = true;
		} else {
			classTypeArgs0 = new TypeReferencePart();
			if (identifierNode != null) {
				classTypeArgs0.setPosition(identifierNode.getPosition());
			}
		}
		
		TypeReference typeReference = new TypeReference().astParts().addToEnd(
				classTypeArgs0.astIdentifier(createIdentifierIfNeeded(identifierNode, currentPos())));
		if (!classTypeArgsCorrect) {
			if (identifier != null && identifier.getValue() != null) {
				typeReference.setPosition(identifier.getValue().getPosition());
			}
		} else {
			positionSpan(typeReference, identifier, classTypeArgs);
		}
		
		ConstructorInvocation constructorInvocation = new ConstructorInvocation()
				.rawTypeReference(typeReference)
				.rawAnonymousClassBody(classBody);
		
		if (constructorTypeArgs instanceof TemporaryNode.TypeArguments) {
			for (Node arg : ((TemporaryNode.TypeArguments)constructorTypeArgs).arguments) {
				constructorInvocation.rawConstructorTypeArguments().addToEnd(arg);
			}
		}
		
		if (methodArguments instanceof TemporaryNode.MethodArguments) {
			for (Node arg : ((TemporaryNode.MethodArguments)methodArguments).arguments) {
				constructorInvocation.rawArguments().addToEnd(arg);
			}
		}
		
		return posify(constructorInvocation);
	}
	
	public Node createChainOfQualifiedConstructorInvocations(
			org.parboiled.Node<Node> qualifier,
			List<org.parboiled.Node<Node>> constructorInvocations) {
		Node current = qualifier.getValue();
		
		if (constructorInvocations == null) return current;
		
		for (org.parboiled.Node<Node> pNode : constructorInvocations) {
			Node n = pNode.getValue();
			if (n instanceof ConstructorInvocation) {
				current = ((ConstructorInvocation)n).rawQualifier(current);
				positionSpan(current, qualifier, pNode);
			} else DanglingNodes.addDanglingNode(current, n);
		}
		
		return current;
	}
	
	public Node createMethodInvocationOperation(org.parboiled.Node<Node> dot, Node typeArguments, Node name, Node arguments) {
		MethodInvocation mi = new MethodInvocation().astName(createIdentifierIfNeeded(name, currentPos()));
		
		if (typeArguments instanceof TemporaryNode.TypeArguments) {
			for (Node arg : ((TemporaryNode.TypeArguments)typeArguments).arguments) {
				mi.rawMethodTypeArguments().addToEnd(arg);
			}
		} else DanglingNodes.addDanglingNode(mi, typeArguments);
		
		if (arguments instanceof TemporaryNode.MethodArguments) {
			for (Node arg : ((TemporaryNode.MethodArguments)arguments).arguments) {
				mi.rawArguments().addToEnd(arg);
			}
		} else DanglingNodes.addDanglingNode(mi, arguments);
		
		source.registerStructure(mi, dot);
		
		return posify(mi);
	}
	
	public Node createSelectOperation(Node identifier) {
		return posify(new Select().astIdentifier(createIdentifierIfNeeded(identifier, currentPos())));
	}
	
	public Node createArrayAccessOperation(Node indexExpression) {
		return posify(new ArrayAccess().rawIndexExpression(indexExpression));
	}
	
	public Node createLevel1Expression(org.parboiled.Node<Node> operand, List<org.parboiled.Node<Node>> operations) {
		Node current = operand.getValue();
		if (operations == null) return current;
		
		for (org.parboiled.Node<Node> pNode : operations) {
			Node o = pNode.getValue();
			
			if (o instanceof ArrayAccess) {
				current = ((ArrayAccess)o).rawOperand(current);
			} else if (o instanceof MethodInvocation) {
				current = ((MethodInvocation)o).rawOperand(current);
			} else if (o instanceof Select) {
				current = ((Select)o).rawOperand(current);
			} else {
				DanglingNodes.addDanglingNode(current, o);
			}
			
			positionSpan(o, operand, pNode);
		}
		return current;
	}
	
	public Node createPrimary(Node identifier, Node methodArguments) {
		Identifier id = createIdentifierIfNeeded(identifier, currentPos());
		
		if (methodArguments instanceof TemporaryNode.MethodArguments) {
			MethodInvocation invoke = new MethodInvocation().astName(id);
			for (Node arg : ((TemporaryNode.MethodArguments)methodArguments).arguments) {
				invoke.rawArguments().addToEnd(arg);
			}
			return posify(invoke);
		} else {
			VariableReference ref = new VariableReference().astIdentifier(id);
			DanglingNodes.addDanglingNode(ref, methodArguments);
			return posify(ref);
		}
	}
	
	public Node createUnqualifiedConstructorInvocation(Node constructorTypeArgs, Node type, Node args, Node anonymousClassBody) {
		ConstructorInvocation result = new ConstructorInvocation()
				.rawTypeReference(type)
				.rawAnonymousClassBody(anonymousClassBody);
		
		if (constructorTypeArgs instanceof TemporaryNode.TypeArguments) {
			for (Node arg : ((TemporaryNode.TypeArguments)constructorTypeArgs).arguments) {
				result.rawConstructorTypeArguments().addToEnd(arg);
			}
		}
		
		if (args instanceof TemporaryNode.MethodArguments) {
			for (Node arg : ((TemporaryNode.MethodArguments)args).arguments) {
				result.rawArguments().addToEnd(arg);
			}
		} else DanglingNodes.addDanglingNode(result, args);
		
		return posify(result);
	}
	
	public Node createArrayInitializerExpression(Node head, List<Node> tail) {
		ArrayInitializer ai = new ArrayInitializer();
		if (head != null) ai.rawExpressions().addToEnd(head);
		if (tail != null) for (Node n : tail) if (n != null) ai.rawExpressions().addToEnd(n);
		return posify(ai);
	}
	
	public Node createDimension(Node dimExpr, org.parboiled.Node<Node> arrayOpen) {
		ArrayDimension d = new ArrayDimension().rawDimension(dimExpr);
		if (arrayOpen != null) d.setPosition(new Position(arrayOpen.getStartIndex(), currentPos()));
		return d;
	}
	
	public Node createArrayCreationExpression(Node type, List<Node> dimensions, Node initializer) {
		ArrayCreation ac = new ArrayCreation().rawComponentTypeReference(type).rawInitializer(initializer);
		if (dimensions != null) for (Node d : dimensions) {
			if (d != null) ac.rawDimensions().addToEnd(d);
		}
		
		return posify(ac);
	}
	
	public Node addParens(Node v) {
		if (v instanceof Expression) {
			((Expression)v).astParensPositions().add(new Position(startPos(), currentPos()));
		}
		return v;
	}
	
	public Node createThisOrSuperOrClass(org.parboiled.Node<Node> dot, String text, Node qualifier) {
		Node result;
		if ("super".equals(text)) result = new Super().rawQualifier(qualifier);
		else if ("class".equals(text)) result = new ClassLiteral().rawTypeReference(qualifier);
		else result = new This().rawQualifier(qualifier);
		if (dot != null) source.registerStructure(result, dot);
		return posify(result);
	}
	
	public boolean checkIfLevel1ExprIsValidForAssignment(Node node) {
		return node instanceof VariableReference || node instanceof Select || node instanceof ArrayAccess;
	}
	
	public boolean checkIfMethodOrConstructorInvocation(Node node) {
		return node instanceof MethodInvocation || node instanceof ConstructorInvocation;
	}
	
	public boolean typeIsAlsoLegalAsExpression(Node type) {
		if (!(type instanceof TypeReference)) return true;
		TypeReference tr = (TypeReference)type;
		if (tr.astArrayDimensions() > 0) return false;
		if (tr.isPrimitive() || tr.isVoid()) return false;
		for (Node part : tr.rawParts()) {
			if (part instanceof TypeReferencePart) {
				if (!((TypeReferencePart)part).rawTypeArguments().isEmpty()) return false;
			}
		}
		
		return true;
	}
}
