package info.ebenoit.ebul.reflection;


import static org.junit.Assert.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.junit.Assert;
import org.junit.Test;



/**
 * Tests for the {@link MemberFinder} class
 *
 * @author <a href="mailto:ebenoit@ebenoit.info">E. BenoƮt</a>
 */
public class TestMemberFinder
{

	/** Test: using {@link MemberFinder} to list all declared methods in a class */
	@Test
	public void testFindAllMethods( )
	{
		MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , false );

		ArrayList< Method > output = new ArrayList< >( );
		mf.find( output , Object.class );

		ArrayList< Method > expected = new ArrayList< >( Arrays.asList( Object.class.getDeclaredMethods( ) ) );
		assertEquals( expected , output );
	}


	/** Test: using {@link MemberFinder} to list all declared methods in a class and its parent */
	@Test
	public void testFindAllMethodsParent( )
	{
		MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , false );

		ArrayList< Method > output = new ArrayList< >( );
		mf.find( output , CTest1.class );

		ArrayList< Method > expected = new ArrayList< >( Arrays.asList( CTest1.class.getDeclaredMethods( ) ) );
		expected.addAll( Arrays.asList( Object.class.getDeclaredMethods( ) ) );
		assertEquals( expected , output );
	}


	/** Test: using {@link MemberFinder} to list all declared fields in a class */
	@Test
	public void testFindAllFields( )
	{
		MemberFinder< Field > mf = new MemberFinder< >( MemberFinder.FIELD_EXTRACTOR , false );

		ArrayList< Field > output = new ArrayList< >( );
		mf.find( output , Object.class );

		ArrayList< Field > expected = new ArrayList< >( Arrays.asList( Object.class.getDeclaredFields( ) ) );
		assertEquals( expected , output );
	}


	/** Test: using {@link MemberFinder} to list all declared fields in a class and its parent */
	@Test
	public void testFindAllFieldsParent( )
	{
		MemberFinder< Field > mf = new MemberFinder< >( MemberFinder.FIELD_EXTRACTOR , false );

		ArrayList< Field > output = new ArrayList< >( );
		mf.find( output , CTest1.class );

		ArrayList< Field > expected = new ArrayList< >( Arrays.asList( CTest1.class.getDeclaredFields( ) ) );
		expected.addAll( Arrays.asList( Object.class.getDeclaredFields( ) ) );
		assertEquals( expected , output );
	}


	/** Test: {@link MemberFinder} class filtering */
	@Test
	public void testClassFiltering( )
	{
		MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , false );
		mf.setClassFilter( ( Class< ? > c ) -> c != Object.class );

		ArrayList< Method > output = new ArrayList< >( );
		mf.find( output , CTest1.class );

		ArrayList< Method > expected = new ArrayList< >( Arrays.asList( CTest1.class.getDeclaredMethods( ) ) );
		assertEquals( expected , output );
	}


	/** Test: {@link MemberFinder} member filtering */
	@Test
	public void testMemberFiltering( )
	{
		MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , false );
		mf.setMemberFilter( ( Method m ) -> m.isAnnotationPresent( ATest1.class ) );

		ArrayList< Method > output = new ArrayList< >( );
		mf.find( output , CTest1.class );

		final List< String > mNames = output.stream( )//
				.map( ( final Method m ) -> m.getName( ) )//
				.collect( Collectors.toList( ) );
		mNames.sort( ( final String a , final String b ) -> a.compareTo( b ) );
		Assert.assertArrayEquals( new String[] {
				"tdm1" , "tim1" , "tm1" , "tpm1"
		} , mNames.toArray( new String[] { } ) );
	}


	/** Test: {@link MemberFinder} support for interfaces (order-independent) */
	@Test
	public void testInterfaces( )
	{
		MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , true );

		ArrayList< Method > output = new ArrayList< >( );
		mf.find( output , CTest5.class );

		final List< String > result = output.stream( )//
				.map( ( final Method m ) -> m.getDeclaringClass( ).getName( ) + "." + m.getName( ) )//
				.collect( Collectors.toList( ) );
		result.sort( ( final String a , final String b ) -> a.compareTo( b ) );

		final ArrayList< Method > allMethods = new ArrayList< >( Arrays.asList( Object.class.getDeclaredMethods( ) ) );
		allMethods.addAll( Arrays.asList( CTest5.class.getDeclaredMethods( ) ) );
		allMethods.addAll( Arrays.asList( ITest1.class.getDeclaredMethods( ) ) );

		final List< String > expected = allMethods.stream( )//
				.map( ( final Method m ) -> m.getDeclaringClass( ).getName( ) + "." + m.getName( ) )//
				.collect( Collectors.toList( ) );
		expected.sort( ( final String a , final String b ) -> a.compareTo( b ) );

		assertEquals( expected , result );
	}


	/** Test: {@link MemberFinder} support for interfaces: processing before superclasses */
	@Test
	public void testInterfacesBeforeClasses( )
	{
		MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , true );
		mf.setClassesFirst( false );

		ArrayList< Method > output = new ArrayList< >( );
		mf.find( output , CTest5.class );

		final ArrayList< Method > expected = new ArrayList< >( Arrays.asList( CTest5.class.getDeclaredMethods( ) ) );
		expected.addAll( Arrays.asList( ITest1.class.getDeclaredMethods( ) ) );
		expected.addAll( Arrays.asList( Object.class.getDeclaredMethods( ) ) );

		assertEquals( expected , output );
	}


	/** Test: {@link MemberFinder} support for interfaces: processing after superclasses */
	@Test
	public void testInterfacesAfterClasses( )
	{
		MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , true );
		mf.setClassesFirst( true );

		ArrayList< Method > output = new ArrayList< >( );
		mf.find( output , CTest5.class );

		final ArrayList< Method > expected = new ArrayList< >( Arrays.asList( CTest5.class.getDeclaredMethods( ) ) );
		expected.addAll( Arrays.asList( Object.class.getDeclaredMethods( ) ) );
		expected.addAll( Arrays.asList( ITest1.class.getDeclaredMethods( ) ) );

		assertEquals( expected , output );
	}

}