package net.ranides.assira.reflection;

import net.ranides.assira.collection.maps.OpenMap;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.junit.NewAssert;
import net.ranides.test.mockup.reflection.ForIClass;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;

public class IClassTest {

    @Test
    public void serialization() throws IOException {
        IClass<?> type1 = IClass.typeinfo(Lister.class);
        IClass<?> type2 = IClass.generic(List.class, String.class);

        assertNotNull(SerializationUtils.copy(type1.reflective()));

        assertNotNull(SerializationUtils.copy(type1));
        assertNotNull(SerializationUtils.copy(type2));
    }

    @Test
    public void genericSerialization() throws IOException {
        IClass<?> type2 = IClass.typeinfo(Lister.class).field("list").get().type();
        IClass<?> type3 = IClass.typeinfo(Lister.class).field("nested").get().type();

        IClass<?> copy2 = SerializationUtils.copy(type2);
        assertNotNull(copy2);
        assertEquals(type2.toString(), copy2.toString());
        assertEquals(ForIClass.FLOAT, type2.params().get(0));

        IClass<?> copy3 = SerializationUtils.copy(type3);
        assertNotNull(copy3);
        assertEquals(type3.toString(), copy3.toString());
        assertEquals(ForIClass.I_NUMBER, copy3.params().get(0).params().get(1));
    }

    @Test
    public void tokenSerialization() throws IOException {
        IClass<?> type4 = new TypeToken<List<String>>() {};

        IClass<?> copy4 = SerializationUtils.read(IClass.class, SerializationUtils.write(type4));
        assertNotNull(copy4);
        assertEquals(type4.toString(), copy4.toString());
        assertEquals(ForIClass.STRING, type4.params().get(0));
    }

    @Test
    public void packageName() {
        assertEquals("java.lang", IClass.typeinfo(Number.class).packageName());
        assertEquals("net.ranides.assira.reflection", IClass.typeinfo(IClass.class).packageName());
        assertEquals("net.ranides.assira.collection.maps", new TypeToken<OpenMap<String, Float>>() {}.packageName());
    }

    @Test
    public void noResolveParameters() {
        List<IClass<?>> params1 = IClass.typeinfo(Map.class).params();

        assertEquals(Object.class, params1.get(0).raw());
        assertEquals(Object.class, params1.get(1).raw());

        assertNotEquals(Object.class, params1.get(0).reflective());
        assertNotEquals(Object.class, params1.get(1).reflective());
    }

    @Test
    public void noResolveParameters2() {
        IClass<ArrayList> typeArray1 = IClass.typeinfo(ArrayList.class);
        IClass<ArrayList> typeArray2 = IClass.typefor(new ArrayList<String>(){});
        IClass<?> typeList1 = typeArray1.interfaces().require(v -> v.name().contains("List")).first().get();
        IClass<?> typeList2 = typeArray2.interfaces().require(v -> v.name().contains("List")).first().get();
        IClass<?> typeList3 = new TypeToken<List<Float>>() {};

        assertFalse(typeList1.isResolved());
        assertTrue(typeList2.isResolved());
        assertTrue(typeList3.isResolved());
    }

    @Test
    public void genericParser() {
        IClass<?> type1 = IClass.typeinfo(Map.class);
        IClass<?> type2 = new TypeToken<Map<String, Float>>() {};
        IClass<?> type3 = new TypeToken<Map<List<String>, Float>>() {};

        assertEquals(type1, IClass.typeinfo("java.util.Map"));
        assertEquals(type2, IClass.typeinfo("java.util.Map<java.lang.String, java.lang.Float>"));
        assertEquals(type3, IClass.typeinfo("java.util.Map<java.util.List<java.lang.String>, java.lang.Float>"));

        assertEquals(type1, IClass.typeinfo("java.util.Map<java.lang.String, java.lang.Float>"));
        assertNotEquals(type2, IClass.typeinfo("java.util.Map<java.lang.String, java.lang.Double>"));
        assertNotEquals(type3, IClass.typeinfo("java.util.Map<java.util.List<java.lang.Character>, java.lang.Float>"));
    }

    @Test
    public void interfaces() {
        IClass<?> type1 = IClass.typeinfo(ForIClass.MyNumbers.class);
        IClass<?> type2 = IClass.typeinfo(ForIClass.MyNumbersInterface.class);

        List<String> expected1 = Arrays.asList(
            "MyNumbersInterface",
            "MyList",
            "MyCollection",
            "MyIterable",
            "List",
            "Collection",
            "Iterable"
        );
        List<String> expected2 = Arrays.asList(
            "MyList",
            "MyCollection",
            "MyIterable"
        );

        NewAssert.assertEquivalent(expected1, type1.interfaces().list(IClass::shortName));
        NewAssert.assertEquivalent(expected2, type2.interfaces().list(IClass::shortName));
    }

    private static final class Lister {

        public List<Float> list;
        public List<Map<String, Number>> nested;

    }
}