/**
 * @author David Yu
 * @created Sep 30, 2009
 */
public class ProtobufJSONTest extends TestCase {
  static final ReflectionJSON REFLECTION_LITE = new ReflectionJSON(new Class[] {V2Lite.class});
  static final ReflectionNumericJSON REFLECTION_LITE_NUM =
      new ReflectionNumericJSON(new Class[] {V2Lite.class});
  static final V2LiteHandcodedJSON HANDCODED_LITE = new V2LiteHandcodedJSON();

  static final Charset utf8 = Charset.forName("UTF-8");

  static final Task task =
      Task.newBuilder()
          .setId(1)
          .setName("task_name")
          .setDescription("task_description")
          .setStatus(Task.Status.COMPLETED)
          .setAttachment(ByteString.copyFrom(new byte[] {0x34}))
          .setAboolean(true)
          .setAfloat(10.101f)
          .setAdouble(10.1010101d)
          .setAlong(1010101l)
          .build();

  static final Person person =
      Person.newBuilder()
          .setId(1)
          .setFirstName("john")
          .setLastName("doe")
          .setAGe(2)
          .setEmail("*****@*****.**")
          .setCurrentTask(task)
          // .addDelegatedTask(task)
          .addPriorityTask(task)
          .addPriorityTask(task)
          .addRepeatedLong(3)
          .addRepeatedLong(4)
          .addRepeatedLong(5)
          .addImage(ByteString.copyFrom(new byte[] {0x35}))
          .addImage(ByteString.copyFrom(new byte[] {0x36}))
          .addImage(ByteString.copyFrom(new byte[] {0x37}))
          .addImage(ByteString.copyFrom(new byte[] {0x38}))
          .build();

  static void generateAndParse(PrintStream out) throws Exception {
    String generated = generateAndParse(HANDCODED_LITE);
    String lite = generateAndParse(REFLECTION_LITE);
    String numLite = generateAndParse(REFLECTION_LITE_NUM);

    assertEquals(generated, lite);

    out.println(generated);
    out.println(lite);
    out.println(numLite);
  }

  static String generateAndParse(ProtobufJSON json) throws Exception {
    StringWriter sw = new StringWriter();
    json.writeTo(sw, person);
    String generated = sw.toString();

    JsonParser parser = json.getJsonFactory().createJsonParser(generated);
    Person parsedPerson = json.parseFrom(parser, Person.class);
    parser.close();

    assertEquals(person, parsedPerson);

    return generated;
  }

  static void assertEquals(Person p, Person p2) {
    assertTrue(p.getId() == p2.getId());

    assertEquals(p.getFirstName(), p2.getFirstName());

    assertEquals(p.getLastName(), p2.getLastName());

    assertTrue(p.getAGe() == p2.getAGe());

    assertEquals(p.getEmail(), p2.getEmail());

    assertEquals(p.getCurrentTask(), p2.getCurrentTask());

    assertTrue(p.getDelegatedTaskCount() == p2.getDelegatedTaskCount());

    for (int i = 0, len = p.getDelegatedTaskCount(); i < len; i++) {
      assertEquals(p.getDelegatedTask(i), p2.getDelegatedTask(i));
    }

    assertTrue(p.getImageCount() == p2.getImageCount());
    for (int i = 0, len = p.getImageCount(); i < len; i++) {
      assertEquals(p.getImage(i), p2.getImage(i));
    }
  }

  static void assertEquals(Task t, Task t2) {
    if (t == null && t2 == null) return;

    assertTrue(t.getId() == t2.getId());

    assertEquals(t.getName(), t2.getName());

    assertEquals(t.getDescription(), t2.getDescription());

    assertTrue(t.getStatus() == t2.getStatus());

    assertEquals(t.getAttachment(), t2.getAttachment());

    assertTrue(t.getAboolean() == t2.getAboolean());

    assertTrue(t.getAfloat() == t2.getAfloat());

    assertTrue(t.getAdouble() == t2.getAdouble());

    assertTrue(t.getAlong() == t2.getAlong());
  }

  public void jacksonTest() throws Exception {
    JsonFactory factory = new JsonFactory();
    StringWriter writer = new StringWriter();
    JsonGenerator generator = factory.createJsonGenerator(writer);
    generator.writeStartObject();
    generator.writeFieldName("bool");
    generator.writeBoolean(true);
    generator.writeFieldName("firstName");
    generator.writeString("john");
    generator.writeFieldName("age");
    generator.writeNumber(1);
    generator.writeFieldName("gg");
    generator.writeStartObject();
    generator.writeFieldName("firstName");
    generator.writeString("john");
    generator.writeEndObject();

    generator.writeEndObject();
    generator.close();
    String generated = writer.toString();
    System.out.print(generated);

    JsonParser parser = factory.createJsonParser(generated);
    assertTrue(parser.nextToken() == JsonToken.START_OBJECT);
    parser.nextToken();

    assertEquals("bool", parser.getCurrentName());
    assertTrue(parser.nextToken() == JsonToken.VALUE_TRUE);
    parser.nextToken();

    assertEquals("firstName", parser.getCurrentName());
    parser.nextToken();
    assertEquals("john", parser.getText());
    parser.nextToken();

    assertEquals("age", parser.getCurrentName());
    parser.nextToken();
    assertTrue(1 == parser.getIntValue());
    parser.nextToken();

    assertEquals("gg", parser.getCurrentName());
    assertTrue(parser.nextToken() == JsonToken.START_OBJECT);
    parser.nextToken();
    assertEquals("firstName", parser.getCurrentName());
    parser.nextToken();
    assertEquals("john", parser.getText());
    assertTrue(parser.nextToken() == JsonToken.END_OBJECT);

    assertTrue(parser.nextToken() == JsonToken.END_OBJECT);

    parser.close();
  }

