package be.iminds.ilabt.jfed.util;

import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.*;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

/**
 * JsonHelperTest
 */
public class JsonHelperTest {
    @Test
    public void testInt() {
        Object res = testIsJSONReconstructable("15");
        Assert.assertEquals(res, 15);
    }
    @Test
    public void testLong() {
        String actual = JSonHelper.jsonObjectToString(10l);
        String expected = "10";
        assertThat(actual, is(equalTo(expected)));
    }
    @Test
    public void testFloat() {
        String actual = JSonHelper.jsonObjectToString(1.5f);
        String expected = "1.5";
        assertThat(actual, is(equalTo(expected)));
    }
    @Test
    public void testString() {
        Object res = testIsJSONReconstructable("\"teststring\"");
        Assert.assertEquals(res, "teststring");
    }
    @Test
    public void testArray() {
        Object res = testIsJSONReconstructable("[ \"test\", 15, 12.6 ]");
        Object[] v = (Object[]) res;
        Assert.assertEquals(v.length, 3);
        Assert.assertEquals(v[0], "test");
        Assert.assertEquals(v[1], 15);
        Assert.assertEquals(v[2], 12.6);
    }
    @Test
    public void testHt() {
        Object res = testIsJSONReconstructable("{ \"a\" : \"test\", \"b\" : 15, \"c\" : 12.6 }");
        Map ht = (Map) res;
        Assert.assertEquals(ht.size(), 3);
        Assert.assertEquals(ht.get("a"), "test");
        Assert.assertEquals(ht.get("b"), 15);
        Assert.assertEquals(ht.get("c"), 12.6);
    }
    @Test
    public void testEmptyNull() {
        Object res = testIsJSONReconstructable("");
        Assert.assertNull(res);
    }
    @Test
    public void testNull() {
        Object res = testIsJSONReconstructable("null");
        Assert.assertNull(res);
    }
    @Test
    public void testEmptyString() {
        String c = "";
        Object res = testIsJSONReconstructable("\""+c+"\"");
        Assert.assertEquals(res, c);
    }
    @Test
    public void testComplexString1() {
        String c = "\nThis\nis\na\ntest\n\n";
        System.out.println("testComplexString1 origString="+c);
        String jsonString = JSonHelper.jsonObjectToString(c);
        System.out.println("testComplexString1 jsonString="+jsonString);
        Object res = testIsJSONReconstructable(jsonString);
        Assert.assertEquals(res, c);
    }
    @Test
    public void testComplexString2() {
        String c = "This\nis\"\nalso ' a test ] [ } < { with funny : \\ chars newline=\"\\n\"";
        System.out.println("testComplexString2 origString="+c);
        String jsonString = JSonHelper.jsonObjectToString(c);
        System.out.println("testComplexString2 jsonString="+jsonString);
        Object res = testIsJSONReconstructable(jsonString);
        Assert.assertEquals(res, c);
    }
    @Test
    public void testComplex() {
        String c = "[ \"test\", [\"test\", [1, 2], \"blah\" , 4.0 ], { \"t1\" : [3, 4], \"t2\" : [\"a\", { \"o\" : 5 } ], \"t3\" : { \"3\" : 4, \"p\" : \"x\" } } ]";
        Object res = testIsJSONReconstructable(c);
    }
    @Test
    public void testString2() {
        String actual = JSonHelper.jsonObjectToString("test");
        String expected = "\"test\"";
        assertThat(actual, is(equalTo(expected)));
    }

    private static class TestHelp1 {
        private String s;

        public TestHelp1(String s) {
            this.s = s;
        }

        @Override
        public String toString() {
            return s;
        }
    }
    @Test
    public void testString3() {
        //unknown object must be converted to and encoded as strings
        String actual = JSonHelper.jsonObjectToString(new TestHelp1("test3"));
        String expected = "\"test3\"";
        assertThat(actual, is(equalTo(expected)));
    }

