package net.ranides.assira.generic;

import lombok.RequiredArgsConstructor;
import net.ranides.assira.annotations.Meta;
import net.ranides.assira.junit.NewAssert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static org.junit.Assert.*;

public class DynamicInvokerTest {

    @Test
    public void basic() {

        DynamicInvoker di = DynamicInvoker.builder()
                .append(List.class, String.class, v -> "List")
                .append(ArrayList.class, String.class, v -> "ArrayList")
                .append(List.class, int.class, v -> v.size())
                .append(ArrayList.class, int.class, v -> 1000 + v.size())
                .build();

        assertNotNull(di.call(Arrays.asList("a","b","c")));
        assertNotNull(di.call(new ArrayList<>(Arrays.asList("a","b","c"))));

        assertEquals("List", di.call(String.class, Arrays.asList("a","b","c")));
        assertEquals("ArrayList", di.call(String.class, new ArrayList<>(Arrays.asList("a","b","c"))));

        assertEquals((Integer)3, di.call(int.class, Arrays.asList("a","b","c")));
        assertEquals((Integer)1003, di.call(int.class, new ArrayList<>(Arrays.asList("a","b","c"))));

    }

    @Test
    public void reflective() {

        DynamicInvoker di = DynamicInvoker.builder()
                .appendDelegate(new InvokeHelper(2000))
                .build();

        assertNotNull(di.call(Arrays.asList("a","b","c")));
        assertNotNull(di.call(new ArrayList<>(Arrays.asList("a","b","c"))));

        assertEquals("List", di.call(String.class, Arrays.asList("a","b","c")));
        assertEquals("ArrayList", di.call(String.class, new ArrayList<>(Arrays.asList("a","b","c"))));

        assertEquals((Integer)3, di.call(int.class, Arrays.asList("a","b","c")));
        assertEquals((Integer)2003, di.call(int.class, new ArrayList<>(Arrays.asList("a","b","c"))));

    }

    @Test
    public void boxing() {

        DynamicInvoker di = DynamicInvoker.builder()
                .appendDelegate(new InvokeHelper(0))
                .build();

        assertEquals("7+7=14", di.call(7));
        assertEquals("7+7=14", di.call(String.class, 7));
    }

    @Test
    public void specials() {

        DynamicInvoker di1 = DynamicInvoker.builder().appendDelegate(new InvokeHelper(0)).passNull(false).passIdent(false).build();
        DynamicInvoker di2 = DynamicInvoker.builder().appendDelegate(new InvokeHelper(0)).passNull(true).passIdent(false).build();
        DynamicInvoker di3 = DynamicInvoker.builder().appendDelegate(new InvokeHelper(0)).passNull(false).passIdent(true).build();

        NewAssert.assertThrows(IllegalArgumentException.class, () -> {
            di1.call(Long.class, null);
        });
        NewAssert.assertThrows(IllegalArgumentException.class, () -> {
            di1.call(Long.class, 45L);
        });

        assertNull(di2.call(Long.class, null));
        NewAssert.assertThrows(IllegalArgumentException.class, () -> {
            di2.call(Long.class, 45L);
        });

        NewAssert.assertThrows(IllegalArgumentException.class, () -> {
            di3.call(Long.class, null);
        });
        assertEquals((Long)45L, di3.call(Long.class, 45L));
    }

    @Test
    public void generic() {

        DynamicInvoker di = DynamicInvoker.builder()
                .appendDelegate(new InvokeHelper(2000))
                .build();

        assertNotNull(di.call(new TypeToken<List<?>>() {}, "10,20,30"));
        assertNotNull(di.call(List.class, "10,20,30"));

        assertEquals(Arrays.asList("~10", "~20", "~30"), di.call(new TypeToken<List<String>>() {}, "10,20,30"));
        assertEquals(Arrays.asList(10, 20, 30), di.call(new TypeToken<List<Integer>>() {}, "10,20,30"));
    }

    @RequiredArgsConstructor
    public static class InvokeHelper {

        private final int v;

        @Meta.DynamicDispatch
        protected static String asText(List<?> values) {
            return "List";
        }

        @Meta.DynamicDispatch
        protected String asText(ArrayList<?> values) {
            return "ArrayList";
        }

        @Meta.DynamicDispatch
        private static int asInt(List<?> values) {
            return values.size();
        }

        @Meta.DynamicDispatch
        private int asInt(ArrayList<?> values) {
            return v + values.size();
        }

        @Meta.DynamicDispatch
        private static List<String> asTexts(String value) {
            return Arrays.stream(value.split(",")).map(v -> "~"+v).collect(Collectors.toList());
        }

        @Meta.DynamicDispatch
        private static List<Integer> asInts(String value) {
            return Arrays.stream(value.split(",")).map(Integer::parseInt).collect(Collectors.toList());
        }

        @Meta.DynamicDispatch
        private static String add(int add) {
            return add+"+"+add+"="+(add+add);
        }

    }
}