package org.jboss.errai.marshalling.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.jboss.errai.marshalling.client.api.json.EJArray;
import org.jboss.errai.marshalling.client.api.json.EJValue;
import org.jboss.errai.marshalling.server.JSONDecoder;
import org.junit.Ignore;
import org.junit.Test;

/**
 * Direct tests for the (server-side) JSON decoder.
 *
 * @author Jonathan Fuerth <jfuerth@gmail.com>
 */
public class JSONDecoderTest {

  @Test
  public void testDecodeArrayOfMixedType() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": [ \"string\", 123.456, {}, [], true, false, null ] }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJArray array = ejv.isObject().get("myValue").isArray();
    assertEquals(7, array.size());
    assertEquals("string", array.get(0).isString().stringValue());
    assertEquals(123.456, array.get(1).isNumber().doubleValue(), .00001);
    assertNotNull(array.get(2).isObject());
    assertNotNull(array.get(3).isArray());
    assertTrue(array.get(4).isBoolean().booleanValue());
    assertFalse(array.get(5).isBoolean().booleanValue());
    assertTrue(array.get(6).isNull());
  }

  @Test
  public void testDecodeEmptyArray() throws Exception {
    EJValue ejv = JSONDecoder.decode("[]");
    assertNotNull(ejv.isArray());
    assertEquals(0, ejv.isArray().size());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(ejv.isString());
    assertNull(ejv.isNumber());
    assertNull(ejv.isObject());
    assertNull(ejv.isBoolean());
    assertFalse(ejv.isNull());
  }

  @Test
  public void testDecodeEmptyObject() throws Exception {
    EJValue ejv = JSONDecoder.decode("{}");
    assertNotNull(ejv.isObject());
    assertEquals(0, ejv.isObject().keySet().size());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(ejv.isString());
    assertNull(ejv.isNumber());
    assertNull(ejv.isArray());
    assertNull(ejv.isBoolean());
    assertFalse(ejv.isNull());
  }

  @Test
  public void testDecodeObjectWithTrue() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": true }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertTrue(myValue.isBoolean().booleanValue());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isString());
    assertNull(myValue.isNumber());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertFalse(myValue.isNull());
  }

  @Test
  public void testDecodeObjectWithFalse() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": false }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertFalse(myValue.isBoolean().booleanValue());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isString());
    assertNull(myValue.isNumber());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertFalse(myValue.isNull());
  }

  @Test
  public void testDecodeObjectWithNull() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": null }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertTrue(myValue.isNull());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isString());
    assertNull(myValue.isNumber());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertNull(myValue.isBoolean());
  }

  @Test
  public void testDecodeObjectWithNumber() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": 123456 }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertEquals(123456, myValue.isNumber().intValue());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isString());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertNull(myValue.isBoolean());
    assertFalse(myValue.isNull());
  }

  @Test
  public void testDecodeObjectWithNegativeNumber() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": -123456 }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertEquals(-123456, myValue.isNumber().intValue());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isString());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertNull(myValue.isBoolean());
    assertFalse(myValue.isNull());
  }

  @Test
  public void testDecodeObjectWithScientificNumbers() throws Exception {
    EJValue ejv = JSONDecoder.decode("{" +
            " \"myValue0\": 1.234e56" +
            ", \"myValue1\": 1.234e+56" +
            ", \"myValue2\": 1.234e-56" +
            ", \"myValue3\": 1.234E56" +
            ", \"myValue4\": 1.234E+56" +
            ", \"myValue5\": 1.234E-56" +
            ", \"myValue6\": 1234E-59" +
    		"}");
    for (int i = 0; i < 6; i++) {
      assertTrue(ejv.isObject().containsKey("myValue" + i));
      EJValue myValue = ejv.isObject().get("myValue" + i);
      switch (i) {
      case 2:
      case 5:
        assertEquals("Failed on myValue" + i, 1.234e-56, myValue.isNumber().doubleValue(), 0.0);
        break;
      default:
        assertEquals("Failed on myValue" + i, 1.234e56, myValue.isNumber().doubleValue(), 0.0);
        break;
      }
    }
  }

  /**
   * JSON numbers can't have a leading 0. This test is presently ignored because
   * the logic for detecting a leading 0 is nontrivial (that is, it may impact
   * performance). We can re-enable this test if leading 0 detection becomes
   * important.
   */
  @Test @Ignore
  public void testDecodeObjectWithInvalidNumberLeading0() throws Exception {
    try {
      JSONDecoder.decode("{ \"myValue\": 0123456 }");
      fail("Expected NumberFormatException");
    } catch (Exception e) {
      assertEquals(NumberFormatException.class, findRootCause(e).getClass());
    }
  }

  /**
   * JSON numbers can't have a leading dot.
   */
  @Test
  public void testDecodeObjectWithInvalidNumberLeadingDot() throws Exception {
    try {
      JSONDecoder.decode("{ \"myValue\": .123456 }");
      fail("Expected NumberFormatException");
    } catch (Exception e) {
      assertEquals(NumberFormatException.class, findRootCause(e).getClass());
    }
  }

  /**
   * JSON numbers can't have a trailing e.
   */
  @Test
  public void testDecodeObjectWithInvalidNumberTrailingE() throws Exception {
    try {
      JSONDecoder.decode("{ \"myValue\": 123456e }");
      fail("Expected NumberFormatException");
    } catch (Exception e) {
      assertEquals(NumberFormatException.class, findRootCause(e).getClass());
    }
  }

  @Test
  public void testDecodeObjectWithString() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": \"myString\" }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertEquals("myString", myValue.isString().stringValue());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isNumber());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertNull(myValue.isBoolean());
    assertFalse(myValue.isNull());
  }

  @Test
  public void testDecodeObjectWithEmptyString() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": \"\" }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertEquals("", myValue.isString().stringValue());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isNumber());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertNull(myValue.isBoolean());
    assertFalse(myValue.isNull());
  }

  @Test
  public void testDecodeObjectWithNonAsciiString() throws Exception {
    EJValue ejv = JSONDecoder.decode("{ \"myValue\": \"myStr\u00efng\" }");
    assertTrue(ejv.isObject().containsKey("myValue"));
    EJValue myValue = ejv.isObject().get("myValue");
    assertEquals("myStr\u00efng", myValue.isString().stringValue());

    // ensure it's not also identifying as any of the other JSON types
    assertNull(myValue.isNumber());
    assertNull(myValue.isObject());
    assertNull(myValue.isArray());
    assertNull(myValue.isBoolean());
    assertFalse(myValue.isNull());
  }

  private static Throwable findRootCause(Throwable e) {
    while (e.getCause() != null && e.getCause() != e) {
      e = e.getCause();
    }
    return e;
  }
}