    /* List must be caught and treated as List */
    @Test
    public void testList() {
        List l = Arrays.asList("test", 15, 12.6);
        String actual = JSonHelper.jsonObjectToString(l);
        Object res = testIsJSONReconstructable(actual);
        Object[] v = (Object[]) res;
        Assert.assertEquals(v.length, 3);
        Assert.assertEquals(v[0], "test");
        Assert.assertEquals(v[1], 15);
        Assert.assertEquals(v[2], 12.6);
    }

    /* Map must be treated as Map */
    @Test
    public void testMap() {
        Map m = new HashMap();
        m.put("a", "test");
        m.put("b", 15);
        m.put("c", 12.6);
        String actual = JSonHelper.jsonObjectToString(m);
        Object res = testIsJSONReconstructable(actual);
        Map ht = (Map) res;
        Assert.assertEquals(ht.size(), 3);
        Assert.assertEquals(ht.get("a"), "test");
        Assert.assertEquals(ht.get("b"), 15);
        Assert.assertEquals(ht.get("c"), 12.6);
    }

    private class ComparePair {
        public Object o1;
        public Object o2;
        private ComparePair(Object o1, Object o2) {
            this.o1 = o1;
            this.o2 = o2;
        }
    }
    /**
     * changes the jsonString into an object, and back again, and checks if it is the same.
     * @param jsonString
     * @return json object created (can be used for further testing)
     */
    public Object testIsJSONReconstructable(String jsonString) {
        Object obj1 = JSonHelper.jsonStringToXmlRpcLikeObject(jsonString);
        String reconstructed = JSonHelper.jsonObjectToString(obj1);
        Object obj2 = JSonHelper.jsonStringToXmlRpcLikeObject(reconstructed);

        if (obj1 == null || obj2 == null) {
            Assert.assertNull(obj1,
                    "obj1="+(obj1 == null ? "null" : ""+obj1.getClass().getName()+"("+obj1+")")+
                    " obj2="+(obj2 == null ? "null" : ""+obj2.getClass().getName()+"("+obj2+")")+
                    " reconstructed="+reconstructed+
                    " orig="+jsonString);
            Assert.assertNull(obj2,
                    "obj1="+(obj1 == null ? "null" : ""+obj1.getClass().getName()+"("+obj1+")"+
                    " obj2="+(obj2 == null ? "null" : ""+obj2.getClass().getName()+"("+obj2+")"))+
                    " reconstructed="+reconstructed+
                    " orig="+jsonString);
        } else {
            Deque<ComparePair> toCompare = new LinkedList<ComparePair>();
            toCompare.add(new ComparePair(obj1, obj2));
            while (!toCompare.isEmpty()) {
                ComparePair comparePair = toCompare.pollFirst();
                Object o1 = comparePair.o1;
                Object o2 = comparePair.o2;

                boolean handled = false;
                if (o1 instanceof String) {
                    handled = true;
                    Assert.assertTrue(o2 instanceof String);

                    String s1 = (String) o1;
                    String s2 = (String) o2;
                }
                if (o1 instanceof Object[]) {
                    handled = true;
                    Assert.assertTrue(o2 instanceof Object[]);

                    Object[] v1 = (Object[]) o1;
                    Object[] v2 = (Object[]) o2;
                    Assert.assertEquals(v1.length, v2.length);
                    for (int i = 0; i < v1.length; i++)
                        toCompare.add(new ComparePair(v1[i], v2[i]));
                }
                if (o1 instanceof Map) {
                    handled = true;
                    Assert.assertTrue(o2 instanceof Map);

                    Map ht1 = (Map) o1;
                    Map ht2 = (Map) o2;
                    Assert.assertEquals(ht1.size(), ht2.size());
                    for (Object key : ht1.keySet()) {
                        Object value1 = ht1.get(key);
                        Object value2 = ht2.get(key);
                        Assert.assertNotNull(value1);
                        Assert.assertNotNull(value2);
                        toCompare.add(new ComparePair(value1, value2));
                    }
                }
                if (!handled) {
                    if (o1 != null && o2 != null)
                        Assert.assertEquals(o2, o1);
                }
            }
        }

        return obj1;
    }
}
