/*******************************************************************************
 * 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
 *    Markus Schorn (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.rewrite.changegenerator;

import org.eclipse.cdt.core.dom.ast.IASTArrayModifier;
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.IASTInitializer;
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.IASTProblem;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTTypeId;
import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier.IASTEnumerator;
import org.eclipse.cdt.core.dom.ast.cpp.CPPASTVisitor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModificationStore;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification.ModificationKind;
import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ASTWriterVisitor;
import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap;

/**
 * 
 * Visits the nodes in consideration of {@link ASTModification}s.
 * 
 * @since 5.0
 * @author Emanuel Graf IFS
 * 
 */
public class ChangeGeneratorWriterVisitor extends ASTWriterVisitor {

	private static final String DEFAULT_INDENTATION = ""; //$NON-NLS-1$
	private final ASTModificationStore modificationStore;
	private final String fileScope;
	private ModificationScopeStack stack;

	public ChangeGeneratorWriterVisitor(CPPASTVisitor delegateVisitor,
			ASTModificationStore modificationStore, String fileScope, NodeCommentMap commentMap) {
		super(commentMap);

		this.modificationStore = modificationStore;
		this.fileScope = fileScope;
		this.stack = new ModificationScopeStack(modificationStore);

		shouldVisitExpressions = delegateVisitor.shouldVisitExpressions;
		shouldVisitStatements = delegateVisitor.shouldVisitStatements;
		shouldVisitNames = delegateVisitor.shouldVisitNames;
		shouldVisitDeclarations = delegateVisitor.shouldVisitDeclarators;
		shouldVisitDeclSpecifiers = delegateVisitor.shouldVisitDeclSpecifiers;
		shouldVisitDeclarators = delegateVisitor.shouldVisitDeclarators;
		shouldVisitInitializers = delegateVisitor.shouldVisitInitializers;
		shouldVisitBaseSpecifiers = delegateVisitor.shouldVisitBaseSpecifiers;
		shouldVisitNamespaces = delegateVisitor.shouldVisitNamespaces;
		shouldVisitTemplateParameters = delegateVisitor.shouldVisitTemplateParameters;
		shouldVisitParameterDeclarations = delegateVisitor.shouldVisitParameterDeclarations;
		shouldVisitTranslationUnit = delegateVisitor.shouldVisitTranslationUnit;
		shouldVisitProblems = delegateVisitor.shouldVisitProblems;
		shouldVisitTypeIds = delegateVisitor.shouldVisitTypeIds;

	}

	public ChangeGeneratorWriterVisitor(ASTModificationStore modStore, NodeCommentMap nodeMap) {
		this(modStore, DEFAULT_INDENTATION, null, nodeMap);
	}

	public ChangeGeneratorWriterVisitor(ASTModificationStore modStore,
			String givenIndentation, String fileScope, NodeCommentMap commentMap) {
		super(givenIndentation, commentMap);
		this.modificationStore = modStore;
		this.fileScope = fileScope;
		this.shouldVisitTranslationUnit = true;
		this.stack = new ModificationScopeStack(modificationStore);
		declaratorWriter = new ModifiedASTDeclaratorWriter(scribe, this,
				stack, commentMap);
		expWriter = new ModifiedASTExpressionWriter(scribe, this, macroHandler,
				stack, commentMap);
		statementWriter = new ModifiedASTStatementWriter(scribe, this, stack, commentMap);
		declSpecWriter = new ModifiedASTDeclSpecWriter(scribe, this, stack, commentMap);
		declarationWriter = new ModifiedASTDeclarationWriter(scribe, this, stack, commentMap);
		

	}

	@Override
	protected IASTDeclarator getParameterDeclarator(
			IASTParameterDeclaration parameterDeclaration) {
		
		IASTDeclarator newDecl = parameterDeclaration.getDeclarator();
		if(stack.getModifiedNodes().contains(newDecl)){	
			for(ASTModification currentModification : stack.getModificationsForNode(newDecl)){
				if(currentModification.getKind() == ASTModification.ModificationKind.REPLACE && currentModification.getTargetNode() == parameterDeclaration){
					newDecl = (IASTDeclarator) currentModification.getNewNode();
				}
			}
		}
		return newDecl;
	}

	@Override
	protected IASTName getParameterName(IASTDeclarator declarator) {
		IASTName newName = declarator.getName();
		if(stack.getModifiedNodes().contains(newName)){	
			for(ASTModification currentModification : stack.getModificationsForNode(newName)){
				if(currentModification.getKind() == ASTModification.ModificationKind.REPLACE && currentModification.getTargetNode() == newName){
					newName = (IASTName) currentModification.getNewNode();
				}
			}
		}
		return newName;
	}

