package br.ufrgs.inf.prosoft.tigris.monitoring.util.threads;

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

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * The type Verbose runnable.
 */
@SuppressWarnings("PMD.DoNotUseThreads")
public final class VerboseRunnable implements Runnable {

    /**
     * Original runnable.
     */
    private final transient Runnable origin;
    /**
     * Rethrow exceptions (TRUE) or swallow them?
     */
    private final transient boolean rethrow;
    /**
     * Shall we report a full stacktrace?
     */
    private final transient boolean verbose;
    /**
     * The Logger.
     */
    Logger logger = LoggerFactory.getLogger(NamedThreads.class);

    /**
     * Default constructor, doesn't swallow exceptions.
     *
     * @param runnable Runnable to wrap
     */
    public VerboseRunnable(final Runnable runnable) {
        this(runnable, false);
    }

    /**
     * Default constructor, doesn't swallow exceptions.
     *
     * @param callable Callable to wrap
     * @since 0.7.17
     */
    public VerboseRunnable(final Callable<?> callable) {
        this(callable, false);
    }

    /**
     * Default constructor, doesn't swallow exceptions.
     *
     * @param callable Callable to wrap
     * @param swallow  Shall we swallow exceptions                 ({@code TRUE}) or re-throw                 ({@code FALSE})? Exception swallowing means that {@link #run()}                 will never throw any exceptions (in any case all exceptions are logged                 using {@link Logger}.
     * @since 0.1.10
     */
    public VerboseRunnable(final Callable<?> callable, final boolean swallow) {
        this(callable, swallow, true);
    }

    /**
     * Default constructor.
     *
     * @param callable Callable to wrap
     * @param swallow  Shall we swallow exceptions                 ({@code TRUE}) or re-throw                 ({@code FALSE})? Exception swallowing means that {@link #run()}                 will never throw any exceptions (in any case all exceptions are logged                 using {@link Logger}.
     * @param vrbs     Shall we report the entire                 stacktrace of the exception                 ({@code TRUE}) or just its message in one line ({@code FALSE})
     * @since 0.7.17
     */
    @SuppressWarnings("PMD.AvoidCatchingGenericException")
    public VerboseRunnable(final Callable<?> callable,
                           final boolean swallow, final boolean vrbs) {
        this(
                new Runnable() {
                    @Override
                    public void run() {
                        try {
                            callable.call();
                        } catch (final InterruptedException ex) {
                            Thread.currentThread().interrupt();
                            throw new IllegalStateException(ex);
                            // @checkstyle IllegalCatch (1 line)
                        } catch (final Exception ex) {
                            throw new IllegalStateException(ex);
                        }
                    }

                    @Override
                    public String toString() {
                        return callable.toString();
                    }
                },
                swallow,
                vrbs
        );
    }

    /**
     * Default constructor, with configurable behavior for exceptions.
     *
     * @param runnable Runnable to wrap
     * @param swallow  Shall we swallow exceptions                 ({@code TRUE}) or re-throw                 ({@code FALSE})? Exception swallowing means that {@link #run()}                 will never throw any exceptions (in any case all exceptions are logged                 using {@link Logger}.
     * @since 0.1.4
     */
    public VerboseRunnable(final Runnable runnable, final boolean swallow) {
        this(runnable, swallow, true);
    }

    /**
     * Default constructor, with fully configurable behavior.
     *
     * @param runnable Runnable to wrap
     * @param swallow  Shall we swallow exceptions                 ({@code TRUE}) or re-throw                 ({@code FALSE})? Exception swallowing means that {@link #run()}                 will never throw any exceptions (in any case all exceptions are logged                 using {@link Logger}.
     * @param vrbs     Shall we report the entire                 stacktrace of the exception                 ({@code TRUE}) or just its message in one line ({@code FALSE})
     * @since 0.7.17
     */
    @SuppressWarnings("PMD.BooleanInversion")
    public VerboseRunnable(final Runnable runnable,
                           final boolean swallow, final boolean vrbs) {
        this.origin = runnable;
        this.rethrow = !swallow;
        this.verbose = vrbs;
    }

    /**
     * {@inheritDoc}
     * <p>
     * <p>We catch {@link RuntimeException} and {@link Error} here. All other
     * types of exceptions are "checked exceptions" and won't be thrown out
     * of {@code Runnable#run()} method.
     */
    @Override
    @SuppressWarnings("PMD.AvoidCatchingGenericException")
    public void run() {
        try {
            this.origin.run();
            // @checkstyle IllegalCatch (1 line)
        } catch (final RuntimeException ex) {
            if (this.rethrow) {
                logger.warn("escalated exception: %s", this.tail(ex));
                throw ex;
            }
            logger.warn("swallowed exception: %s", this.tail(ex));
            // @checkstyle IllegalCatch (1 line)
        } catch (final Error error) {
            if (this.rethrow) {
                logger.error("escalated error: %s", this.tail(error));
                throw error;
            }
            logger.error("swallowed error: %s", this.tail(error));
        }
        try {
            TimeUnit.MICROSECONDS.sleep(1L);
        } catch (final InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException(ex);
        }
    }

    /**
     * Make a tail of the error/warning message, using the exception thrown.
     *
     * @param throwable The exception/error caught
     * @return The message to show in logs
     */
    private String tail(final Throwable throwable) {
        final String tail;
        if (this.verbose) {
            tail = String.format("%[exception]s", throwable);
        } else {
            tail = String.format(
                    "%[type]s('%s')",
                    throwable,
                    throwable.getMessage()
            );
        }
        return tail;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof VerboseRunnable)) return false;
        final VerboseRunnable other = (VerboseRunnable) o;
        if (!other.canEqual((Object) this)) return false;
        return true;
    }

    public int hashCode() {
        int result = 1;
        return result;
    }

    /**
     * Can equal boolean.
     *
     * @param other the other
     * @return the boolean
     */
    protected boolean canEqual(Object other) {
        return other instanceof VerboseRunnable;
    }

    public String toString() {
        return "br.ufrgs.inf.prosoft.adaptivecaching.monitoring.util.VerboseRunnable(logger=" + this.logger + ", origin=" + this.origin + ", rethrow=" + this.rethrow + ", verbose=" + this.verbose + ")";
    }
}
