/*******************************************************************************
 * Copyright (c) 2006, 2013 Wind River Systems, Inc. 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:
 *     Markus Schorn - initial API and implementation
 *     Sergey Prigogin (Google)
 *******************************************************************************/ 
package org.eclipse.cdt.internal.core.pdom.indexer;

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

import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
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.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTInitializer;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTProblem;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IFunction;
import org.eclipse.cdt.core.dom.ast.c.ICASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCapture;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLambdaExpression;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;

abstract public class IndexerASTVisitor extends ASTVisitor {
	private static class Definition {
		Definition(IASTName name, IASTNode node) {
			fName= name;
			fNode= node;
		}
		IASTName fName;
		IASTNode fNode;
	}
	
	private IASTName fDefinitionName;
	private IASTNode fDefinitionNode;
	private ArrayList<Definition> fStack= new ArrayList<Definition>();
	private ArrayList<IASTProblem> fProblems= new ArrayList<IASTProblem>();

	public IndexerASTVisitor(boolean visitImplicitNames) {
		shouldVisitNames= true;
		shouldVisitImplicitNames = visitImplicitNames;
		shouldVisitDeclarations= true;
		shouldVisitInitializers= true;
		shouldVisitDeclSpecifiers= true;
		shouldVisitProblems= true;
		shouldVisitExpressions= true;
	}
	
	public List<IASTProblem> getProblems() {
		return fProblems;
	}

	abstract public void visit(IASTName name, IASTName definitionName);

	@Override
	final public int visit(IASTName name) {
		if (!(name instanceof ICPPASTQualifiedName)) {
			if (name != fDefinitionName) {
				visit(name, fDefinitionName);
			}
		}
		return PROCESS_CONTINUE;
	}

	private void push(IASTName name, IASTNode node) {
		if (fDefinitionName != null) {
			fStack.add(new Definition(fDefinitionName, fDefinitionNode));
		}
		name = getLastInQualified(name);
		fDefinitionName= name;
		fDefinitionNode= node;
	}

	private IASTName getLastInQualified(IASTName name) {
		if (name instanceof ICPPASTQualifiedName) {
			name= ((ICPPASTQualifiedName) name).getLastName();
		}
		return name;
	}

	private void pop(IASTNode node) {
		if (node == fDefinitionNode) {
			if (fStack.isEmpty()) {
				fDefinitionName= null;
				fDefinitionNode= null;
			}
			else {
				Definition old= fStack.remove(fStack.size()-1);
				fDefinitionName= old.fName;
				fDefinitionNode= old.fNode;
			}
		}
	}

	// functions and methods
	@Override
	public int visit(IASTDeclaration decl) {
		if (decl instanceof IASTFunctionDefinition) {
			IASTFunctionDefinition fdef= (IASTFunctionDefinition) decl;
			final IASTFunctionDeclarator declarator= fdef.getDeclarator();
			IASTDeclarator nestedDeclarator= declarator;
			while (nestedDeclarator.getNestedDeclarator() != null) {
				nestedDeclarator= nestedDeclarator.getNestedDeclarator();
			}
			IASTName name= getLastInQualified(nestedDeclarator.getName());
			visit(name, fDefinitionName);
			push(name, decl);
		} else if (decl instanceof IASTSimpleDeclaration) {
			IASTSimpleDeclaration sdecl= (IASTSimpleDeclaration) decl;
			if (sdecl.getDeclSpecifier().getStorageClass() == IASTDeclSpecifier.sc_typedef) {
				IASTDeclarator[] declarators= sdecl.getDeclarators();
				for (IASTDeclarator declarator : declarators) {
					if (declarator.getPointerOperators().length == 0 &&
							declarator.getNestedDeclarator() == null) {
						IASTName name= getLastInQualified(declarator.getName());
						visit(name, fDefinitionName);
						push(name, decl);
					}
				}
			}
		} 
		return PROCESS_CONTINUE;
	}

	@Override
	public int leave(IASTDeclaration decl) {
		pop(decl);
		return PROCESS_CONTINUE;
	}

	// class definitions, typedefs
	@Override
	public int visit(IASTDeclSpecifier declspec) {
		if (declspec instanceof ICPPASTCompositeTypeSpecifier) {
			ICPPASTCompositeTypeSpecifier cts= (ICPPASTCompositeTypeSpecifier) declspec;
			IASTName name = getLastInQualified(cts.getName());
			visit(name, fDefinitionName);
			push(name, declspec);
		}
		if (declspec instanceof ICASTCompositeTypeSpecifier) {
			ICASTCompositeTypeSpecifier cts= (ICASTCompositeTypeSpecifier) declspec;
			IASTName name = cts.getName();
			visit(name, fDefinitionName);
			push(name, declspec);
		}
		return PROCESS_CONTINUE;
	}

	@Override
	public int leave(IASTDeclSpecifier declspec) {
		pop(declspec);
		return PROCESS_CONTINUE;
	}
	
	@Override
	public int visit(IASTProblem problem) {
		fProblems.add(problem);
		return PROCESS_SKIP;
	}

	// variable and field initializers
	@Override
	public int visit(IASTInitializer initializer) {
		if (!(fDefinitionNode instanceof IASTFunctionDefinition)) {
			IASTNode cand= initializer.getParent();
			if (cand instanceof IASTDeclarator) {
				cand= ASTQueries.findInnermostDeclarator((IASTDeclarator) cand);
				push(((IASTDeclarator) cand).getName(), initializer);
			}
		}
		return PROCESS_CONTINUE;
	}
	
	@Override
	public int leave(IASTInitializer initializer) {
		pop(initializer);
		return PROCESS_CONTINUE;
	}
	
	// Lambda expressions
	@Override
	public int visit(IASTExpression expr) {
		if (expr instanceof ICPPASTLambdaExpression) {
			return visit((ICPPASTLambdaExpression) expr);
		}
		return PROCESS_CONTINUE;
	}

	private int visit(final ICPPASTLambdaExpression lambdaExpr) {
		// Captures 
		for (ICPPASTCapture cap : lambdaExpr.getCaptures()) {
			if (!cap.accept(this))
				return PROCESS_ABORT;
		}
		// Definition of closure type
		final IASTName closureName = lambdaExpr.getClosureTypeName();
		visit(closureName, fDefinitionName);

		// Definition of call operator
		IASTName callOp= lambdaExpr.getFunctionCallOperatorName();
		visit(callOp, closureName);

		IBinding owner = CPPVisitor.findDeclarationOwner(lambdaExpr, true);
		boolean localToFunction = owner instanceof IFunction;
		if (!localToFunction)
			push(callOp, lambdaExpr); // Local closures don't appear in the index, so don't refer to them.

		ICPPASTFunctionDeclarator dtor = lambdaExpr.getDeclarator();
		if (dtor != null && !dtor.accept(this))
			return PROCESS_ABORT;
		
		IASTCompoundStatement body = lambdaExpr.getBody();
		if (body != null && !body.accept(this))
			return PROCESS_ABORT;
		
		if (!localToFunction)
			pop(lambdaExpr);
		return PROCESS_SKIP;
	}
}