/*******************************************************************************
 * Copyright (c) 2005, 2013 BEA Systems, Inc.
 *
 * 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:
 *     tyeung@bea.com - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.apt.tests;

import java.io.File;
import java.util.HashSet;

import junit.framework.Test;
import junit.framework.TestSuite;

import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.apt.tests.annotations.readannotation.CodeExample;
import org.eclipse.jdt.apt.tests.plugin.AptTestsPlugin;
import org.eclipse.jdt.core.BindingKey;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.tests.builder.BuilderTests;
import org.eclipse.jdt.core.tests.util.Util;

/**
 * This test the dom layer of annotation support. No APT involved.
 * @author tyeung
 */
public class ReadAnnotationTests2 extends BuilderTests {
	
	private String[] NO_ANNOTATIONS = new String[0];
	private ICompilationUnit[] NO_UNIT = new ICompilationUnit[0];
	private int counter = 0;
	private String projectName = null;
	public ReadAnnotationTests2(final String name) {
		super( name );
	}
	
	public static Test suite() {
		return new TestSuite( ReadAnnotationTests2.class );
	}
	
	public String getProjectName() {
		return projectName;
	}
	
	public String getUniqueProjectName(){
		projectName = ReadAnnotationTests.class.getName() + "Project" + counter; //$NON-NLS-1$
		counter ++;
		return projectName;
	}
	

	public IPath getSourcePath() {
		IProject project = env.getProject( getProjectName() );
		IFolder srcFolder = project.getFolder( "src" ); //$NON-NLS-1$
		IPath srcRoot = srcFolder.getFullPath();
		return srcRoot;
	}
	
	public IPath getBinaryPath(){
		IProject project = env.getProject( getProjectName() );
		IFolder srcFolder = project.getFolder( "binary" ); //$NON-NLS-1$
		IPath lib = srcFolder.getFullPath();
		return lib;
	}
	
	public IPath getOutputPath(){
		IProject project = env.getProject( getProjectName() );
		IFolder binFolder = project.getFolder( "bin" ); //$NON-NLS-1$
		IPath bin = binFolder.getFullPath();
		return bin;
	}
	
	private void addAllSources()
	{
		IPath srcRoot = getSourcePath();
		// SimpleAnnotation.java
		env.addClass( 
				srcRoot, 
				CodeExample.PACKAGE_QUESTION, 
				CodeExample.SIMPLE_ANNOTATION_CLASS,
				CodeExample.SIMPLE_ANNOTATION_CODE );
		
		// RTVisibleAnnotation.java
		env.addClass(
				srcRoot,
				CodeExample.PACKAGE_QUESTION, 
				CodeExample.RTVISIBLE_CLASS,
				CodeExample.RTVISIBLE_ANNOTATION_CODE);
		
		// RTInvisibleAnnotation.java
		env.addClass(
				srcRoot,
				CodeExample.PACKAGE_QUESTION, 
				CodeExample.RTINVISIBLE_CLASS,
				CodeExample.RTINVISIBLE_ANNOTATION_CODE);
		
		// package-info.java
		env.addClass(
				srcRoot,
				CodeExample.PACKAGE_QUESTION, 
				CodeExample.PACKAGE_INFO_CLASS,
				CodeExample.PACKAGE_INFO_CODE);
		
		// Color.java
		env.addClass(
				srcRoot,
				CodeExample.PACKAGE_QUESTION, 
				CodeExample.COLOR_CLASS,
				CodeExample.COLOR_CODE);
		
		// AnnotationTest.java
		env.addClass(
				srcRoot,
				CodeExample.PACKAGE_QUESTION, 
				CodeExample.ANNOTATION_TEST_CLASS,
				CodeExample.ANNOTATION_TEST_CODE);
	}
	
