/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.impl;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.ranides.assira.annotations.Meta;
import net.ranides.assira.functional.special.AnyFunction;
import net.ranides.assira.reflection.*;
import net.ranides.test.mockup.reflection.ForConstructor;
import net.ranides.test.mockup.reflection.ForConstructor.Cities;
import net.ranides.test.mockup.reflection.ForConstructor.Letters;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;

import net.ranides.test.mockup.reflection.ForConstructor.SomeAnnotation;
import net.ranides.assira.reflection.util.AnnotationUtils;
import org.junit.Ignore;

/**
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class RConstructorTest {
    
    IClass<?> ic = IClass.typeinfo(ForConstructor.Record.class);
            
    @Test
    public void testParams() {
        String cn = "net.ranides.test.mockup.reflection.ForConstructor$Record";
        List<String> exp = Arrays.asList(
            MessageFormat.format("{0}(R param1, R param2)", cn),
            MessageFormat.format("{0}(int x, int y, int z)", cn),
            MessageFormat.format("{0}(java.lang.String param)", cn),
            MessageFormat.format("{0}()", cn)
        );
        assertEquivalent(exp, ic.constructors().list(m -> m.toString()));
        assertEquivalent(Arrays.asList("[R]", "[]", "[]", "[]"), ic.constructors().list(m -> m.params().toString()));
    }
    
    @Test
    public void testTypeInfo() throws Exception {
        IConstructor<?> c1 = ic.constructors().require(c -> c.arguments().count()==3).first().get();

        assertNotNull(c1.reflective());

        Constructor<ForConstructor.Record> rc1 = ForConstructor.Record.class.getConstructor(int.class, int.class, int.class);
        assertEquals(c1.reflective(), rc1);

        IConstructor<?> c2 = IConstructor.typeinfo(rc1);
        assertEquals(c1, c2);
    }
    
    @Test
    public void testApply() {
        ForConstructor.Record that = new ForConstructor.Record();

        IConstructor<?> c1 = ic.constructors().require(0).first().get();
        IConstructor<?> c2 = ic.constructors().require(2).first().get();
        IConstructor<?> c3 = ic.constructors().require(3).first().get();

        assertEquals("Record{0:0:0}", c1.invoke().toString());
        assertEquals("Record{0:0:0}", c1.invoke(null).toString());
        assertThrows(IReflectiveException.class, ()->{
            c1.invoke(that);
        });
        
        assertEquals("Record{5:6:11}", c2.invoke(null, 5.1, 6.4).toString());
        assertThrows(IReflectiveException.class, ()->{
            c2.invoke(that, 5.1, 6.4);
        });
        
        assertEquals("Record{1:2:3}", c3.invoke(null, 1, 2, 3).toString());
    }
    
    @Test
    public void testAnnotations() {
        IConstructor<?> c1 = ic.constructors().require(Meta.Unsafe.class).first().get();
        List<String> types = Arrays.asList(
            "@net.ranides.assira.annotations.Meta$Unsafe()", 
            "@net.ranides.test.mockup.reflection.ForConstructor$SomeAnnotation()"
        ); 
        List<String> names = Arrays.asList(
            "net.ranides.assira.annotations.Meta$Unsafe", 
            "net.ranides.test.mockup.reflection.ForConstructor$SomeAnnotation"
        );
        assertEquivalent(types, c1.annotations().list(a -> a.toString()));
        assertEquivalent(names, c1.annotations().names());

        SomeAnnotation dc = AnnotationUtils.make(SomeAnnotation.class);
        String exp = "[net.ranides.assira.annotations.Meta$Unsafe]";
        assertEquals(exp, c1.annotations().discard(SomeAnnotation.class).names().toString());
        assertEquals(exp, c1.annotations().discard(a -> a.equals(dc)).names().toString());
    }
    
    
    @Test
    public void testCall() {
        IConstructor<?> c1 = ic.constructors().require(0).first().get();
        IConstructor<?> c2 = ic.constructors().require(2).first().get();
        IConstructor<?> c3 = ic.constructors().require(3).first().get();

        assertEquals("Record{0:0:0}", c1.call().toString());
        
        assertEquals("Record{5:6:11}", c2.call(5.1, 6.4).toString());
        assertEquals("Record{1:2:3}", c3.call(1, 2, 3).toString());
    }
    
    @Test
    public void testHandle() throws Throwable {
        MethodHandle c1 = ic.constructors().require(0).first().get().handle();
        MethodHandle c2 = ic.constructors().require(2).first().get().handle();
        MethodHandle c3 = ic.constructors().require(3).first().get().handle();

        assertEquals("Record{0:0:0}", c1.invoke().toString());
        
        assertEquals("Record{5:6:11}", c2.invoke(5.1, 6.4).toString());
        assertEquals("Record{1:2:3}", c3.invoke(1, 2, 3).toString());
    }

    @Test
    public void testAccessor() {
        // we search for constructors of "enum" types
        // although it is quite strange behaviour, we do this intentionally because
        // it is most important use-case for #accessor method anyway
        //
        // accessor is useful if you want to bypass all sanity checks inside Class#newInstance
        // most of them can be disabled by calling AccessibleObject#setAccessible
        // but not all. it applies especially for enum.

        AnyFunction<Letters> aLetters = IClass.typeinfo(Letters.class).constructors()
            .require(IAttribute.DECLARED)
            .first()
            .orElseThrow(() -> new AssertionError("Constructor not found: Letters"))
            .accessor();

        AnyFunction<Cities> aCities = IClass.typeinfo(Cities.class).constructors()
            .require(IAttribute.DECLARED)
            .first()
            .orElseThrow(() -> new AssertionError("Constructor not found: Cities"))
            .accessor();

        Letters letter1 = aLetters.call("Z", 26);

        assertTrue(letter1 instanceof Letters);
        assertEquals("Z", letter1.name());
        assertEquals(26, letter1.ordinal());

        Cities city1 = aCities.call("ROME", 4, "Rome");

        assertTrue(city1 instanceof Cities);
        assertEquals("ROME", city1.name());
        assertEquals(4, city1.ordinal());
        assertEquals("Rome", city1.getLongName());
    }

    @Test
    public void testMatchGeneric() {
        IConstructors<?> list1 = ic.constructors().require(IArguments.typeinfo(Number.class, Number.class));
        IConstructors<?> list2 = ic.constructors().matches(IClasses.typeinfo(Number.class, Number.class));
        IConstructors<?> list3 = ic.constructors().accepts(0,0);

        List<? extends IConstructor<?>> exp1 = ic.constructors().require(SomeAnnotation.class).list();

        assertEquals(Collections.emptyList(), list1.list());
        assertEquals(exp1, list2.list());
        assertEquals(exp1, list3.list());
    }
    
}