  static String generatedAndParse(
      ProtobufJSON json, List<Person> personList, List<Person> parsedPersonList) throws Exception {
    StringWriter sw = new StringWriter();
    json.writeTo(sw, personList, Person.class);
    String generated = sw.toString();

    JsonParser parser = json.getJsonFactory().createJsonParser(generated);
    json.appendMessageFrom(parser, parsedPersonList, Person.class);
    parser.close();
    assertTrue(personList.size() == parsedPersonList.size());
    for (int i = 0, len = personList.size(); i < len; i++)
      assertEquals(personList.get(i), parsedPersonList.get(i));

    return generated;
  }

  public void testCollection() throws Exception {
    ArrayList<Person> personList = new ArrayList<Person>();
    ArrayList<Person> parsedPersonList = new ArrayList<Person>();
    personList.add(person);
    personList.add(person);

    // System.err.println(generatedAndParse(HANDCODED_LITE, personList, parsedPersonList));
    parsedPersonList.clear();

    // System.err.println(generatedAndParse(REFLECTION_LITE, personList, parsedPersonList));
    parsedPersonList.clear();

    // System.err.println(generatedAndParse(REFLECTION_LITE_NUM, personList, parsedPersonList));
    parsedPersonList.clear();
  }

  public void testGenerateAndParse() throws Exception {
    generateAndParse(HANDCODED_LITE);
    generateAndParse(REFLECTION_LITE);
    generateAndParse(REFLECTION_LITE_NUM);
    // System.err.println(generateAndParse(HANDCODED_LITE));
    // System.err.println(generateAndParse(REFLECTION_LITE));
    // System.err.println(generateAndParse(REFLECTION_LITE_NUM));
  }

  public void testBenchmark() throws Exception {
    if (!"false".equals(System.getProperty("benchmark.skip"))) return;

    String dir = System.getProperty("benchmark.output_dir");

    PrintStream out =
        dir == null
            ? System.out
            : new PrintStream(
                new FileOutputStream(
                    new File(
                        new File(dir),
                        "protostuff-benchmark-" + System.currentTimeMillis() + ".html"),
                    true));

    int warmups = Integer.getInteger("benchmark.warmups", 100000);
    int loops = Integer.getInteger("benchmark.loops", 1000000);
    String title = "protostuff-json ser/deser benchmark for " + loops + " runs";
    out.println("<html><head><title>");
    out.println(title);
    out.println("</title></head><body><p>");
    out.println(title);
    out.println("</p><pre>\n\n");

    generateAndParse(out);
    out.println("\n\n</pre><hr/><pre>");
    start(out, warmups, loops);

    if (System.out != out) out.close();
  }

  public static void main(String[] args) throws Exception {
    String dir = System.getProperty("benchmark.output_dir");

    PrintStream out =
        dir == null
            ? System.out
            : new PrintStream(
                new FileOutputStream(
                    new File(
                        new File(dir),
                        "protostuff-benchmark-" + System.currentTimeMillis() + ".html"),
                    true));
    int runs = Integer.getInteger("benchmark.runs", 1);

    int warmups = Integer.getInteger("benchmark.warmups", 100000);
    int loops = Integer.getInteger("benchmark.loops", 1000000);
    String title = "protostuff-json ser/deser benchmark for " + loops + " runs";
    out.println("<html><head><title>");
    out.println(title);
    out.println("</title></head><body><p>");
    out.println(title);
    out.println("</p><pre>\n\n");

    generateAndParse(out);
    out.println("\n\n</pre><hr/><pre>");
    for (int i = 0; i < runs; i++) start(out, warmups, loops);

    if (System.out != out) out.close();
  }

  public static void start(PrintStream out, int warmups, int loops) throws Exception {
    serDeser(out, HANDCODED_LITE, "HANDCODED_LITE", warmups, loops);
    serDeser(out, REFLECTION_LITE, "REFLECTION_LITE", warmups, loops);
    serDeser(out, REFLECTION_LITE_NUM, "REFLECTION_LITE_NUM", warmups, loops);
  }

  static void serDeser(PrintStream out, ProtobufJSON json, String name, int warmups, int loops)
      throws Exception {
    int len = generateAndParse(json).getBytes(utf8).length;
    for (int i = 0; i < warmups; i++) generateAndParse(json);
    long start = System.currentTimeMillis();
    for (int i = 0; i < loops; i++) generateAndParse(json);
    long finish = System.currentTimeMillis();
    long elapsed = finish - start;
    out.println(elapsed + " ms elapsed with " + len + " bytes for " + name);
  }
}
  static void assertEquals(Task t, Task t2) {
    if (t == null && t2 == null) return;

    assertTrue(t.getId() == t2.getId());

    assertEquals(t.getName(), t2.getName());

    assertEquals(t.getDescription(), t2.getDescription());

    assertTrue(t.getStatus() == t2.getStatus());

    assertEquals(t.getAttachment(), t2.getAttachment());

    assertTrue(t.getAboolean() == t2.getAboolean());

    assertTrue(t.getAfloat() == t2.getAfloat());

    assertTrue(t.getAdouble() == t2.getAdouble());

    assertTrue(t.getAlong() == t2.getAlong());
  }