package com.foo;
// just showing logback imports for brevity
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.encoder.EncoderBase;\

public class JsonEncoder
  extends EncoderBase<ILoggingEvent>
{
  /**
   * The charset to use when converting a String into bytes.
   * <p/>
   * By default this property has the value
   * <code>null</null> which corresponds to
   * the system's default charset.
   */
  private Charset charset;

  private boolean immediateFlush = true;


  /**
   * <p>Sets the immediateFlush option. The default value for immediateFlush is
   * 'true'. If set to true, the doEncode() method will immediately flush the
   * underlying OutputStream. Although immediate flushing is safer, it also
   * significantly degrades logging throughput.</p>
   */
  public void setImmediateFlush(boolean immediateFlush)
  {
    this.immediateFlush = immediateFlush;
  }

  public boolean isImmediateFlush()
  {
    return immediateFlush;
  }

  public Charset getCharset()
  {
    return charset;
  }

  /**
   * <p>Set the charset to use when converting the string returned by the layout
   * into bytes.</p>
   *
   * <p>By default this property has the value <code>null</null> which
   * corresponds to the system's default charset.</p>
   *
   * @param charset
   */
  public void setCharset(Charset charset)
  {
    this.charset = charset;
  }

  @Override
  public void close()
    throws IOException
  {
    outputStream.write(convertToBytes(CoreConstants.LINE_SEPARATOR, getCharset()));
  }

  @Override
  public void doEncode(ILoggingEvent event)
    throws IOException
  {
    GsonLoggingEventBuilder builder = new GsonLoggingEventBuilder();
    builder.setMessage(event.getFormattedMessage());

    builder.addField("level", event.getLevel().toString());
    builder.addField("logger", event.getLoggerName());
    builder.addField("thread", event.getThreadName());
    builder.addField("context.name", event.getLoggerContextVO().getName());

    addTimestamp(builder, event);
    addMDC(builder, event);
    addContext(builder, event);
    addThrowableInformation(builder, event);

    Charset charset = getCharset();
    outputStream.write(convertToBytes(builder.toJson(), charset));
    outputStream.write(convertToBytes(CoreConstants.LINE_SEPARATOR, charset));

    if (immediateFlush) {
      outputStream.flush();
    }
  }

  private void addTimestamp(JsonLoggingEventBuilder builder, ILoggingEvent event)
  {
    builder.addField("timestamp", formatTimestamp(event));
  }

  private void addThrowableInformation(JsonLoggingEventBuilder builder, ILoggingEvent event)
  {
    final IThrowableProxy tp = event.getThrowableProxy();
    if (tp != null) {
      builder.addField("stacktrace", ThrowableProxyUtil.asString(tp));
      //...what else should be included?
    }
  }

  private void addMDC(JsonLoggingEventBuilder builder, ILoggingEvent event)
  {
    Map<String, String> mdc = event.getMDCPropertyMap();
    if (!mdc.isEmpty()) {
      builder.addField("mdc", mdc);
    }
  }

  private void addContext(JsonLoggingEventBuilder builder, ILoggingEvent event)
  {
    Context ctx = getContext();
    if (ctx != null) {
      builder.addField("context", ctx.getCopyOfPropertyMap());
    }
  }
}
