/*******************************************************************************
 * Copyright (c) 2008 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
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring.utils;

import java.util.ArrayList;
import java.util.Collection;

import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTPointerOperator;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDirective;

import org.eclipse.cdt.internal.ui.refactoring.extractfunction.TrailNodeEqualityChecker;

public class ASTHelper {
	private ASTHelper() {
	}

	static public IASTNode getDeclarationForNode(IASTNode tmpNode) {
		while (tmpNode != null && !(tmpNode instanceof IASTSimpleDeclaration) && !(tmpNode instanceof IASTParameterDeclaration)) {
			tmpNode = tmpNode.getParent();
		}
		return tmpNode;
	}

	static public IASTDeclarator getDeclaratorForNode(IASTNode aNode) {
		IASTNode tmpNode = getDeclarationForNode(aNode);

		IASTDeclarator declarator = null;
		if (tmpNode instanceof IASTSimpleDeclaration) {
			IASTSimpleDeclaration decl = (IASTSimpleDeclaration) tmpNode;
			if (decl.getDeclarators().length > 0) {
				declarator = decl.getDeclarators()[0];
			}
		} else if (tmpNode instanceof IASTParameterDeclaration) {
			IASTParameterDeclaration decl = (IASTParameterDeclaration) tmpNode;
			declarator = decl.getDeclarator();
		}
		return declarator;
	}

	static public IASTDeclSpecifier getDeclarationSpecifier(IASTNode declaration) {
		if (declaration != null) {
			if (declaration instanceof IASTSimpleDeclaration) {
				IASTSimpleDeclaration simpleDecl = (IASTSimpleDeclaration) declaration;
				return simpleDecl.getDeclSpecifier();
			} else if (declaration instanceof ICPPASTParameterDeclaration) {
				ICPPASTParameterDeclaration paramDecl = (ICPPASTParameterDeclaration) declaration;
				return paramDecl.getDeclSpecifier();
			}
		}
		return null;
	}

	public static boolean samePointers(IASTPointerOperator[] pointerOperators1, IASTPointerOperator[] pointerOperators2, TrailNodeEqualityChecker checker) {
		if (pointerOperators2.length == pointerOperators1.length) {
			for (int i = 0; i < pointerOperators2.length; i++) {
				IASTPointerOperator operator1 = pointerOperators1[i];
				IASTPointerOperator operator2 = pointerOperators2[i];
				if (!checker.isEquals(operator1, operator2)) {
					return false;
				}
			}
		} else {
			return false;
		}

		return true;
	}

	public static boolean isClassDeklarationName(IASTName astName) {
		if (astName == null)
			return false;
		IASTNode parent = astName.getParent();
		if (parent instanceof ICPPASTCompositeTypeSpecifier) {
			ICPPASTCompositeTypeSpecifier typeSpecifier = (ICPPASTCompositeTypeSpecifier) parent;
			return typeSpecifier.getKey() == ICPPASTCompositeTypeSpecifier.k_class;
		}
		return false;
	}

	public static ArrayList<ICPPASTNamespaceDefinition> getNamespaces(IASTNode node) {
		ArrayList<ICPPASTNamespaceDefinition> namespaces = new ArrayList<ICPPASTNamespaceDefinition>();
		for (IASTNode aktNode = node; aktNode != null; aktNode = aktNode.getParent()) {
			if (aktNode instanceof ICPPASTNamespaceDefinition) {
				namespaces.add(0, (ICPPASTNamespaceDefinition) aktNode);
			} else if(aktNode instanceof ICPPASTQualifiedName) {
				namespaces.addAll(getNamespaces((ICPPASTQualifiedName) aktNode));
			}
		}
		return namespaces;
	}
	
	public static ArrayList<ICPPASTNamespaceDefinition> getNamespaces(ICPPASTQualifiedName qualifiedName) {
		ArrayList<ICPPASTNamespaceDefinition> namespaces = new ArrayList<ICPPASTNamespaceDefinition>();
		for(IASTName aktQualifiedPartName : qualifiedName.getNames()) {
			IBinding binding = aktQualifiedPartName.resolveBinding();
			for(IASTName aktResolvedName : qualifiedName.getTranslationUnit().getDefinitionsInAST(binding)) {
				if(aktResolvedName.getParent() instanceof ICPPASTNamespaceDefinition) {
					namespaces.add((ICPPASTNamespaceDefinition) aktResolvedName.getParent());
					break;
				}
			}
		}
		return namespaces;
	}

	public static Collection<IASTDeclSpecifier> getCompositTypeSpecifiers(IASTNode baseNode) {
		final Collection<IASTDeclSpecifier> specifiers = new ArrayList<IASTDeclSpecifier>();
		ASTVisitor visitor = new ASTVisitor() {

			@Override
			public int visit(IASTDeclSpecifier declSpec) {
				specifiers.add(declSpec);
				return super.visit(declSpec);
			}
		};
		visitor.shouldVisitDeclSpecifiers = true;
		baseNode.accept(visitor);
		return specifiers;
	}
	
	public static Collection<IASTPreprocessorStatement> getAllInFilePreprocessorStatements(IASTTranslationUnit unit, String aktFileName) {
		Collection<IASTPreprocessorStatement> statements = new ArrayList<IASTPreprocessorStatement>();
		for(IASTPreprocessorStatement aktStatement : unit.getAllPreprocessorStatements()) {
			if(aktStatement.getFileLocation() == null) {
				continue;
			} else if (aktStatement.getFileLocation().getFileName().equals(aktFileName)) {
				statements.add(aktStatement);
			}
		}
		return statements;
	}
	
	public static Collection<IASTDeclaration> getAllInFileDeclarations(IASTTranslationUnit unit, String aktFileName) {
		Collection<IASTDeclaration> decls = new ArrayList<IASTDeclaration>();
		for(IASTDeclaration aktDecl: unit.getDeclarations()) {
			if(aktDecl.getFileLocation() == null) {
				continue;
			} else if(aktDecl.getFileLocation().getFileName().equals(aktFileName)) {
				decls.add(aktDecl);
			}
		}
		return decls;
	}
	
	public static ICPPASTUsingDirective getActiveUsingDirecitveForNode(IASTNode node, IASTTranslationUnit unit) {
		ICPPASTUsingDirective activeDirective = null;
		for(IASTDeclaration aktDeclaration : getAllInFileDeclarations(unit, node.getFileLocation().getFileName())) {
			if(aktDeclaration.getFileLocation().getNodeOffset() >= node.getFileLocation().getNodeOffset()) {
				break;
			}
			if(aktDeclaration instanceof ICPPASTUsingDirective) {
				activeDirective = (ICPPASTUsingDirective) aktDeclaration;
			}
		} 
		return activeDirective;
	}

	public static Collection<ICPPASTUsingDeclaration> getUsingDeclarations(IASTTranslationUnit unit) {
		Collection<ICPPASTUsingDeclaration> usingDecls = new ArrayList<ICPPASTUsingDeclaration>();
		for(IASTDeclaration aktDecl : unit.getDeclarations()) {
			if (aktDecl instanceof ICPPASTUsingDeclaration) {
				usingDecls.add((ICPPASTUsingDeclaration) aktDecl);
			}
		}
		return usingDecls;
	}

	public static boolean isClassDefinitionName(IASTName name) {
		try {
			if(!(name.getParent().getParent().getParent() instanceof IASTFunctionDefinition)) {
				return false;
			}
			ICPPASTQualifiedName qName = (ICPPASTQualifiedName) name.getParent();
			IASTName secondLastName = qName.getNames()[qName.getNames().length-2];
			if(!(name.equals(secondLastName))) {
				return false;
			}
			IBinding binding = name.resolveBinding();
			for(IASTName aktName : name.getTranslationUnit().getDeclarationsInAST(binding)) {
				if(!isClassDeklarationName(aktName)) {
					return false;
				}
			}
			
		} catch (NullPointerException e) {
			return false;
		}
		return true;
	}
	
	public static IASTCompositeTypeSpecifier getCompositeTypeSpecifierForName(IASTName name) {
		IBinding binding = name.resolveBinding();
		for(IASTName aktName : name.getTranslationUnit().getDefinitionsInAST(binding)) {
			if(aktName.getParent() instanceof IASTCompositeTypeSpecifier) {
				return (IASTCompositeTypeSpecifier) aktName.getParent();
			}
		}
		return null;
	}
	
	public static Collection<IASTFunctionDeclarator> getFunctionDeclaratorsForClass(IASTCompositeTypeSpecifier klass) {
		Collection<IASTFunctionDeclarator> declarators = new ArrayList<IASTFunctionDeclarator>();
		for(IASTDeclaration aktDeclaration : klass.getMembers()) {
			if(aktDeclaration instanceof IASTSimpleDeclaration) {
				for(IASTDeclarator aktDeclarator : ((IASTSimpleDeclaration) aktDeclaration).getDeclarators()) {
					if(aktDeclarator instanceof IASTFunctionDeclarator) {
						declarators.add((IASTFunctionDeclarator) aktDeclarator);
					}
				}
			}
		}
		return declarators;
	}
	
	public static Collection<IASTFunctionDefinition> getFunctionDefinitionsForClass(IASTCompositeTypeSpecifier klass) {
		Collection<IASTFunctionDefinition> definitions = new ArrayList<IASTFunctionDefinition>();
		for(IASTFunctionDeclarator aktDeclarator : getFunctionDeclaratorsForClass(klass)) {
			IBinding binding = aktDeclarator.getName().resolveBinding();
			for(IASTName aktName : aktDeclarator.getTranslationUnit().getDefinitionsInAST(binding)) {
				if(aktName.getParent().getParent().getParent() instanceof IASTFunctionDefinition) {
					definitions.add((IASTFunctionDefinition) aktName.getParent().getParent().getParent());
				}
			}
		}
		return definitions;
	}
}
