package com.github.rschmitt.dynamicobject;


import org.junit.Before;
import org.junit.Test;

import static com.github.rschmitt.dynamicobject.DynamicObject.*;
import static org.junit.Assert.*;

public class RecursionTest {
    @Before
    public void setup() {
        try {
            DynamicObject.deregisterTag(LinkedList.class);
        } catch (NullPointerException ignore) { }
    }

    @Test
    public void recursion() {
        LinkedList tail = newInstance(LinkedList.class).value(3);
        LinkedList middle = newInstance(LinkedList.class).value(2).next(tail);
        LinkedList head = newInstance(LinkedList.class).value(1).next(middle);

        roundTrip(tail);
        roundTrip(middle);
        roundTrip(head);

        assertEquals(1, head.value());
        assertEquals(2, head.next().value());
        assertEquals(3, head.next().next().value());
        assertNull(head.next().next().next());

        assertEquals("{:next {:next {:value 3}, :value 2}, :value 1}", serialize(head));
        assertEquals("{:next {:value 3}, :value 2}", serialize(middle));
        assertEquals("{:value 3}", serialize(tail));
    }

    @Test
    public void taggedRecursion() {
        DynamicObject.registerTag(LinkedList.class, "LinkedList");

        LinkedList tail = newInstance(LinkedList.class).value(3);
        LinkedList middle = newInstance(LinkedList.class).value(2).next(tail);
        LinkedList head = newInstance(LinkedList.class).value(1).next(middle);

        roundTrip(tail);
        roundTrip(middle);
        roundTrip(head);
        assertEquals("#LinkedList{:value 3}", serialize(tail));
        assertEquals("#LinkedList{:next #LinkedList{:value 3}, :value 2}", serialize(middle));
        assertEquals("#LinkedList{:next #LinkedList{:next #LinkedList{:value 3}, :value 2}, :value 1}", serialize(head));
    }

    @Test
    public void registeringTheTagAddsItToSerializedOutput() {
        LinkedList tail = newInstance(LinkedList.class).value(3);
        LinkedList middle = newInstance(LinkedList.class).value(2).next(tail);
        LinkedList head = newInstance(LinkedList.class).value(1).next(middle);

        DynamicObject.registerTag(LinkedList.class, "LinkedList");

        roundTrip(tail);
        roundTrip(middle);
        roundTrip(head);
        assertEquals("#LinkedList{:value 3}", serialize(tail));
        assertEquals("#LinkedList{:next #LinkedList{:value 3}, :value 2}", serialize(middle));
        assertEquals("#LinkedList{:next #LinkedList{:next #LinkedList{:value 3}, :value 2}, :value 1}", serialize(head));
    }

    @Test
    public void deregisteringTheTagRemovesItFromSerializedOutput() {
        DynamicObject.registerTag(LinkedList.class, "LinkedList");

        LinkedList tail = newInstance(LinkedList.class).value(3);
        LinkedList middle = newInstance(LinkedList.class).value(2).next(tail);
        LinkedList head = newInstance(LinkedList.class).value(1).next(middle);

        DynamicObject.deregisterTag(LinkedList.class);

        roundTrip(tail);
        roundTrip(middle);
        roundTrip(head);

        assertEquals("{:next {:next {:value 3}, :value 2}, :value 1}", serialize(head));
        assertEquals("{:next {:value 3}, :value 2}", serialize(middle));
        assertEquals("{:value 3}", serialize(tail));
    }

    @Test
    public void registeringTheTagDoesNotAffectEqualityOfDeserializedInstances() {
        LinkedList obj1 = DynamicObject.deserialize("{:value 1, :next {:value 2, :next {:value 3}}}", LinkedList.class);
        DynamicObject.registerTag(LinkedList.class, "LinkedList");
        LinkedList obj2 = DynamicObject.deserialize("#LinkedList{:value 1, :next #LinkedList{:value 2, :next #LinkedList{:value 3}}}", LinkedList.class);
        DynamicObject.deregisterTag(LinkedList.class);

        LinkedList next = obj1.next().next();
        LinkedList next2 = obj1.next().next();
        assertEquals(next, next2);
        assertTrue(next.equals(next2));
        assertTrue(obj1.equals(obj2));
        assertEquals(obj1.next(), obj2.next());
        assertEquals(obj1, obj2);
        assertEquals(DynamicObject.serialize(obj1), DynamicObject.serialize(obj2));
    }

    private void roundTrip(LinkedList linkedList) {
        assertEquals(linkedList, deserialize(serialize(linkedList), LinkedList.class));
    }
}

interface LinkedList extends DynamicObject<LinkedList> {
    long value();
    LinkedList next();

    LinkedList value(long value);
    LinkedList next(LinkedList linkedList);
}

@@ -663,32 +663,39 @@
   roundTrip(tail
+, false
 );%0A        round
@@ -697,32 +697,39 @@
 roundTrip(middle
+, false
 );%0A        round
@@ -729,32 +729,39 @@
   roundTrip(head
+, false
 );%0A%0A        asse
@@ -1509,32 +1509,38 @@
   roundTrip(tail
+, true
 );%0A        round
@@ -1542,32 +1542,38 @@
 roundTrip(middle
+, true
 );%0A        round
@@ -1573,32 +1573,38 @@
   roundTrip(head
+, true
 );%0A        asser
@@ -2257,32 +2257,38 @@
   roundTrip(tail
+, true
 );%0A        round
@@ -2290,32 +2290,38 @@
 roundTrip(middle
+, true
 );%0A        round
@@ -2329,16 +2329,22 @@
 rip(head
+, true
 );%0A     
@@ -3072,24 +3072,31 @@
 undTrip(tail
+, false
 );%0A        r
@@ -3110,16 +3110,23 @@
 p(middle
+, false
 );%0A     
@@ -3142,16 +3142,23 @@
 rip(head
+, false
 );%0A%0A    
@@ -4293,16 +4293,32 @@
 nkedList
+, boolean binary
 ) %7B%0A    
@@ -4397,24 +4397,138 @@
 st.class));%0A
+        if (binary)%0A            assertEquals(linkedList, fromFressianByteArray(toFressianByteArray(linkedList)));%0A
     %7D%0A%7D%0A%0Aint

Add binary round-trip assertions