package com.carrotsearch.ant.tasks.junit4.listeners.json;

import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Type;
import java.util.Date;

import org.apache.commons.io.output.WriterOutputStream;

import com.carrotsearch.ant.tasks.junit4.ForkedJvmInfo;
import com.carrotsearch.ant.tasks.junit4.events.*;
import com.carrotsearch.ant.tasks.junit4.events.aggregated.AggregatedSuiteResultEvent;
import com.google.gson.*;

import static com.carrotsearch.ant.tasks.junit4.events.EventType.*;

/**
 * Serialization of {@link AggregatedSuiteResultEvent}.
 */
public class JsonAggregatedSuiteResultEventAdapter implements JsonSerializer<AggregatedSuiteResultEvent> {
  private final boolean outputStreams;

  public JsonAggregatedSuiteResultEventAdapter(boolean outputStreams) {
    this.outputStreams = outputStreams;
  }

  @Override
  public JsonElement serialize(AggregatedSuiteResultEvent e, 
      Type type, JsonSerializationContext context) {
    JsonObject suite = new JsonObject();
    suite.addProperty("slave", e.getSlave().id);
    suite.addProperty("startTimestamp", e.getStartTimestamp());
    suite.add("startTimestampDate", context.serialize(new Date(e.getStartTimestamp())));
    suite.addProperty("executionTime", e.getExecutionTime());

    suite.add("description", context.serialize(e.getDescription()));

    suite.add("tests", context.serialize(e.getTests()));
    suite.add("suiteFailures", context.serialize(e.getFailures()));
    suite.add("executionEvents", serializeEvents(e, context));
    return suite;
  }

  public JsonArray serializeEvents(AggregatedSuiteResultEvent e, JsonSerializationContext context) {
    final JsonArray output = new JsonArray();
    final ForkedJvmInfo slave = e.getSlave();
    int lineBuffer = 160;
    final StringWriter out = new StringWriter();
    final StringWriter err = new StringWriter();
    WriterOutputStream stdout = new WriterOutputStream(out, slave.getCharset(), lineBuffer, false);
    WriterOutputStream stderr = new WriterOutputStream(err, slave.getCharset(), lineBuffer, false);
    for (IEvent evt : e.getEventStream()) {
      try {
        JsonObject marker;
        switch (evt.getType()) {
          case SUITE_FAILURE:
          case TEST_IGNORED_ASSUMPTION:
          case TEST_IGNORED:
          case TEST_STARTED:
          case TEST_FINISHED:
          case TEST_FAILURE:
            flushBoth(output, out, err, stdout, stderr);
            marker = new JsonObject();
            marker.addProperty("event", evt.getType().toString());
            marker.add("description", context.serialize(((IDescribable) evt).getDescription()));
            if (evt instanceof FailureEvent) {
              marker.add("failure", context.serialize(((FailureEvent) evt).getFailure()));
            }
            output.add(marker);
            break;

          // Flush streams only if there's interwoven output between them.

          case APPEND_STDOUT:
            if (outputStreams) {
              flush(APPEND_STDERR, output, stderr, err);
              ((IStreamEvent) evt).copyTo(stdout);
            }
            break;

          case APPEND_STDERR:
            if (outputStreams) {
              flush(APPEND_STDOUT, output, stdout, out);
              ((IStreamEvent) evt).copyTo(stderr);
            }
            break;

          default:
            break;
        }
      } catch (IOException ex) {
        // Ignore.
      }
    }
    flushBoth(output, out, err, stdout, stderr);
    return output;
  }

  public void flushBoth(JsonArray output, final StringWriter out, final StringWriter err, WriterOutputStream stdout, WriterOutputStream stderr) {
    try {
      flush(APPEND_STDOUT, output, stdout, out);
      flush(APPEND_STDERR, output, stderr, err);
    } catch (IOException ex) {
      // Ignore.
    }
  }

  private void flush(EventType evt, JsonArray output, WriterOutputStream wos, StringWriter out) throws IOException {
    wos.flush();
    if (out.getBuffer().length() > 0) {
      JsonObject chunk = new JsonObject();
      chunk.addProperty("event", evt.toString());
      chunk.addProperty("content", out.getBuffer().toString());
      out.getBuffer().setLength(0);
      output.add(chunk);
    }
  }
}
