/*******************************************************************************
 * Copyright (c) 2008, 2016 Institute for Software, HSR Hochschule fuer Technik
 * Rapperswil, University of applied sciences and others
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * 	   Institute for Software - initial API and implementation
 *     Sergey Prigogin (Google)
 *     Thomas Corbat (IFS)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.rewrite.astwriter;

import java.util.EnumSet;

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.IASTElaboratedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier.IASTEnumerator;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTNamedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.c.ICASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.c.ICASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.c.ICASTElaboratedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.c.ICASTEnumerationSpecifier;
import org.eclipse.cdt.core.dom.ast.c.ICASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.c.ICASTTypedefNameSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTElaboratedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTEnumerationSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.parser.GCCKeywords;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap;

/**
 * Generates source code of declaration specifier nodes. The actual string operations are delegated
 * to the <code>Scribe</code> class.
 *
 * @see Scribe
 * @see IASTDeclSpecifier
 * @author Emanuel Graf IFS
 */
public class DeclSpecWriter extends NodeWriter {
	private boolean constRight = false;

	public DeclSpecWriter(Scribe scribe, ASTWriterVisitor visitor, NodeCommentMap commentMap) {
		super(scribe, visitor, commentMap);
	}

	public void setPlaceConstRight(boolean placeConstRight) {
		constRight = placeConstRight;
	}

	protected void writeDelcSpec(IASTDeclSpecifier declSpec) {
		writeDeclSpec(declSpec, !constRight);
		if (declSpec instanceof ICPPASTDeclSpecifier) {
			writeCPPDeclSpec((ICPPASTDeclSpecifier) declSpec);
		} else if (declSpec instanceof ICASTDeclSpecifier) {
			writeCDeclSpec((ICASTDeclSpecifier) declSpec);
		}
		if (constRight && declSpec.isConst()) {
			scribe.printSpace();
			scribe.printStringSpace(Keywords.CONST);
		}
	}

	private String getCPPSimpleDecSpecifier(ICPPASTSimpleDeclSpecifier simpDeclSpec) {
		return getASTSimpleDecSpecifier(simpDeclSpec.getType(), true);
	}

	private String getCSimpleDecSpecifier(ICASTSimpleDeclSpecifier simpDeclSpec) {
		return getASTSimpleDecSpecifier(simpDeclSpec.getType(), false);
	}

	private String getASTSimpleDecSpecifier(int type, boolean isCpp) {
		switch (type) {
		case IASTSimpleDeclSpecifier.t_unspecified:
			return ""; //$NON-NLS-1$
		case IASTSimpleDeclSpecifier.t_void:
			return Keywords.VOID;
		case IASTSimpleDeclSpecifier.t_char:
			return Keywords.CHAR;
		case IASTSimpleDeclSpecifier.t_int:
			return Keywords.INT;
		case IASTSimpleDeclSpecifier.t_int128:
			return GCCKeywords.__INT128;

		case IASTSimpleDeclSpecifier.t_float:
			return Keywords.FLOAT;
		case IASTSimpleDeclSpecifier.t_double:
			return Keywords.DOUBLE;
		case IASTSimpleDeclSpecifier.t_float128:
			return GCCKeywords.__FLOAT128;
		case IASTSimpleDeclSpecifier.t_decimal32:
			return GCCKeywords._DECIMAL32;
		case IASTSimpleDeclSpecifier.t_decimal64:
			return GCCKeywords._DECIMAL64;
		case IASTSimpleDeclSpecifier.t_decimal128:
			return GCCKeywords._DECIMAL128;

		case IASTSimpleDeclSpecifier.t_bool:
			return isCpp ? Keywords.BOOL : Keywords._BOOL;

		case IASTSimpleDeclSpecifier.t_wchar_t:
			if (isCpp)
				return Keywords.WCHAR_T;
			break;
		case IASTSimpleDeclSpecifier.t_char16_t:
			if (isCpp)
				return Keywords.CHAR16_T;
			break;
		case IASTSimpleDeclSpecifier.t_char32_t:
			if (isCpp)
				return Keywords.CHAR32_T;
			break;
		case IASTSimpleDeclSpecifier.t_auto:
			if (isCpp)
				return Keywords.AUTO;
			break;
		case IASTSimpleDeclSpecifier.t_typeof:
			if (isCpp)
				return Keywords.TYPEOF;
			break;
		case IASTSimpleDeclSpecifier.t_decltype:
		case IASTSimpleDeclSpecifier.t_decltype_auto:
			if (isCpp)
				return Keywords.DECLTYPE;
			break;
		}

		throw new IllegalArgumentException("Unknown specifier type: " + type); //$NON-NLS-1$
	}

