package net.kencochrane.raven.event.interfaces;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * Class associating an exception to its {@link net.kencochrane.raven.event.interfaces.StackTraceInterface}.
 */
public final class ExceptionWithStackTrace {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionWithStackTrace.class);
    /**
     * Name used when the class' package is the default one.
     */
    private static final String DEFAULT_PACKAGE_NAME = "(default)";
    private final String exceptionMessage;
    private final String exceptionClassName;
    private final String exceptionPackageName;
    private final StackTraceInterface stackTraceInterface;

    /**
     * Creates a new instance from the given exceptions data.
     *
     * @param exceptionMessage     the message of the exception
     * @param exceptionClassName   the exception's class name
     * @param exceptionPackageName the exception's package name
     * @param stackTraceInterface  the stack trace interface holding the stack trace information of the exception
     */
    public ExceptionWithStackTrace(String exceptionMessage,
                                   String exceptionClassName,
                                   String exceptionPackageName,
                                   StackTraceInterface stackTraceInterface) {
        this.exceptionMessage = exceptionMessage;
        this.exceptionClassName = exceptionClassName;
        this.exceptionPackageName = exceptionPackageName;
        this.stackTraceInterface = stackTraceInterface;
    }

    /**
     * Transforms a {@link Throwable} into a Queue of {@link ExceptionWithStackTrace}.
     * <p>
     * Exceptions are stored in the queue from the most recent one to the oldest one.
     * </p>
     *
     * @param throwable throwable to transform in a queue of exceptions.
     * @return a queue of exception with StackTrace.
     */
    public static Deque<ExceptionWithStackTrace> extractExceptionQueue(Throwable throwable) {
        Deque<ExceptionWithStackTrace> exceptions = new ArrayDeque<ExceptionWithStackTrace>();
        Set<Throwable> circularityDetector = new HashSet<Throwable>();
        StackTraceElement[] enclosingStackTrace = new StackTraceElement[0];

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

            StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace);
            exceptions.add(createExceptionWithStackTraceFrom(throwable, stackTrace));
            enclosingStackTrace = throwable.getStackTrace();
            throwable = throwable.getCause();
        }

        return exceptions;
    }

    private static ExceptionWithStackTrace createExceptionWithStackTraceFrom(Throwable throwable,
                                                                             StackTraceInterface stackTrace) {
        String exceptionMessage = throwable.getMessage();
        String exceptionClassName = throwable.getClass().getSimpleName();
        String exceptionPackageName = extractPackageName(throwable);
        return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace);
    }

    private static String extractPackageName(Throwable throwable) {
        Package exceptionPackage = throwable.getClass().getPackage();

        if (exceptionPackage != null) {
            return exceptionPackage.getName();
        }

        return null;
    }

    /**
     * Gets the exception message.
     *
     * @return the exception message
     */
    public String getExceptionMessage() {
        return exceptionMessage;
    }

    /**
     * Gets the exception's class name.
     *
     * @return the exception's class name
     */
    public String getExceptionClassName() {
        return exceptionClassName;
    }

    /**
     * Gets the exception's package name.
     *
     * @return the exception's package name
     */
    public String getExceptionPackageName() {

        if (exceptionPackageName == null) {
            return DEFAULT_PACKAGE_NAME;
        }

        return exceptionPackageName;
    }

    /**
     * Gets the exceptions stack trace interface.
     *
     * @return the exceptions stack trace interface
     */
    public StackTraceInterface getStackTraceInterface() {
        return stackTraceInterface;
    }

    /**
     * Gets the exception's stack trace.
     *
     * @return the exception's stack trace
     */
    public StackTraceElement[] getStackTrace() {
        return stackTraceInterface.getStackTrace();
    }
}

@@ -801,16 +801,975 @@
 rface;%0A%0A
+    /**%0A     * Creates a Sentry exception based on a Java Throwable.%0A     * %3Cp%3E%0A     * The %7B@code childExceptionStackTrace%7D parameter is used to define the common frames with the child exception%0A     * (Exception caused by %7B@code throwable%7D).%0A     * %3C/p%3E%0A     *%0A     * @param throwable                Java exception to send to Sentry.%0A     * @param childExceptionStackTrace StackTrace of the exception caused by %7B@code throwable%7D.%0A     */%0A    public ExceptionWithStackTrace(Throwable throwable, StackTraceElement%5B%5D childExceptionStackTrace) %7B%0A        this.exceptionMessage = throwable.getMessage();%0A        this.exceptionClassName = throwable.getClass().getSimpleName();%0A        Package exceptionPackage = throwable.getClass().getPackage();%0A        this.exceptionPackageName = exceptionPackage != null ? exceptionPackage.getName() : null;%0A        this.stackTraceInterface = new StackTraceInterface(throwable.getStackTrace(), childExceptionStackTrace);%0A    %7D%0A%0A
     /**%0A
@@ -3271,25 +3271,30 @@
 ement%5B%5D 
-enclosing
+childException
 StackTra
@@ -3647,126 +3647,8 @@
  %7D%0A%0A
-            StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace);%0A
     
@@ -3670,22 +3670,20 @@
 ons.add(
-create
+new 
 Exceptio
@@ -3697,20 +3697,16 @@
 ackTrace
-From
 (throwab
@@ -3713,126 +3713,33 @@
 le, 
-stackTrace));%0A            enclosingStackTrace = throwable.getStackTrace();%0A            throwable = throwable.getCause(
+childExceptionStackTrace)
 );%0A 
@@ -3748,95 +3748,18 @@
     
- %7D%0A%0A
      
-   return exceptions;%0A    %7D%0A%0A    private static ExceptionWithStackTrace create
+child
 Exce
@@ -3755,36 +3755,32 @@
   childException
-With
 StackTraceFrom(T
@@ -3777,568 +3777,58 @@
 race
-From(Throwable throwable,%0A                                                                             StackTraceInterface stackTrace) %7B%0A        String exceptionMessage = throwable.getMessage();%0A        String exceptionClassName = throwable.getClass().getSimpleName();%0A        String exceptionPackageName = extractPackageName(throwable);%0A        return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace
+ = throwable.getStackTrace(
 );%0A    
-%7D%0A%0A
     
-private static String extractPackageName(Throwable throwable) %7B%0A        Package exceptionPackag
+    throwabl
 e = 
@@ -3845,38 +3845,24 @@
 getC
-lass().getPackag
+aus
 e();%0A
-%0A
         
 if (
@@ -3861,117 +3861,36 @@
     
-if (exceptionPackage != null) %7B%0A            return exceptionPackage.getName();%0A        %7D%0A%0A        return null
+%7D%0A%0A        return exceptions
 ;%0A  

Simplify the creation of ExceptionWithStackTrace with a constructor accepting a Throwable