/*******************************************************************************
 * Copyright (c) 2014 Institute for Software, HSR Hochschule fuer Technik
 * Rapperswil, University of applied sciences.
 * 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:
 *     Thomas Corbat (IFS) - Initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.core.parser.tests.ast2;

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

import junit.framework.TestSuite;

import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTArrayModifier;
import org.eclipse.cdt.core.dom.ast.IASTAttribute;
import org.eclipse.cdt.core.dom.ast.IASTAttributeOwner;
import org.eclipse.cdt.core.dom.ast.IASTAttributeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTCaseStatement;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTDefaultStatement;
import org.eclipse.cdt.core.dom.ast.IASTExpressionStatement;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTLabelStatement;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTPointerOperator;
import org.eclipse.cdt.core.dom.ast.IASTReturnStatement;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTToken;
import org.eclipse.cdt.core.dom.ast.IASTTokenList;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTAliasDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTAttribute;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclarator;
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.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTIfStatement;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTReferenceOperator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTryBlockStatement;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDirective;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTWhileStatement;
import org.eclipse.cdt.core.parser.IToken;
import org.eclipse.cdt.core.parser.ParserLanguage;
import org.eclipse.cdt.internal.core.dom.parser.ASTNode;
import org.eclipse.cdt.internal.core.dom.parser.ASTTokenList;

public class AST2CPPAttributeTests extends AST2TestBase {

	public AST2CPPAttributeTests() {
	}

	public AST2CPPAttributeTests(String name) {
		super(name);
	}

	public static TestSuite suite() {
		return suite(AST2CPPAttributeTests.class);
	}

	private IASTTranslationUnit parseAndCheckBindings() throws Exception {
		String code = getAboveComment();
		return parseAndCheckBindings(code, ParserLanguage.CPP);
	}

	private final class TokenPositionCheckVisitor extends ASTVisitor {
		{
			shouldVisitTokens = true;
		}
		int offset;
		String[] tokenImages;
		int index = 0;

		private TokenPositionCheckVisitor(int startOffset, String[] tokenImages) {
			this.offset = startOffset;
			this.tokenImages = tokenImages;
		}

		@Override
		public int visit(IASTToken token) {
			if (token instanceof ASTTokenList) {
				return ASTVisitor.PROCESS_CONTINUE;
			}
			IToken location;
			if (token instanceof ASTNode) {
				ASTNode node = (ASTNode) token;
				assertEquals(offset, node.getOffset());
				assertEquals(tokenImages[index++], String.valueOf(token.getTokenCharImage()));
				offset += node.getLength();
			}
			return ASTVisitor.PROCESS_CONTINUE;
		}
	}

	private class AttributeNodeFinder extends ASTVisitor {
		{
			shouldVisitAttributes = true;
		}

		private List<IASTAttributeSpecifier> specifiers = new ArrayList<IASTAttributeSpecifier>();

		public List<IASTAttributeSpecifier> getAttributes() {
			return specifiers;
		}

		public int visit(IASTAttributeSpecifier specifier) {
			specifiers.add(specifier);
			return PROCESS_CONTINUE;
		}
	}

	private List<IASTAttributeSpecifier> getAttributeSpecifiers(IASTTranslationUnit tu) {
		AttributeNodeFinder attributeFinder = new AttributeNodeFinder();
		tu.accept(attributeFinder);
		List<IASTAttributeSpecifier> specifiers = attributeFinder.getAttributes();
		return specifiers;
	}

	private IASTAttribute[] getAttributes() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		List<IASTAttributeSpecifier> specifiers = getAttributeSpecifiers(tu);
		assertEquals(1, specifiers.size());
		IASTAttributeSpecifier specifier = specifiers.get(0);
		return specifier.getAttributes();
	}

	private void checkAttributeRelations(List<IASTAttributeSpecifier> specifiers, Class<? extends IASTAttributeOwner>... parentType) {
		assertEquals(parentType.length, specifiers.size());
		for (int i = 0; i < specifiers.size(); i++) {
			IASTAttributeSpecifier specifier = specifiers.get(i);
			IASTNode attributeParent = specifier.getParent();
			IASTAttributeOwner owner = assertInstance(attributeParent, parentType[i]);
			IASTAttributeSpecifier[] ownerAttributes = owner.getAttributeSpecifiers();
			assertSame(specifier, ownerAttributes[0]);
		}
	}

	//	auto t = []() mutable throw(char const *) [[attr]] { throw "exception"; };
	public void testAttributedLambda() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTFunctionDeclarator.class);
	}

	//	int * arr = new int[1][[attr]]{2};
	public void testAttributedNewArrayExpression() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTArrayModifier.class);
	}

	//	int (* matrix) = new int[2][[attr1]][2][[attr2]];
	public void testAttributedMultidimensionalNewArrayExpression() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		List<IASTAttributeSpecifier> specifiers = getAttributeSpecifiers(tu);
		checkAttributeRelations(specifiers, IASTArrayModifier.class, IASTArrayModifier.class);
		IASTAttributeSpecifier arrayModifierAttribute1 = specifiers.get(0);
		IASTNode arrayModifier1 = arrayModifierAttribute1.getParent();
		IASTAttributeSpecifier arrayModifierAttribute2 = specifiers.get(1);
		IASTNode arrayModifier2 = arrayModifierAttribute2.getParent();
		assertNotSame(arrayModifier1, arrayModifier2);
	}

	//	void foo() {
	//	  [[attr]] label:;
	//	}
	public void testAttributeInLabeledStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTLabelStatement.class);
	}

	//	void foo(int i) {
	//	  switch(i) {
	//	    [[case_attr]] case 42:
	//	    [[default_attr]] default:
	//	    ;
	//	  }
	//	}
	public void testAttributedSwitchLabels() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		List<IASTAttributeSpecifier> specifiers = getAttributeSpecifiers(tu);
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTCaseStatement.class, IASTDefaultStatement.class);
	}

	//	void foo() {
	//	  int i{0};
	//	  [[attr]] i++;
	//	}
	public void testAttributedExpressionStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTExpressionStatement.class);
	}

	//	void foo() {
	//	  [[attr]] {}
	//	}
	public void testAttributedCompoundStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTCompoundStatement.class);
	}

	//	void foo() {
	//	  [[attr]] if(false);
	//	}
	public void testAttributedSelectionStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTIfStatement.class);
	}

	//	void foo() {
	//	  [[attr]] while(false);
	//	}
	public void testAttributedIterationStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTWhileStatement.class);
	}

	//	void foo() {
	//	  [[attr]] return;
	// }
	public void testAttributedJumpStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTReturnStatement.class);
	}

	//	void foo() {
	//	  [[attr]] try{} catch(...) {}
	//	}
	public void testAttributedTryBlockStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTTryBlockStatement.class);
	}

	//	void foo() {
	//	  if([[attr]]int i{0});
	//	}
	public void testAttributedConditionWithInitializer() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTSimpleDeclaration.class);
	}

	//	void foo() {
	//	  int a[1]{0};
	//	  for([[attr]]auto i : a){}
	//	}
	public void testAttributedForRangeDeclaration() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTSimpleDeclaration.class);
	}

	//	using number [[attr]] = int;
	public void testAttributedAliasDeclaration() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTAliasDeclaration.class);
	}

	//	enum [[attr]] e {};
	public void testAttributedEnumDeclaration() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTEnumerationSpecifier.class);
	}

	//	namespace NS{}
	//	[[attr]] using namespace NS;
	public void testAttributedUsingDirective() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTUsingDirective.class);
	}

	//	void foo() throw(char const *) [[noreturn]] -> void {
	//	  throw "exception";
	//	}
	public void testAttributedFunction() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTFunctionDeclarator.class);
	}

	//	class [[attr]] C{};
	public void testAttributedClass() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTCompositeTypeSpecifier.class);
	}

	//	void f() { try { } catch ([[attr]] int& id) {} }
	public void testAttributedExceptionDeclaration() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTSimpleDeclaration.class);
	}

	//	struct [[attr]] S;
	public void testAttributedElaboratedTypeSpecifier() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTElaboratedTypeSpecifier.class);
	}

	//	static int [[int_attr]] v;
	public void testAttributedDeclSpecifier() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTSimpleDeclSpecifier.class);
	}

	//	const volatile unsigned long int [[attr]] cvuli;
	public void testAttributedTypeSpecifier() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTSimpleDeclSpecifier.class);
	}

	//	int * [[pointer_attribute]] * [[pointer_attribute]] ipp;
	public void testAttributedPtrOperators() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		List<IASTAttributeSpecifier> specifiers = getAttributeSpecifiers(tu);
		checkAttributeRelations(specifiers, IASTPointerOperator.class, IASTPointerOperator.class);
		IASTAttributeSpecifier pointerAttribute1 = specifiers.get(0);
		IASTNode pointer1 = pointerAttribute1.getParent();
		IASTAttributeSpecifier pointerAttribute2 = specifiers.get(1);
		IASTNode pointer2 = pointerAttribute2.getParent();
		assertNotSame(pointer1, pointer2);
	}

	//	int & [[ref_attribute]] iRef;
	public void testAttributedRefOperator() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTReferenceOperator.class);
	}

	//	int && [[rvalue_ref_attribute]] iRvalueRef;
	public void testAttributedRvalueRefOperator() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTReferenceOperator.class);
	}

	//	void foo() [[function_attr]];
	public void testAttributedFunctionDeclaration() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTFunctionDeclarator.class);
	}

	//	int ipp [[declarator_attr]];
	public void testAttributedDeclarator() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTDeclarator.class);
	}

	//	int iArr[5] [[arr_attr]];
	public void testAttributedArrayDeclarator() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTArrayModifier.class);
	}

	//	[[attr]] int i;
	public void testAttributedSimpleDeclaration() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTSimpleDeclaration.class);
	}

	//	[[attr]] void bar(){}
	public void testAttributedFunctionDefinition() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTFunctionDefinition.class);
	}

	//	struct S {
	//	  [[ctor_attr]] S() = delete;
	//	};
	public void testDeletedCtor() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTFunctionDefinition.class);
	}

	//	struct S {
	//	  [[dtor_attr]] ~S() = default;
	//	};
	public void testDefaultedDtor() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTFunctionDefinition.class);
	}

	//	void bar() {
	//	  [[attr]] int i;
	//	}
	public void testAttributedSimpleDeclarationInStatement() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTSimpleDeclaration.class);
	}

	//	[[]] int i;
	public void testEmptyAttributeSpecifier() throws Exception {
		IASTAttribute[] attributes = getAttributes();
		assertEquals(IASTAttribute.EMPTY_ATTRIBUTE_ARRAY, attributes);
	}

	//	[[attr]] [[attr2]] [[attr3]] int i;
	public void testMultipleSequentialAttributeSpecifiers() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings();
		List<IASTAttributeSpecifier> specifiers = getAttributeSpecifiers(tu);
		assertEquals(3, specifiers.size());
		IASTAttributeSpecifier simpleDeclarationAttribute1 = specifiers.get(0);
		IASTNode parent1 = simpleDeclarationAttribute1.getParent();
		assertInstance(parent1, IASTSimpleDeclaration.class);
		IASTAttributeSpecifier simpleDeclarationAttribute2 = specifiers.get(1);
		IASTNode parent2 = simpleDeclarationAttribute2.getParent();
		assertInstance(parent2, IASTSimpleDeclaration.class);
		IASTAttributeSpecifier simpleDeclarationAttribute3 = specifiers.get(2);
		IASTNode parent3 = simpleDeclarationAttribute3.getParent();
		assertInstance(parent3, IASTSimpleDeclaration.class);
		assertSame(parent1, parent2);
		assertSame(parent1, parent3);
	}

	//	[[attr1, attr2]] int i;
	public void testMultipleAttributes() throws Exception {
		IASTAttribute[] attributes = getAttributes();
		assertEquals(2, attributes.length);
		IASTAttribute attr1 = attributes[0];
		assertEquals("attr1", String.valueOf(attr1.getName()));
		IASTAttribute attr2 = attributes[1];
		assertEquals("attr2", String.valueOf(attr2.getName()));
	}

	//	[[attribute ...]] int i;
	public void testPackExpansionAttribute() throws Exception {
		IASTAttribute[] attributes = getAttributes();
		assertEquals(1, attributes.length);
		IASTAttribute attribute = attributes[0];
		assertInstance(attribute, ICPPASTAttribute.class);
		assertTrue(((ICPPASTAttribute) attribute).hasPackExpansion());
	}

	//	[[scope::attribute]] int i;
	public void testScopedAttribute() throws Exception {
		IASTAttribute[] attributes = getAttributes();
		assertEquals(1, attributes.length);
		IASTAttribute scopedAttribute = attributes[0];
		assertInstance(scopedAttribute, ICPPASTAttribute.class);
		assertEquals("scope", String.valueOf(((ICPPASTAttribute) scopedAttribute).getScope()));
		assertEquals("attribute", String.valueOf(scopedAttribute.getName()));
	}

	//	[[attr()]] int i;
	public void testAttributeWithEmptyArgument() throws Exception {
		IASTAttribute[] attributes = getAttributes();
		assertEquals(1, attributes.length);
		IASTAttribute attribute = attributes[0];
		IASTToken argument = attribute.getArgumentClause();
		IASTTokenList tokenList = assertInstance(argument, IASTTokenList.class);
		assertEquals(IASTToken.EMPTY_TOKEN_ARRAY, tokenList.getTokens());
	}

	//	[[attr(this(is){[my]}(argument[with]{some},parentheses))]] int i;
	public void testAttributeWithBalancedArgument() throws Exception {
		IASTAttribute[] attributes = getAttributes();
		assertEquals(1, attributes.length);
		IASTAttribute attribute = attributes[0];
		IASTToken argumentClause = attribute.getArgumentClause();
		final int startOffset = 8;
		final String[] tokenImages = new String[] { "this", "(", "is", ")", "{", "[",
				"my", "]", "}", "(", "argument", "[", "with", "]", "{", "some",
				"}", ",", "parentheses", ")"};
		argumentClause.accept(new TokenPositionCheckVisitor(startOffset, tokenImages));
	}

	//	[[attr(class)]] int i;
	public void testAttributeWithKeywordArgument() throws Exception {
		IASTAttribute[] attributes = getAttributes();
		assertEquals(1, attributes.length);
		IASTAttribute attribute = attributes[0];
		IASTToken argument = attribute.getArgumentClause();
		IASTTokenList tokenList = assertInstance(argument, IASTTokenList.class);
		IASTToken[] argumentTokens = tokenList.getTokens();
		assertEquals(1, argumentTokens.length);
		IASTToken classToken = argumentTokens[0];
		assertEquals("class", String.valueOf(classToken.getTokenCharImage()));
	}

	//	struct S __attribute__((__packed__)) {};
	public void testGCCAttributedStruct() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings(getAboveComment(), ParserLanguage.CPP, true);
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTCompositeTypeSpecifier.class);
	}

	//	int a __attribute__ ((aligned ((64))));
	public void testGCCAttributedVariableDeclarator_bug391572() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings(getAboveComment(), ParserLanguage.CPP, true);
		checkAttributeRelations(getAttributeSpecifiers(tu), IASTDeclarator.class);
	}

	//	struct S {
	//	  void foo() override __attribute__((attr));
	//	};
	public void testGCCAttributeAfterOverride_bug413615() throws Exception {
		IASTTranslationUnit tu = parseAndCheckBindings(getAboveComment(), ParserLanguage.CPP, true);
		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTFunctionDeclarator.class);
	}
}