	private void writeCDeclSpec(ICASTDeclSpecifier cDeclSpec) {
		if (cDeclSpec.isRestrict()) {
			scribe.printStringSpace(Keywords.RESTRICT);
		}

		if (cDeclSpec instanceof ICASTCompositeTypeSpecifier) {
			writeCompositeTypeSpecifier((ICASTCompositeTypeSpecifier) cDeclSpec);
		} else if (cDeclSpec instanceof ICASTEnumerationSpecifier) {
			writeEnumSpec((ICASTEnumerationSpecifier) cDeclSpec);
		} else if (cDeclSpec instanceof ICASTElaboratedTypeSpecifier) {
			writeElaboratedTypeSec((ICASTElaboratedTypeSpecifier) cDeclSpec);
		} else if (cDeclSpec instanceof ICASTSimpleDeclSpecifier) {
			writeCSimpleDeclSpec((ICASTSimpleDeclSpecifier) cDeclSpec);
		} else if (cDeclSpec instanceof ICASTTypedefNameSpecifier) {
			writeNamedTypeSpecifier((ICASTTypedefNameSpecifier) cDeclSpec);
		}
	}

	private void writeNamedTypeSpecifier(ICPPASTNamedTypeSpecifier namedSpc) {
		if (namedSpc.isTypename()) {
			scribe.printStringSpace(Keywords.TYPENAME);
		}
		namedSpc.getName().accept(visitor);
	}

	private void writeNamedTypeSpecifier(IASTNamedTypeSpecifier namedSpc) {
		namedSpc.getName().accept(visitor);
	}

	private void writeElaboratedTypeSec(IASTElaboratedTypeSpecifier elabType) {
		scribe.printStringSpace(getElabTypeString(elabType.getKind()));
		writeAttributes(elabType, EnumSet.of(SpaceLocation.AFTER));
		elabType.getName().accept(visitor);
	}

	private String getElabTypeString(int kind) {
		switch (kind) {
		case IASTElaboratedTypeSpecifier.k_enum:
			return Keywords.ENUM;
		case IASTElaboratedTypeSpecifier.k_struct:
			return Keywords.STRUCT;
		case IASTElaboratedTypeSpecifier.k_union:
			return Keywords.UNION;
		case ICPPASTElaboratedTypeSpecifier.k_class:
			return Keywords.CLASS;

		default:
			throw new IllegalArgumentException("Unknown elaborated type: " + kind); //$NON-NLS-1$
		}
	}

	private void writeCPPDeclSpec(ICPPASTDeclSpecifier cppDelcSpec) {
		if (cppDelcSpec.isVirtual()) {
			scribe.printStringSpace(Keywords.VIRTUAL);
		}
		if (cppDelcSpec.isConstexpr()) {
			scribe.printStringSpace(Keywords.CONSTEXPR);
		}
		if (cppDelcSpec.isExplicit()) {
			scribe.printStringSpace(Keywords.EXPLICIT);
		}
		if (cppDelcSpec.isFriend()) {
			scribe.printStringSpace(Keywords.FRIEND);
		}
		if (cppDelcSpec.isThreadLocal()) {
			scribe.printStringSpace(Keywords.THREAD_LOCAL);
		}
		if (cppDelcSpec.getStorageClass() == IASTDeclSpecifier.sc_mutable) {
			scribe.printStringSpace(Keywords.MUTABLE);
		}

		if (cppDelcSpec instanceof ICPPASTCompositeTypeSpecifier) {
			writeCompositeTypeSpecifier((ICPPASTCompositeTypeSpecifier) cppDelcSpec);
		} else if (cppDelcSpec instanceof IASTEnumerationSpecifier) {
			writeEnumSpec((IASTEnumerationSpecifier) cppDelcSpec);
		} else if (cppDelcSpec instanceof ICPPASTElaboratedTypeSpecifier) {
			writeElaboratedTypeSec((ICPPASTElaboratedTypeSpecifier) cppDelcSpec);
		} else if (cppDelcSpec instanceof ICPPASTSimpleDeclSpecifier) {
			writeCPPSimpleDeclSpec((ICPPASTSimpleDeclSpecifier) cppDelcSpec);
		} else if (cppDelcSpec instanceof ICPPASTNamedTypeSpecifier) {
			writeNamedTypeSpecifier((ICPPASTNamedTypeSpecifier) cppDelcSpec);
		}
	}

