package net.kencochrane.raven.marshaller.json;

import com.fasterxml.jackson.core.JsonGenerator;
import net.kencochrane.raven.event.interfaces.ExceptionInterface;
import net.kencochrane.raven.event.interfaces.ImmutableThrowable;
import net.kencochrane.raven.event.interfaces.StackTraceInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;

/**
 * Binding system allowing to convert an {@link ExceptionInterface} to a JSON stream.
 */
public class ExceptionInterfaceBinding implements InterfaceBinding<ExceptionInterface> {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionInterfaceBinding.class);
    private static final String TYPE_PARAMETER = "type";
    private static final String VALUE_PARAMETER = "value";
    private static final String MODULE_PARAMETER = "module";
    private static final String STACKTRACE_PARAMETER = "stacktrace";
    private static final String DEFAULT_PACKAGE_NAME = "(default)";
    private final InterfaceBinding<StackTraceInterface> stackTraceInterfaceBinding;

    /**
     * Creates a Binding system to send a {@link ExceptionInterface} on JSON stream.
     * <p>
     * Exceptions may contain StackTraces, this means that the system should also be able to send a
     * {@link StackTraceInterface} on the JSON stream.
     * </p>
     *
     * @param stackTraceInterfaceBinding InterfaceBinding allowing to send a {@link StackTraceInterface} on the JSON
     *                                   stream.
     */
    public ExceptionInterfaceBinding(InterfaceBinding<StackTraceInterface> stackTraceInterfaceBinding) {
        this.stackTraceInterfaceBinding = stackTraceInterfaceBinding;
    }

    @Override
    public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException {
        Deque<ExceptionWithStackTrace> exceptions = unfoldExceptionInterface(exceptionInterface);

        //Unstack the exceptions
        generator.writeStartArray();
        while (!exceptions.isEmpty()) {
            writeException(generator, exceptions.pop());
        }
        generator.writeEndArray();
    }

    /**
     * Stack the exception and its causes with their StackTraces to provide them in the order expected by Sentry.
     * <p>
     * Sentry expects to get the exception from the first generated (cause) to the last generated. To provide the
     * exceptions in this order, those are stacked then later the stack is emptied.
     * </p>
     * <p>
     * Each exception provides a {@link StackTraceInterface}.
     * </p>
     *
     * @param exceptionInterface Sentry interface containing the captured exception.
     * @return a Stack of Exceptions with their {@link StackTraceInterface}.
     */
    private Deque<ExceptionWithStackTrace> unfoldExceptionInterface(ExceptionInterface exceptionInterface) {
        Deque<ExceptionWithStackTrace> exceptions = new ArrayDeque<ExceptionWithStackTrace>();
        Set<ImmutableThrowable> circularityDetector = new HashSet<ImmutableThrowable>();
        ImmutableThrowable throwable = exceptionInterface.getThrowable();
        StackTraceElement[] enclosingStackTrace = new StackTraceElement[0];

        //Stack the exceptions to send them in the reverse order
        while (throwable != null) {
            if (!circularityDetector.add(throwable)) {
                logger.warn("Exiting a circular exception!");
                break;
            }

            StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace);
            exceptions.push(new ExceptionWithStackTrace(throwable, stackTrace));
            enclosingStackTrace = throwable.getStackTrace();
            throwable = throwable.getCause();
        }

        return exceptions;
    }

    /**
     * Outputs an exception with its StackTrace on a JSon stream.
     *
     * @param generator JSonGenerator.
     * @param ewst      Exception with its associated {@link StackTraceInterface}.
     * @throws IOException
     */
    private void writeException(JsonGenerator generator, ExceptionWithStackTrace ewst) throws IOException {
        generator.writeStartObject();
        generator.writeStringField(TYPE_PARAMETER, ewst.exception.getActualClass().getSimpleName());
        generator.writeStringField(VALUE_PARAMETER, ewst.exception.getMessage());
        Package aPackage = ewst.exception.getActualClass().getPackage();
        generator.writeStringField(MODULE_PARAMETER, (aPackage != null) ? aPackage.getName() : DEFAULT_PACKAGE_NAME);

        generator.writeFieldName(STACKTRACE_PARAMETER);
        stackTraceInterfaceBinding.writeInterface(generator, ewst.stackTrace);
        generator.writeEndObject();
    }

    /**
     * Class associating an exception to its {@link StackTraceInterface}.
     */
    private static final class ExceptionWithStackTrace {
        private ImmutableThrowable exception;
        private StackTraceInterface stackTrace;

        private ExceptionWithStackTrace(ImmutableThrowable exception, StackTraceInterface stackTrace) {
            this.exception = exception;
            this.stackTrace = stackTrace;
        }
    }
}