	private IProject setupTest() throws Exception
	{				
		// project will be deleted by super-class's tearDown() method
		IPath projectPath = env.addProject( getUniqueProjectName(), "1.5" ); //$NON-NLS-1$
		env.setOutputFolder(projectPath, "bin"); //$NON-NLS-1$ 
		env.addExternalJars( projectPath, Util.getJavaClassLibs() );
		
		IJavaProject jproj = env.getJavaProject(projectPath);
		jproj.setOption("org.eclipse.jdt.core.compiler.problem.deprecation", "ignore");

		// remove old package fragment root so that names don't collide
		env.removePackageFragmentRoot( projectPath, "" ); //$NON-NLS-1$

		env.addPackageFragmentRoot( projectPath, "src" ); //$NON-NLS-1$
		return env.getProject(getProjectName());
	}
	
	public void testSourceAnnotation() throws Exception {
		// reset the error reset the error;
		IProject project = setupTest();	
		addAllSources();	
		fullBuild( project.getFullPath() );
		expectingNoProblems();
		_testAnnotations();
	}

	public void testBinaryAnnotation() throws Exception 
	{	
		IProject project = setupTest();
		final File jar = 
			TestUtil.getFileInPlugin(AptTestsPlugin.getDefault(), 
									 new Path("/resources/question.jar")); //$NON-NLS-1$
		final String path = jar.getAbsolutePath();
		env.addExternalJar(project.getFullPath(), path);		
		fullBuild( project.getFullPath() );
		expectingNoProblems();
		_testAnnotations();		
	}
	
	private ITypeBinding getTypeBinding(final String key, final IJavaProject javaProj)
	{
		class BindingRequestor extends ASTRequestor
		{
			private ITypeBinding _result = null;
			public void acceptBinding(String bindingKey, IBinding binding)
			{
				if( binding != null && binding.getKind() == IBinding.TYPE )
					_result = (ITypeBinding)binding;
			}
		}

		final BindingRequestor requestor = new BindingRequestor();
		final ASTParser parser = ASTParser.newParser(AST.JLS10);
		parser.setResolveBindings(true);
		parser.setProject(javaProj);
		parser.setIgnoreMethodBodies(true);
		parser.createASTs(NO_UNIT, new String[]{key}, requestor, null);
		return requestor._result;
	}
	
