/*
 * Copyright (c) 2008, Rickard Öberg. All Rights Reserved.
 * Copyright (c) 2012, Paul Merlin.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.qi4j.api.util;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.qi4j.functional.Iterables;
import org.qi4j.functional.Specification;
import org.qi4j.functional.Specifications;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.qi4j.api.util.Classes.interfacesOf;
import static org.qi4j.api.util.Classes.interfacesWithMethods;
import static org.qi4j.functional.Iterables.count;

/**
 * Tests for Classes
 */
public class ClassesTest
{

    @Test
    public void givenClassWithInterfacesWhenInterfacesOfThenGetCorrectSet()
    {
        assertThat( "one interface returned", count( interfacesOf( A.class ) ), equalTo( 1L ) );
        assertThat( "two interface returned", count( interfacesOf( B.class ) ), equalTo( 2L ) );
        assertThat( "tree interface returned", count( interfacesOf( C.class ) ), equalTo( 4L ) );
    }

    @Test
    public void givenClassWithInterfacesWhenGetInterfacesWithMethodsThenGetCorrectSet()
    {
        HashSet<Class<?>> interfaces = new HashSet<Class<?>>();
        interfaces.add( B.class );
        Set<Class<?>> types = interfacesWithMethods( interfaces );
        assertThat( "one interface returned", types.size(), equalTo( 1 ) );
        assertThat( "correct interface returned", types.contains( B.class ), is( true ) );
    }

    @Test
    public void givenClassesWithInterfacesWhenGetInterfacesWithMethodsThenGetCorrectSet()
    {
        Iterable<Type> types = Iterables.filter( Methods.HAS_METHODS, interfacesOf( C.class ) );
        assertThat( "one interface returned", count( types ), equalTo( 1L ) );
        assertThat( "correct interface returned", Iterables.matchesAny( (Specification) Specifications.in( B.class ), Iterables
            .<Class<?>>cast( types ) ), is( true ) );
    }

    @Test
    public void givenClassNameWhenToUriThenUriIsReturned()
    {
        assertThat( "URI is correct", Classes.toURI( A.class ), equalTo( "urn:qi4j:type:org.qi4j.api.util.ClassesTest-A" ) );
    }

    @Test
    public void givenUriWhenToClassNameThenClassNameIsReturned()
    {
        assertThat( "Class name is correct", Classes.toClassName( "urn:qi4j:type:org.qi4j.api.util.ClassesTest-A" ), equalTo( "org.qi4j.api.util.ClassesTest$A" ) );
    }

    @Test
    public void givenGenericTypeWithWildCardWhenGetRawClassThenCorrectTypeIsReturned()
        throws NoSuchMethodException
    {
        Type returnType = Generics.class.getMethod( "wildcard" ).getGenericReturnType();
        Type wildcardType = ( (ParameterizedType) returnType ).getActualTypeArguments()[ 0];
        assertThat( "Return type is A", Classes.RAW_CLASS.map( wildcardType ), equalTo( (Class) A.class ) );
    }

    @Test
    public void givenTypeVariableWhenResolveThenResolved()
    {
        for( Method method : Type1.class.getMethods() )
        {
            Type type = method.getGenericReturnType();
            TypeVariable typeVariable = (TypeVariable) type;
            Type resolvedType = Classes.resolveTypeVariable( typeVariable, method.getDeclaringClass(), Type1.class );
            System.out.println( type + "=" + resolvedType );
            switch( method.getName() )
            {
                case "type":
                    assertThat( resolvedType, equalTo( (Type) String.class ) );
                    break;
                case "type1":
                    assertThat( resolvedType, equalTo( (Type) String.class ) );
                    break;
                case "type2":
                    assertThat( resolvedType, equalTo( (Type) Long.class ) );
                    break;
            }
        }
    }

    @Test
    public void givenGenericTypeWhenGetSimpleGenericNameThenCorrectStringIsReturned()
        throws NoSuchMethodException
    {
        assertThat( "Simple Generic Name is 'A'",
                    Classes.simpleGenericNameOf( A.class ),
                    equalTo( "A" ) );
        assertThat( "Simple Generic Name is 'B'",
                    Classes.simpleGenericNameOf( B.class ),
                    equalTo( "B" ) );
        assertThat( "Simple Generic Name is 'C'",
                    Classes.simpleGenericNameOf( C.class ),
                    equalTo( "C" ) );

        assertThat( "Simple Generic Name is 'Generics'",
                    Classes.simpleGenericNameOf( Generics.class ),
                    equalTo( "Generics" ) );
        assertThat( "Simple Generic Name is 'Iterable<? extends A>'",
                    Classes.simpleGenericNameOf( Generics.class.getMethod( "wildcard" ).getGenericReturnType() ),
                    equalTo( "Iterable<? extends A>" ) );

        assertThat( "Simple Generic Name is 'Type1'",
                    Classes.simpleGenericNameOf( Type1.class ),
                    equalTo( "Type1" ) );
        assertThat( "Simple Generic Name is 'TYPE'",
                    Classes.simpleGenericNameOf( Type1.class.getMethod( "type" ).getGenericReturnType() ),
                    equalTo( "TYPE" ) );
        assertThat( "Simple Generic Name is 'TYPE1'",
                    Classes.simpleGenericNameOf( Type1.class.getMethod( "type1" ).getGenericReturnType() ),
                    equalTo( "TYPE1" ) );
        assertThat( "Simple Generic Name is 'TYPE2'",
                    Classes.simpleGenericNameOf( Type1.class.getMethod( "type2" ).getGenericReturnType() ),
                    equalTo( "TYPE2" ) );

        assertThat( "Simple Generic Name is 'Type2'",
                    Classes.simpleGenericNameOf( Type2.class ),
                    equalTo( "Type2" ) );
        assertThat( "Simple Generic Name is 'TYPE'",
                    Classes.simpleGenericNameOf( Type2.class.getMethod( "type" ).getGenericReturnType() ),
                    equalTo( "TYPE" ) );
        assertThat( "Simple Generic Name is 'TYPE1'",
                    Classes.simpleGenericNameOf( Type2.class.getMethod( "type1" ).getGenericReturnType() ),
                    equalTo( "TYPE1" ) );
        assertThat( "Simple Generic Name is 'TYPE2'",
                    Classes.simpleGenericNameOf( Type2.class.getMethod( "type2" ).getGenericReturnType() ),
                    equalTo( "TYPE2" ) );

        assertThat( "Simple Generic Name is 'Type3'",
                    Classes.simpleGenericNameOf( Type3.class ),
                    equalTo( "Type3" ) );
        assertThat( "Simple Generic Name is 'TYPE'",
                    Classes.simpleGenericNameOf( Type3.class.getMethod( "type" ).getGenericReturnType() ),
                    equalTo( "TYPE" ) );
    }

    interface A
    {
    }

    interface B
        extends A
    {

        public void doStuff();

    }

    interface C
        extends A, B
    {
    }

    interface Generics
    {

        Iterable<? extends A> wildcard();

    }

    interface Type1
        extends Type2<String, Long>
    {
    }

    interface Type2<TYPE1, TYPE2>
        extends Type3<TYPE1>
    {

        TYPE1 type1();

        TYPE2 type2();

    }

    interface Type3<TYPE>
    {

        TYPE type();

    }

}
