/*******************************************************************************
 * Copyright (c) 2008, 2012 Institute for Software, HSR Hochschule fuer Technik  
 * Rapperswil, University of applied sciences and others
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution, and is available at 
 * http://www.eclipse.org/legal/epl-v10.html  
 *  
 * Contributors: 
 *     Institute for Software - initial API and implementation
 *     Sergey Prigogin (Google)
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring.extractfunction;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTExpressionStatement;
import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTNode.CopyStyle;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IBasicType;
import org.eclipse.cdt.core.dom.ast.IBasicType.Kind;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.INodeFactory;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNewExpression;
import org.eclipse.cdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.cdt.core.dom.rewrite.DeclarationGenerator;

import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTBinaryExpression;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFieldReference;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFunctionDefinition;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTIdExpression;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTReturnStatement;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTSimpleDeclSpecifier;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTSimpleDeclaration;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPFunction;

import org.eclipse.cdt.internal.ui.refactoring.NameInformation;

/**
 * Handles the extraction of expression nodes, for example, return type determination.
 * 
 * @author Mirko Stocker
 */
public class ExpressionExtractor extends FunctionExtractor {
	@Override
	public boolean canChooseReturnValue() {
		return false;
	}

	@Override
	public void constructMethodBody(IASTCompoundStatement compound, List<IASTNode> nodes,
			List<NameInformation> parameters, ASTRewrite rewrite, TextEditGroup group) {
		CPPASTReturnStatement statement = new CPPASTReturnStatement();
		statement.setReturnValue(getExpression(nodes));
		ASTRewrite subRewrite = rewrite.insertBefore(compound, null, statement, group);
		Map<IASTName, NameInformation> changedParameters = getChangedParameterReferences(parameters);
		INodeFactory nodeFactory = nodes.get(0).getTranslationUnit().getASTNodeFactory();
		adjustParameterReferences(statement, changedParameters, nodeFactory, subRewrite, group);
	}

	private IASTExpression getExpression(List<IASTNode> nodes) {
		if (nodes.size() > 1) {
			CPPASTBinaryExpression expression = new CPPASTBinaryExpression();
			expression.setParent(nodes.get(0).getParent());
			expression.setOperand1((IASTExpression) nodes.get(0).copy(CopyStyle.withLocations));
			expression.setOperator(((IASTBinaryExpression) nodes.get(1).getParent()).getOperator());
			expression.setOperand2(getExpression(nodes.subList(1, nodes.size())));
			return expression;
		} else {
			return (IASTExpression) nodes.get(0).copy(CopyStyle.withLocations);
		}
	}

	@Override
	public IASTDeclSpecifier determineReturnType(IASTNode extractedNode, NameInformation _) {
		List<ITypedef> typedefs = getTypedefs(extractedNode);
		if (extractedNode instanceof IASTExpression) {
			IASTExpression exp = (IASTExpression) extractedNode;
			INodeFactory factory = extractedNode.getTranslationUnit().getASTNodeFactory();
			DeclarationGenerator generator = DeclarationGenerator.create(factory);
			IType expressionType = exp.getExpressionType();
			for (ITypedef typedef : typedefs) {
				if (typedef.getType().isSameType(expressionType)) {
					return generator.createDeclSpecFromType(typedef);
				}
			}
			return generator.createDeclSpecFromType(expressionType);
		} else { // Fallback
			return createSimpleDeclSpecifier(Kind.eVoid);
		}
	}

	private List<ITypedef> getTypedefs(IASTNode extractedNode) {
		final ArrayList<ITypedef> typeDefs = new ArrayList<ITypedef>();
		extractedNode.accept(new ASTVisitor() {
			{
				shouldVisitExpressions = true;
			}
			
			@Override
			public int visit(IASTExpression expression) {
				if (expression instanceof IASTIdExpression) {
					IASTIdExpression id = (IASTIdExpression) expression;
					IBinding binding = id.getName().resolveBinding();
					IType expressionType = null;
					if (binding instanceof IVariable) {
						expressionType = ((IVariable) binding).getType();
					}
					if (binding instanceof IType) {
						expressionType = (IType) binding;
					}
					if (expressionType != null && expressionType instanceof ITypedef) {
						ITypedef typdef = (ITypedef) expressionType;
						typeDefs.add(typdef);
					}
				}
				return PROCESS_CONTINUE;
			}
		});
		return typeDefs;
	}

	private static IASTDeclSpecifier createSimpleDeclSpecifier(IBasicType.Kind type) {
		IASTSimpleDeclSpecifier declSpec = new CPPASTSimpleDeclSpecifier();
		declSpec.setType(type);
		return declSpec;
	}
	
	private static IASTName findCalledFunctionName(IASTFunctionCallExpression callExpression) {
		IASTExpression functionNameExpression = callExpression.getFunctionNameExpression();
		IASTName functionName = null;
		
		if (functionNameExpression instanceof CPPASTIdExpression) {
			CPPASTIdExpression idExpression = (CPPASTIdExpression) functionNameExpression;
			functionName = idExpression.getName();
		} else if (functionNameExpression instanceof CPPASTFieldReference) {
			CPPASTFieldReference fieldReference = (CPPASTFieldReference) functionNameExpression;
			functionName = fieldReference.getFieldName();
		}		
		return functionName;
	}

	@Override
	protected boolean hasPointerReturnType(IASTNode node) {
		if (node instanceof ICPPASTNewExpression) {
			return true;
		} else if (!(node instanceof IASTFunctionCallExpression)) {
			return false;
		}

		IASTName functionName = findCalledFunctionName((IASTFunctionCallExpression) node);
		if (functionName != null) {
			IBinding binding = functionName.resolveBinding();
			if (binding instanceof CPPFunction) {
				CPPFunction function =  (CPPFunction) binding;
				if (function.getDefinition() != null) {
					IASTNode parent = function.getDefinition().getParent();
					if (parent instanceof CPPASTFunctionDefinition) {
						CPPASTFunctionDefinition definition = (CPPASTFunctionDefinition) parent;
						return definition.getDeclarator().getPointerOperators().length > 0;
					}
				} else if (hasDeclaration(function)) {
					IASTNode parent = function.getDeclarations()[0].getParent();
					if (parent instanceof CPPASTSimpleDeclaration) {
						CPPASTSimpleDeclaration declaration = (CPPASTSimpleDeclaration) parent;
						return declaration.getDeclarators().length > 0 &&
								declaration.getDeclarators()[0].getPointerOperators().length > 0;
					}
				}
			}
		}
		return false;
	}

	private static boolean hasDeclaration(CPPFunction function) {
		return function != null && function.getDeclarations() != null &&
				function.getDeclarations().length > 0;
	}
	
	@Override
	public IASTNode createReturnAssignment(IASTNode node, IASTExpressionStatement stmt,
			IASTExpression callExpression) {
		return callExpression;
	}
}