	public void _testAnnotations()
	{	
		final String typeKey = BindingKey.createTypeBindingKey("question.AnnotationTest");
		final ITypeBinding typeBinding = getTypeBinding(typeKey, env.getJavaProject(getProjectName()));
			
		assertNotNull("failed to locate 'question.AnnotationTest'", typeBinding);
		assertEquals("Type name mismatch", "question.AnnotationTest", typeBinding.getQualifiedName());
		
		// test package annotation
		final String[] expectedPkgAnnos = new String[]{ "@Deprecated()" };
		assertAnnotation(expectedPkgAnnos, typeBinding.getPackage().getAnnotations() );
		
		// test annotation on type.
		final String[] expectedTypeAnnos = new String[]{ "@Deprecated()",
		  	     "@RTVisibleAnno(anno = @SimpleAnnotation(value = test), clazzes = {})",
			     "@RTInvisibleAnno(value = question)" };
		
		assertAnnotation(expectedTypeAnnos, typeBinding.getAnnotations());	
		
		final IVariableBinding[] fieldBindings = typeBinding.getDeclaredFields();
		int counter = 0;
		assertEquals(5, fieldBindings.length);
		for(IVariableBinding fieldDecl : fieldBindings ){
			final String name = "field" + counter;				
			
			assertEquals("field name mismatch", name, fieldDecl.getName());
			final String[] expected;
			switch(counter){				
			case 0:		
				expected = new String[] { "@RTVisibleAnno(name = Foundation, boolValue = false, byteValue = 16, charValue = c, doubleValue = 99.0, floatValue = 9.0, intValue = 999, longValue = 3333, shortValue = 3, colors = {question.Color RED, question.Color BLUE}, anno = @SimpleAnnotation(value = core), simpleAnnos = {@SimpleAnnotation(value = org), @SimpleAnnotation(value = eclipse), @SimpleAnnotation(value = jdt)}, clazzes = {Object.class, String.class}, clazz = Object.class)",
								          "@RTInvisibleAnno(value = org.eclipse.jdt.core)",
								          "@Deprecated()" };
				break;	
			case 1:
				expected = new String[] { "@Deprecated()" };
				break;
			case 2:
				expected = new String[] { "@RTVisibleAnno(anno = @SimpleAnnotation(value = field), clazzes = {})",
										  "@RTInvisibleAnno(value = 2)" };
				break;
			case 3:
				expected = new String[] { "@RTInvisibleAnno(value = 3)" };
				break;
			case 4:
				expected = new String[] { "@SimpleAnnotation(value = 4)" };
				break;
			default:
				expected = NO_ANNOTATIONS;
			}
			
			assertAnnotation(expected, fieldDecl.getAnnotations());
			counter ++;
		}	
		
		
		final IMethodBinding[] methodBindings = typeBinding.getDeclaredMethods();
		counter = 0;
		assertEquals(7, methodBindings.length);
		for(IMethodBinding methodDecl : methodBindings ){
			final String name = "method" + counter;				
			
			assertEquals("method name mismatch", name, methodDecl.getName());
			final String[] expected;
			switch(counter)
			{
			case 0:
				expected = new String[] { "@RTVisibleAnno(anno = @SimpleAnnotation(value = method0), clazzes = {})",
					                      "@RTInvisibleAnno(value = 0)",
					                      "@Deprecated()" };				
				break;
			case 1:
				expected = new String[] { "@Deprecated()" };
				break;
			case 2:
				expected = new String[] { "@RTVisibleAnno(anno = @SimpleAnnotation(value = method2), clazzes = {})",
										  "@RTInvisibleAnno(value = 2)" };
				break;
			case 3:
				expected = new String[] { "@RTInvisibleAnno(value = 3)" };
				break;
			case 4:
				expected = new String[] { "@SimpleAnnotation(value = method4)" };
				break;
			case 5:
			case 6:
			default:
				expected = NO_ANNOTATIONS;
			}
			
			assertAnnotation(expected, methodDecl.getAnnotations());
			
			if( counter == 5 ){
				final int numParameters = methodDecl.getParameterTypes().length;
				for( int pCounter=0; pCounter<numParameters; pCounter++ ){
					final String[] expectedParamAnnotations;
					switch( pCounter )
					{
					case 1:
						expectedParamAnnotations = new String[] { "@Deprecated()" };
						break;
					case 2:							
						expectedParamAnnotations = new String[] { "@RTVisibleAnno(anno = @SimpleAnnotation(value = param2), clazzes = {})",
															      "@RTInvisibleAnno(value = 2)" };							
						break;						
					default:
						expectedParamAnnotations = NO_ANNOTATIONS;						
					}
					assertAnnotation(expectedParamAnnotations, methodDecl.getParameterAnnotations(pCounter));
				}
				
			}
			counter ++;
		}
	}
	
	private void assertAnnotation(final String[] expected, IAnnotationBinding[] annotations)
	{
		final int expectedLen = expected.length;		
		assertEquals("annotation number mismatch", expected.length, annotations.length); //$NON-NLS-1$
		
		final HashSet<String> expectedSet = new HashSet<String>(expectedLen * 4 / 3 + 1);
		for( int i=0; i<expectedLen; i++ )
			expectedSet.add(expected[i]);
			
		int counter = 0;
		for( IAnnotationBinding mirror : annotations ){
			if( counter >= expectedLen )
				assertEquals("", mirror.toString()); //$NON-NLS-1$
			else{
				final String mirrorToString = mirror.toString();
				final boolean contains = expectedSet.contains(mirrorToString);
				if( !contains ){					
					System.err.println(mirrorToString);
					System.err.println(expectedSet);
				}
				assertTrue("unexpected annotation " + mirrorToString, contains); //$NON-NLS-1$
				expectedSet.remove(mirrorToString);
			}
			counter ++;
		}
	}
	
 
}
