/*******************************************************************************
 * 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.astwriter;

import org.eclipse.cdt.core.dom.ast.IASTASMDeclaration;
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.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTProblemDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.CPPASTVisitor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCatchHandler;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTExplicitTemplateInstantiation;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionTryBlockDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLinkageSpecification;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceAlias;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDirective;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
import org.eclipse.cdt.core.dom.ast.gnu.cpp.IGPPASTExplicitTemplateInstantiation;
import org.eclipse.cdt.internal.core.dom.parser.IASTAmbiguousDeclaration;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap;


/**
 * 
 * Generates source code of declaration nodes. The actual string operations are delegated
 * to the <code>Scribe</code> class.
 * 
 * @see Scribe
 * @see IASTDeclaration
 * @author Emanuel Graf IFS
 * 
 */
public class DeclarationWriter extends NodeWriter{
	
	private static final String ASM_END = ")"; //$NON-NLS-1$
	private static final String ASM_START = "asm("; //$NON-NLS-1$
	private static final String TEMPLATE_DECLARATION = "template<"; //$NON-NLS-1$
	private static final String EXPORT = "export "; //$NON-NLS-1$
	private static final String TEMPLATE_SPECIALIZATION = "template <> "; //$NON-NLS-1$
	private static final String NAMESPACE = "namespace "; //$NON-NLS-1$
	private static final String USING = "using "; //$NON-NLS-1$
	private boolean printSemicolon;
	
	public DeclarationWriter(Scribe scribe, CPPASTVisitor visitor, NodeCommentMap commentMap) {
		super(scribe, visitor, commentMap);
	}
	
	protected void writeDeclaration(IASTDeclaration declaration) throws ProblemRuntimeException{
		writeDeclaration(declaration, true);
	}
	
	protected void writeDeclaration(IASTDeclaration declaration, boolean writeSemicolon) {
		boolean addNewLine = true;
		printSemicolon = writeSemicolon;
		if (declaration instanceof IASTAmbiguousDeclaration) {
			//Not implemented
		} else if (declaration instanceof IASTASMDeclaration) {
			writeASMDeclatation((IASTASMDeclaration) declaration);
		} else if (declaration instanceof IASTFunctionDefinition) {
			writeFunctionDefinition((IASTFunctionDefinition) declaration);
		} else if (declaration instanceof IASTProblemDeclaration) {
			throw new ProblemRuntimeException((IASTProblemDeclaration) declaration);
		} else if (declaration instanceof IASTSimpleDeclaration) {
			writeSimpleDeclaration((IASTSimpleDeclaration) declaration);
		} else if (declaration instanceof ICPPASTExplicitTemplateInstantiation) {
			writeExplicitTemplateInstantiation((ICPPASTExplicitTemplateInstantiation) declaration);
			addNewLine = false;
		} else if (declaration instanceof ICPPASTLinkageSpecification) {
			writeLinkageSpecification((ICPPASTLinkageSpecification) declaration);
		} else if (declaration instanceof ICPPASTNamespaceAlias) {
			writeNamespaceAlias((ICPPASTNamespaceAlias) declaration); 
		} else if (declaration instanceof ICPPASTTemplateDeclaration) {
			writeTemplateDeclaration((ICPPASTTemplateDeclaration) declaration);
			addNewLine = false;
		} else if (declaration instanceof ICPPASTTemplateSpecialization) {
			writeTemplateSpecialization((ICPPASTTemplateSpecialization) declaration);
			addNewLine = false;
		} else if (declaration instanceof ICPPASTUsingDeclaration) {
			writeUsingDeclaration((ICPPASTUsingDeclaration) declaration);
		} else if (declaration instanceof ICPPASTUsingDirective) {
			writeUsingDirective((ICPPASTUsingDirective) declaration);
		} else if (declaration instanceof ICPPASTVisibilityLabel) {
			writeVisibilityLabel((ICPPASTVisibilityLabel) declaration);
		}
		
		if(hasTrailingComments(declaration)) {
			writeTrailingComments(declaration, addNewLine);
		}else if(addNewLine){
			scribe.newLine();
		}
		if(hasFreestandingComments(declaration)){
			writeFreeStandingComments(declaration);
		}

		if (declaration instanceof ICPPASTUsingDirective) {
			scribe.newLine();
		}
	}