	private void writeEnumSpec(IASTEnumerationSpecifier enumSpec) {
		scribe.printStringSpace(Keywords.ENUM);
		boolean isCppEnum = enumSpec instanceof ICPPASTEnumerationSpecifier;
		if (isCppEnum) {
			ICPPASTEnumerationSpecifier cppEnumSpec = (ICPPASTEnumerationSpecifier) enumSpec;
			if (cppEnumSpec.isScoped()) {
				switch (cppEnumSpec.getScopeStyle()) {
				case CLASS:
					scribe.printStringSpace(Keywords.CLASS);
					break;
				case STRUCT:
					scribe.printStringSpace(Keywords.STRUCT);
					break;
				default:
					break;
				}
			}
			writeAttributes(cppEnumSpec, EnumSet.of(SpaceLocation.AFTER));
		}
		enumSpec.getName().accept(visitor);
		if (isCppEnum) {
			ICPPASTDeclSpecifier baseType = ((ICPPASTEnumerationSpecifier) enumSpec).getBaseType();
			if (baseType != null) {
				scribe.print(SPACE_COLON_SPACE);
				writeDelcSpec(baseType);
				scribe.printSpace();
			}
		}
		scribe.print('{');
		scribe.printSpace();
		IASTEnumerator[] enums = enumSpec.getEnumerators();
		for (int i = 0; i < enums.length; ++i) {
			writeEnumerator(enums[i]);
			if (i + 1 < enums.length) {
				scribe.print(NodeWriter.COMMA_SPACE);
			}
		}
		scribe.print('}');
	}

	private void writeEnumerator(IASTEnumerator enumerator) {
		enumerator.getName().accept(visitor);
		writeAttributes(enumerator, EnumSet.of(SpaceLocation.BEFORE));
		IASTExpression value = enumerator.getValue();
		if (value != null) {
			scribe.print(EQUALS);
			value.accept(visitor);
		}
	}

	private void writeCompositeTypeSpecifier(IASTCompositeTypeSpecifier compDeclSpec) {
		boolean hasTrailingComments = hasTrailingComments(compDeclSpec.getName());
		scribe.printStringSpace(getCPPCompositeTypeString(compDeclSpec.getKey()));
		writeAttributes(compDeclSpec, EnumSet.of(SpaceLocation.AFTER));
		compDeclSpec.getName().accept(visitor);
		if (compDeclSpec instanceof ICPPASTCompositeTypeSpecifier) {
			ICPPASTCompositeTypeSpecifier cppComp = (ICPPASTCompositeTypeSpecifier) compDeclSpec;
			if (cppComp.isFinal()) {
				scribe.printSpace();
				scribe.print(Keywords.cFINAL);
			}
			ICPPASTBaseSpecifier[] baseSpecifiers = cppComp.getBaseSpecifiers();
			if (baseSpecifiers.length > 0) {
				scribe.print(SPACE_COLON_SPACE);
				for (int i = 0; i < baseSpecifiers.length; ++i) {
					writeBaseSpecifiers(baseSpecifiers[i]);
					if (i + 1 < baseSpecifiers.length) {
						scribe.print(COMMA_SPACE);
					}
				}
				hasTrailingComments = hasTrailingComments(baseSpecifiers[baseSpecifiers.length - 1].getNameSpecifier());
			}
		}
		if (!hasTrailingComments) {
			scribe.newLine();
		}
		scribe.print('{');
		scribe.newLine();
		scribe.incrementIndentationLevel();
		visitor.setSuppressLeadingBlankLine(true);
		IASTDeclaration[] decls = getMembers(compDeclSpec);

		if (decls.length > 0) {
			for (IASTDeclaration declaration : decls) {
				declaration.accept(visitor);
			}
		}

		if (hasFreestandingComments(compDeclSpec)) {
			writeFreestandingComments(compDeclSpec);
		}
		scribe.decrementIndentationLevel();
		scribe.print('}');

		if (hasTrailingComments(compDeclSpec)) {
			writeTrailingComments(compDeclSpec);
		}
	}

	protected IASTDeclaration[] getMembers(IASTCompositeTypeSpecifier compDeclSpec) {
		return compDeclSpec.getMembers();
	}

	public void writeBaseSpecifiers(ICPPASTBaseSpecifier specifier) {
		switch (specifier.getVisibility()) {
		case ICPPASTBaseSpecifier.v_public:
			scribe.printStringSpace(Keywords.PUBLIC);
			break;
		case ICPPASTBaseSpecifier.v_protected:
			scribe.printStringSpace(Keywords.PROTECTED);
			break;
		case ICPPASTBaseSpecifier.v_private:
			scribe.printStringSpace(Keywords.PRIVATE);
			break;
		}

		if (specifier.isVirtual()) {
			scribe.printStringSpace(Keywords.VIRTUAL);
		}
		specifier.getNameSpecifier().accept(visitor);
	}

