package net.ranides.assira.reflection;

import net.ranides.assira.collection.maps.OpenMap;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.test.adapter.example.InnerDefs;
import net.ranides.test.adapter.model0.Image0;
import net.ranides.test.adapter.model0.Screen0;
import net.ranides.test.adapter.model1.Screen1;
import net.ranides.test.adapter.model1.Screen1P;
import net.ranides.test.adapter.model2.Image2;
import net.ranides.test.adapter.model2.Image2class;
import net.ranides.test.adapter.model2.Image2error;
import net.ranides.test.adapter.model2.Screen2;
import net.ranides.test.adapter.model3.Image3;
import net.ranides.test.adapter.model3.Screen3;
import net.ranides.test.adapter.example.LongInterface;
import net.ranides.test.adapter.example.LongMock;
import org.junit.Test;

import java.io.IOException;
import java.util.Arrays;

import static org.junit.Assert.*;

public class AdapterFactoryTest {


    @Test
    public void serialization() throws IOException {
        Screen0 screen0 = new Screen0(640, 480);

        Screen1 screen1 = AdapterFactory.getSystem().cast(Screen1.class, screen0);

        assertEquals("640x480 false", screen1.width()+"x"+screen1.height()+" "+screen1.isWide());

        screen1.put("first",4,5);
        screen1.put(new Image0("second",7,8));


        Screen1 screen2 = SerializationUtils.copy(screen1);

        assertEquals("first", screen2.findId(4,5));
        assertEquals("second", screen2.findId(7,8));

        screen1.put(new Image0("old",3,2));
        screen2.put(new Image0("new",3,2));

        assertEquals("old", screen1.findId(3,2));
        assertEquals("new", screen2.findId(3,2));
    }

    @Test
    public void castM1P() {
        Screen0 screen0 = new Screen0(640, 480);

        Screen1P screen1 = AdapterFactory.getSystem().cast(Screen1P.class, screen0);
        Screen1P screen12 = AdapterFactory.getSystem().cast(Screen1P.class, screen0);
        Screen1P screen13 = AdapterFactory.getSystem().cast(Screen1P.class, screen0);

        assertEquals("640x480 false", screen1.width()+"x"+screen1.height()+" "+screen1.isWide());

        assertEquals(new Image0("first", 4, 5), screen1.put("first",4,5));
        assertEquals(new Image0("second", 7, 8), screen1.put(new Image0("second",7,8)));

        screen1.add("far",420,150);
        screen1.add(new Image0("last",2,3));

        assertEquals("first", screen1.findId(4,5));
        assertEquals("far", screen1.findId(420,150));
    }

    @Test
    public void castM1() {
        Screen0 screen0 = new Screen0(640, 480);

        Screen1 screen1 = AdapterFactory.getSystem().cast(Screen1.class, screen0);

        assertEquals("640x480 false", screen1.width()+"x"+screen1.height()+" "+screen1.isWide());

        assertEquals(new Image0("first", 4, 5), screen1.put("first",4,5));
        assertEquals(new Image0("second", 7, 8), screen1.put(new Image0("second",7,8)));

        screen1.add("far",420,150);
        screen1.add("last",2,3);

        assertEquals("first", screen1.findId(4,5));
        assertEquals("far", screen1.findId(420,150));

        assertEquals(new Image0("second", 7, 8), screen1.findImage(7,8));
        assertEquals(new Image0("last", 2, 3), screen1.findImage(2,3));

        assertTrue(screen1.contains(screen1.findImage(2,3)));

        assertEquals("Screen0{960x720}", screen1.resize(1.5f).toString());
    }

    @Test
    public void castM2() {
        Screen0 screen0 = new Screen0(640, 480);

        Screen2 screen2 = AdapterFactory.getSystem().cast(Screen2.class, screen0);

        assertEquals("640x480 false", screen2.width()+"x"+screen2.height()+" "+screen2.isWide());

        assertEquals(new Image0("first", 4, 5), screen2.put("first",4,5));
        assertEquals(new Image0("second", 7, 8), screen2.put(new Image2class("second",7,8)));

        try {
            assertEquals(new Image0("second", 7, 8), screen2.put(new Image2error()));
            fail("ClassCastException expected");
        } catch(ClassCastException ex) {
            assertTrue(true);
        }

        screen2.add("far",420,150);
        screen2.add(new Image2("last",2,3));

        assertEquals("first", screen2.findId(4,5));
        assertEquals("far", screen2.findId(420,150));
    }

    @Test
    public void castM3() {
        Screen3 buffer = AdapterFactory.getSystem().cast(Screen3.class, new Screen0(32,32));
        Screen3 screen = AdapterFactory.getSystem().cast(Screen3.class, new Screen0(640, 480));

        assertEquals("640x480 false", screen.width()+"x"+screen.height()+" "+screen.isWide());

        Image3 a = screen.put("first",4,5);
        Image3 b = buffer.put("buffd",7,9);
        Image3 c = buffer.put("foreign",21,22);

        assertTrue(a instanceof Image3);

        assertEquals(4, a.getX());

        screen.add("far",420,150);
        screen.add(c);

        assertEquals("first", screen.findId(4,5));
        assertEquals("far", screen.findId(420,150));

        assertTrue(screen.contains(a));
        assertFalse(screen.contains(b));

        assertFalse(buffer.contains(a));
        assertTrue(buffer.contains(b));

        assertEquals("Adapter{4:5:first => Image3}", screen.findImage(4,5).toString());
        assertEquals("Adapter{21:22:foreign => Image3}", screen.findImage(21,22).toString());
    }

    @Test
    public void castExample() {
        InnerDefs.MutableRecord source = new InnerDefs.MutableRecord(7,3);

        InnerDefs.IRecord3 target3 = AdapterFactory.getSystem().cast(InnerDefs.IRecord3.class, source);

        assertEquals(7, target3.getX());
        assertEquals(3, target3.getY());
        assertEquals(21, target3.area());
        assertEquals(14, target3.resize(2).getX());
        assertEquals("false:true", target3.stats().isThin() + ":" +  target3.stats().isWide());
        assertEquals("375", target3.args1(new int[]{3}, new Float[]{7.0f}, 5));
        assertEquals("15", target3.complex(Arrays.asList("1","2"), new OpenMap<String, Number>(){{ put("a", 14); }}));
        assertEquals(56, source.resize(4).getX());
        assertEquals(1344, target3.area());

        InnerDefs.IRecord1 target1 = AdapterFactory.getSystem().cast(InnerDefs.IRecord1.class, source);
        assertEquals(56, target1.getX());
        assertEquals(24, target1.getY());
        assertEquals(1344, target1.area());

        InnerDefs.IRecord2 target2 = AdapterFactory.getSystem().cast(InnerDefs.IRecord2.class, source);
        assertEquals(56, target2.getX());
        assertEquals(24, target2.getY());
        assertEquals(1344, target2.area());
        assertEquals("Internal{168:72}", target2.resize(3).toString());
        assertEquals("Adapter{Internal{168:72} => IRecord2}", target2.toString());
    }

    @Test
    public void testUnsafe() {
        LongMock mock = new LongMock();
        LongInterface adapter = AdapterFactory.getSystem().cast(IClass.typeinfo(LongInterface.class), mock);

        adapter.putNumbers("name", 1200L, "-", -5L);
        adapter.putNumbers(70L);
        assertEquals(33L, adapter.parseMe("33"));

        assertEquals(Arrays.asList("putNumbers name:1200:-:-5", "putNumbers auto:70:-:70", "parseMe 33"), mock.calls);
    }
}