	private void writeVisibilityLabel(ICPPASTVisibilityLabel visiblityLabel) {
		scribe.decrementIndentationLevel();
		switch (visiblityLabel.getVisibility()) {
		case ICPPASTVisibilityLabel.v_private:
			scribe.print(PRIVATE);
			scribe.print(':');
			break;
		case ICPPASTVisibilityLabel.v_protected:
			scribe.print(PROTECTED);
			scribe.print(':');
			break;
		case ICPPASTVisibilityLabel.v_public:
			scribe.print(PUBLIC);	
			scribe.print(':');
			break;
		default:
			return;
		}
		scribe.incrementIndentationLevel();
	}

	private void writeUsingDirective(ICPPASTUsingDirective usingDirective) {
		scribe.print(USING + NAMESPACE);
		usingDirective.getQualifiedName().accept(visitor);
		scribe.printSemicolon();
	}

	private void writeUsingDeclaration(ICPPASTUsingDeclaration usingDeclaration) {
		scribe.print(USING);
		if(usingDeclaration.isTypename()){
			scribe.print(TYPENAME);
		}
		usingDeclaration.getName().accept(visitor);		
		scribe.printSemicolon();
	}

	private void writeTemplateSpecialization(ICPPASTTemplateSpecialization templateSpecialization) {
		scribe.print(TEMPLATE_SPECIALIZATION);
		templateSpecialization.getDeclaration().accept(visitor);
	}

	protected void writeTemplateDeclaration(ICPPASTTemplateDeclaration templateDeclaration) {
		if(templateDeclaration.isExported()){
			scribe.print(EXPORT);
		}
		scribe.print(TEMPLATE_DECLARATION);
		ICPPASTTemplateParameter[] paraDecls = templateDeclaration.getTemplateParameters();
		for(int i = 0; i < paraDecls.length; ++i) {
			paraDecls[i].accept(visitor);
			if(i + 1 < paraDecls.length) {
				scribe.print(',');
				scribe.printSpaces(1);
			}
		}
		scribe.print('>');
		scribe.printSpace();
		templateDeclaration.getDeclaration().accept(visitor);
	}

	protected void writeDeclaration(ICPPASTNamespaceDefinition declaration){
		printSemicolon = true;
		writeNamespaceDefinition(declaration);
	}
	
	private void writeNamespaceDefinition(ICPPASTNamespaceDefinition namespaceDefinition) {
		scribe.print(NAMESPACE);
		namespaceDefinition.getName().accept(visitor);
		if(!hasTrailingComments(namespaceDefinition.getName())) {
			scribe.newLine();
		}
		scribe.printLBrace();
		scribe.newLine();
		writeDeclarationsInNamespace(namespaceDefinition, namespaceDefinition.getDeclarations());
		if(hasFreestandingComments(namespaceDefinition)) {
			writeFreeStandingComments(namespaceDefinition);
		}
		scribe.printRBrace();
		
		if(hasTrailingComments(namespaceDefinition)) {
			writeTrailingComments(namespaceDefinition);
		}else{
			scribe.newLine();
		}	
	}

	protected void writeDeclarationsInNamespace(ICPPASTNamespaceDefinition namespaceDefinition, IASTDeclaration[] declarations) {
		for (IASTDeclaration declaration : declarations) {
			declaration.accept(visitor);
		}
	}

	private void writeNamespaceAlias(ICPPASTNamespaceAlias namespaceAliasDefinition) {
		scribe.print(NAMESPACE);
		namespaceAliasDefinition.getAlias().accept(visitor);
		scribe.print(EQUALS);
		namespaceAliasDefinition.getMappingName().accept(visitor);
		printSemicolon();
	}