	private String getCPPCompositeTypeString(int key) {
		if (key <= IASTCompositeTypeSpecifier.k_last) {
			return getCompositeTypeString(key);
		}
		switch (key) {
		case ICPPASTCompositeTypeSpecifier.k_class:
			return Keywords.CLASS;
		default:
			throw new IllegalArgumentException("Unknown type specifier: " + key); //$NON-NLS-1$
		}
	}

	private String getCompositeTypeString(int key) {
		switch (key) {
		case IASTCompositeTypeSpecifier.k_struct:
			return Keywords.STRUCT;
		case IASTCompositeTypeSpecifier.k_union:
			return Keywords.UNION;
		default:
			throw new IllegalArgumentException("Unknown type specifier: " + key); //$NON-NLS-1$
		}
	}

	private void writeDeclSpec(IASTDeclSpecifier declSpec, boolean constEnabled) {
		if (declSpec.isInline()) {
			scribe.printStringSpace(Keywords.INLINE);
		}
		switch (declSpec.getStorageClass()) {
		case IASTDeclSpecifier.sc_typedef:
			scribe.printStringSpace(Keywords.TYPEDEF);
			break;
		case IASTDeclSpecifier.sc_extern:
			scribe.printStringSpace(Keywords.EXTERN);
			break;
		case IASTDeclSpecifier.sc_static:
			scribe.printStringSpace(Keywords.STATIC);
			break;
		case IASTDeclSpecifier.sc_auto:
			scribe.printStringSpace(Keywords.AUTO);
			break;
		case IASTDeclSpecifier.sc_register:
			scribe.printStringSpace(Keywords.REGISTER);
			break;
		}
		if (declSpec.isConst() && constEnabled) {
			scribe.printStringSpace(Keywords.CONST);
		}
		if (declSpec.isVolatile()) {
			scribe.printStringSpace(Keywords.VOLATILE);
		}
	}

	private void writeCPPSimpleDeclSpec(ICPPASTSimpleDeclSpecifier simpDeclSpec) {
		printQualifiers(simpDeclSpec);
		scribe.print(getCPPSimpleDecSpecifier(simpDeclSpec));
		writeAttributes(simpDeclSpec, EnumSet.of(SpaceLocation.BEFORE));
		if (simpDeclSpec.getType() == IASTSimpleDeclSpecifier.t_typeof) {
			scribe.printSpace();
			visitNodeIfNotNull(simpDeclSpec.getDeclTypeExpression());
		} else if (simpDeclSpec.getType() == IASTSimpleDeclSpecifier.t_decltype) {
			scribe.print('(');
			visitNodeIfNotNull(simpDeclSpec.getDeclTypeExpression());
			scribe.print(')');
		} else if (simpDeclSpec.getType() == IASTSimpleDeclSpecifier.t_decltype_auto) {
			scribe.print('(');
			scribe.print(Keywords.AUTO);
			scribe.print(')');
		}
	}

	private void printQualifiers(IASTSimpleDeclSpecifier simpDeclSpec) {
		if (simpDeclSpec.isSigned()) {
			scribe.printStringSpace(Keywords.SIGNED);
		} else if (simpDeclSpec.isUnsigned()) {
			scribe.printStringSpace(Keywords.UNSIGNED);
		}

		if (simpDeclSpec.isShort()) {
			scribe.printStringSpace(Keywords.SHORT);
		} else if (simpDeclSpec.isLong()) {
			scribe.printStringSpace(Keywords.LONG);
		} else if (simpDeclSpec.isLongLong()) {
			scribe.printStringSpace(Keywords.LONG);
			scribe.printStringSpace(Keywords.LONG);
		}
		if (simpDeclSpec instanceof ICASTSimpleDeclSpecifier) {
			ICASTSimpleDeclSpecifier cSimpDeclSpec = (ICASTSimpleDeclSpecifier) simpDeclSpec;
			if (cSimpDeclSpec.isComplex()) {
				scribe.printStringSpace(Keywords._COMPLEX);
			}
		}
	}

	private void writeCSimpleDeclSpec(ICASTSimpleDeclSpecifier simpDeclSpec) {
		printQualifiers(simpDeclSpec);
		scribe.print(getCSimpleDecSpecifier(simpDeclSpec));
	}
}