@@ -202,33 +202,38 @@
 erfaces.
-ImmutableThrowabl
+ExceptionWithStackTrac
 e;%0Aimpor
@@ -298,193 +298,59 @@
 ce;%0A
-import org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;%0A%0Aimport java.io.IOException;%0Aimport java.util.ArrayDeque;%0Aimport java.util.Deque;%0Aimport java.util.HashSet;%0Aimport java.util.Set
+%0Aimport java.io.IOException;%0Aimport java.util.Deque
 ;%0A%0A/
@@ -535,107 +535,8 @@
 %3E %7B%0A
-    private static final Logger logger = LoggerFactory.getLogger(ExceptionInterfaceBinding.class);%0A
     
@@ -781,76 +781,8 @@
 e%22;%0A
-    private static final String DEFAULT_PACKAGE_NAME = %22(default)%22;%0A
     
@@ -1682,33 +1682,8 @@
 s = 
-unfoldExceptionInterface(
 exce
@@ -1696,16 +1696,31 @@
 nterface
+.getExceptions(
 );%0A%0A    
@@ -1938,1664 +1938,8 @@
  %7D%0A%0A
-    /**%0A     * Stack the exception and its causes with their StackTraces to provide them in the order expected by Sentry.%0A     * %3Cp%3E%0A     * Sentry expects to get the exception from the first generated (cause) to the last generated. To provide the%0A     * exceptions in this order, those are stacked then later the stack is emptied.%0A     * %3C/p%3E%0A     * %3Cp%3E%0A     * Each exception provides a %7B@link StackTraceInterface%7D.%0A     * %3C/p%3E%0A     *%0A     * @param exceptionInterface Sentry interface containing the captured exception.%0A     * @return a Stack of Exceptions with their %7B@link StackTraceInterface%7D.%0A     */%0A    private Deque%3CExceptionWithStackTrace%3E unfoldExceptionInterface(ExceptionInterface exceptionInterface) %7B%0A        Deque%3CExceptionWithStackTrace%3E exceptions = new ArrayDeque%3CExceptionWithStackTrace%3E();%0A        Set%3CImmutableThrowable%3E circularityDetector = new HashSet%3CImmutableThrowable%3E();%0A        ImmutableThrowable throwable = exceptionInterface.getThrowable();%0A        StackTraceElement%5B%5D enclosingStackTrace = new StackTraceElement%5B0%5D;%0A%0A        //Stack the exceptions to send them in the reverse order%0A        while (throwable != null) %7B%0A            if (!circularityDetector.add(throwable)) %7B%0A                logger.warn(%22Exiting a circular exception!%22);%0A                break;%0A            %7D%0A%0A            StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace);%0A            exceptions.push(new ExceptionWithStackTrace(throwable, stackTrace));%0A            enclosingStackTrace = throwable.getStackTrace();%0A            throwable = throwable.getCause();%0A        %7D%0A%0A        return exceptions;%0A    %7D%0A%0A
     
@@ -2370,33 +2370,36 @@
 METER, ewst.
-e
+getE
 xception
 .getActualCl
@@ -2390,35 +2390,13 @@
 tion
-.getActualClass().getSimple
+Class
 Name
@@ -2453,33 +2453,36 @@
 METER, ewst.
-e
+getE
 xception
 .getMessage(
@@ -2469,20 +2469,16 @@
 xception
-.get
 Message(
@@ -2485,81 +2485,8 @@
 ));%0A
-        Package aPackage = ewst.exception.getActualClass().getPackage();%0A
     
@@ -2538,74 +2538,41 @@
 ER, 
-(aPackage != null) ? a
+ewst.getException
 Package
-.get
 Name()
- : DEFAULT_PACKAGE_NAME
 );%0A
-%0A
     
@@ -2693,506 +2693,76 @@
 wst.
-stackTrace);%0A        generator.writeEndObject();%0A    %7D%0A%0A    /**%0A     * Class associating an exception to its %7B@link StackTraceInterface%7D.%0A     */%0A    private static final class ExceptionWithStackTrace %7B%0A        private ImmutableThrowable exception;%0A        private StackTraceInterface stackTrace;%0A%0A        private ExceptionWithStackTrace(ImmutableThrowable exception, StackTraceInterface stackTrace) %7B%0A            this.exception = exception;%0A            this.stackTrace = stackTrace;%0A        %7D%0A    %7D
+getStackTraceInterface());%0A        generator.writeEndObject();%0A    %7D%0A
 %0A%7D%0A

Remove the inner ExceptionWithStackTrace from ExceptionInterfaceBinding