	private void writeLinkageSpecification(ICPPASTLinkageSpecification linkageSpecification) {
		scribe.print( EXTERN);
		scribe.print(linkageSpecification.getLiteral());
		scribe.printSpaces(1);
		
		IASTDeclaration[] declarations = linkageSpecification.getDeclarations();
		if(declarations.length > 1){
			scribe.printLBrace();
			scribe.decrementIndentationLevel();
			scribe.newLine();
			for (IASTDeclaration declaration : declarations) {
				declaration.accept(visitor);
			}
			scribe.printRBrace();
			scribe.incrementIndentationLevel();
		} else if(declarations.length > 0) {
			visitNodeIfNotNull(declarations[0]);
		}
	}

	private void writeExplicitTemplateInstantiation(ICPPASTExplicitTemplateInstantiation explicitTemplateInstantiation) {
		if (explicitTemplateInstantiation instanceof IGPPASTExplicitTemplateInstantiation) {
			IGPPASTExplicitTemplateInstantiation gppExplicitTemplateInstantiation = (IGPPASTExplicitTemplateInstantiation) explicitTemplateInstantiation;
			switch(gppExplicitTemplateInstantiation.getModifier()){
			case IGPPASTExplicitTemplateInstantiation.ti_extern:
				scribe.print(EXTERN);
				break;
			case IGPPASTExplicitTemplateInstantiation.ti_inline:
				scribe.print(INLINE);
				break;
			case IGPPASTExplicitTemplateInstantiation.ti_static:
				scribe.print(STATIC);
				break;
			}
		}
		
		scribe.print(TEMPLATE);
		explicitTemplateInstantiation.getDeclaration().accept(visitor);
	}

	private void writeASMDeclatation(IASTASMDeclaration asmDeclaration) {
		scribe.print(ASM_START);
		scribe.print(asmDeclaration.getAssembly());
		scribe.print(ASM_END);
		printSemicolon();
	}

	private void printSemicolon() {
		if(printSemicolon) {
			scribe.printSemicolon();
		}
	}

	private void writeFunctionDefinition(IASTFunctionDefinition funcDef) {
		IASTDeclSpecifier declSpecifier = funcDef.getDeclSpecifier();
		declSpecifier.accept(visitor);
		if (declSpecifier instanceof IASTSimpleDeclSpecifier) {
			IASTSimpleDeclSpecifier simDeclSpec = (IASTSimpleDeclSpecifier) declSpecifier;
			if(simDeclSpec.getType() != IASTSimpleDeclSpecifier.t_unspecified) {
				scribe.printSpace();
			}
		}else {
			scribe.printSpace();
		}
		IASTDeclarator declarator = CPPVisitor.findOutermostDeclarator(funcDef.getDeclarator());
		declarator.accept(visitor);
		scribe.newLine();
		funcDef.getBody().accept(visitor);
		if (declarator instanceof ICPPASTFunctionTryBlockDeclarator) {
			ICPPASTFunctionTryBlockDeclarator tryDeclSpec = (ICPPASTFunctionTryBlockDeclarator) declarator;
			ICPPASTCatchHandler[] catches = tryDeclSpec.getCatchHandlers();
			for (ICPPASTCatchHandler handler : catches) {
				handler.accept(visitor);
			}
		}
	}

	private void writeSimpleDeclaration(IASTSimpleDeclaration simpDec) {
		IASTDeclSpecifier declSpecifier = simpDec.getDeclSpecifier();
		IASTDeclarator[] decls = simpDec.getDeclarators();
						
		declSpecifier.accept(visitor);
		boolean noSpace = false;
		if (declSpecifier instanceof IASTSimpleDeclSpecifier) {
			IASTSimpleDeclSpecifier simpleDeclSpecifier = (IASTSimpleDeclSpecifier) declSpecifier;
			if(simpleDeclSpecifier.getType() == IASTSimpleDeclSpecifier.t_unspecified) {
				noSpace = true;
			}
		}
		
		if(decls.length > 0) {
			if(!noSpace) {
				scribe.printSpace();
			}
			writeNodeList(decls);
		}
		
		printSemicolon();
	}

}