	@Override
	public int leave(ICPPASTBaseSpecifier specifier) {
		super.leave(specifier);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(ICPPASTNamespaceDefinition namespace) {
		super.leave(namespace);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(ICPPASTTemplateParameter parameter) {
		super.leave(parameter);
		return PROCESS_SKIP;
	}

	@Override
	public int visit(ICPPASTBaseSpecifier specifier) {
		if (doBeforeEveryNode(specifier) == PROCESS_CONTINUE) {
			return super.visit(specifier);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(ICPPASTNamespaceDefinition namespace) {
		if (doBeforeEveryNode(namespace) == PROCESS_CONTINUE) {
			return super.visit(namespace);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(ICPPASTTemplateParameter parameter) {
		if (doBeforeEveryNode(parameter) == PROCESS_CONTINUE) {
			return super.visit(parameter);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTDeclaration declaration) {
		super.leave(declaration);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTDeclarator declarator) {
		super.leave(declarator);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTDeclSpecifier declSpec) {
		super.leave(declSpec);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTEnumerator enumerator) {
		super.leave(enumerator);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTExpression expression) {
		super.leave(expression);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTInitializer initializer) {
		super.leave(initializer);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTName name) {
		super.leave(name);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTParameterDeclaration parameterDeclaration) {
		super.leave(parameterDeclaration);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTProblem problem) {
		super.leave(problem);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTStatement statement) {
		super.leave(statement);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTTranslationUnit tu) {
		super.leave(tu);
		return PROCESS_SKIP;
	}

	@Override
	public int leave(IASTTypeId typeId) {
		super.leave(typeId);
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTDeclaration declaration) {
		if (doBeforeEveryNode(declaration) == PROCESS_CONTINUE) {
			return super.visit(declaration);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTDeclarator declarator) {
		if (doBeforeEveryNode(declarator) == PROCESS_CONTINUE) {
			return super.visit(declarator);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTDeclSpecifier declSpec) {
		if (doBeforeEveryNode(declSpec) == PROCESS_CONTINUE) {
			return super.visit(declSpec);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTEnumerator enumerator) {
		if (doBeforeEveryNode(enumerator) == PROCESS_CONTINUE) {
			return super.visit(enumerator);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTArrayModifier arrayModifier) {
		if (doBeforeEveryNode(arrayModifier) == PROCESS_CONTINUE) {
			return super.visit(arrayModifier);
		}
		return PROCESS_SKIP;
	}
	
	@Override
	public int visit(IASTExpression expression) {
		if (doBeforeEveryNode(expression) == PROCESS_CONTINUE) {
			return super.visit(expression);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTInitializer initializer) {
		if (doBeforeEveryNode(initializer) == PROCESS_CONTINUE) {
			return super.visit(initializer);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTName name) {
		if (doBeforeEveryNode(name) == PROCESS_CONTINUE) {
			return super.visit(name);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTParameterDeclaration parameterDeclaration) {
		if (doBeforeEveryNode(parameterDeclaration) == PROCESS_CONTINUE) {
			return super.visit(parameterDeclaration);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTProblem problem) {
		if (doBeforeEveryNode(problem) == PROCESS_CONTINUE) {
			return super.visit(problem);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTStatement statement) {
		if (doBeforeEveryNode(statement) == PROCESS_CONTINUE) {
			return super.visit(statement);
		}
		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTTranslationUnit tu) {
		ASTModificationHelper helper = new ASTModificationHelper(
				stack);

		IASTDeclaration[] declarations = helper.createModifiedChildArray(tu, tu.getDeclarations(), IASTDeclaration.class);
		for (IASTDeclaration currentDeclaration : declarations) {
			currentDeclaration.accept(this);
		}

		return PROCESS_SKIP;
	}

	@Override
	public int visit(IASTTypeId typeId) {
		if (doBeforeEveryNode(typeId) == PROCESS_CONTINUE) {
			return super.visit(typeId);
		}
		return PROCESS_SKIP;
	}

	protected int doBeforeEveryNode(IASTNode node) {
		stack.clean(node);
		if (fileScope != null) {
			String file = getCorrespondingFile(node);
			if (!fileScope.equals(file)) {
				return PROCESS_SKIP;
			}
		}
		
		//Check all insert before and append modifications for the current node. If necessary put it onto the stack.
		for (IASTNode currentModifiedNode : stack.getModifiedNodes()) {
			for (ASTModification currentMod : stack.getModificationsForNode(currentModifiedNode)) {
				if(currentMod.getNewNode() == node){
					if(currentMod.getKind() != ModificationKind.REPLACE) {
						stack.pushScope(currentModifiedNode);
						return PROCESS_CONTINUE;
					}
				}
			}
		}
		//Check all replace modifications for the current node. Visit the replacing node if found.
		for (IASTNode currentModifiedNode : stack.getModifiedNodes()) {			
			for (ASTModification currentMod : stack.getModificationsForNode(currentModifiedNode)) {
				if(currentMod.getTargetNode() == node && currentMod.getKind() == ModificationKind.REPLACE){
					if(currentMod.getNewNode() != null){
						stack.pushScope(node);
						currentMod.getNewNode().accept(this);
						stack.popScope(node);
						return PROCESS_SKIP;
					}
					else{
						return PROCESS_SKIP;
					}
				}
			}
		}
		
		return PROCESS_CONTINUE;
	}

	private String getCorrespondingFile(IASTNode node) {

		if (node.getFileLocation() != null) {
			return node.getFileLocation().getFileName();
		}

		if (node.getParent() != null) {
			return getCorrespondingFile(node.getParent());
		}

		for (IASTNode modifiedNode : modificationStore.getRootModifications()
				.getModifiedNodes()) {
			for (ASTModification modification : modificationStore
					.getRootModifications().getModificationsForNode(
							modifiedNode)) {
				if (modification.getNewNode() == node) {
					return getCorrespondingFile(modification.getTargetNode());
				}
			}
		}
		return null;
	}
}
