package com.atlassian.bitbucket.util;

import com.atlassian.util.profiling.UtilTimerStack;

import javax.annotation.Nonnull;

import static com.google.common.base.Preconditions.checkNotNull;

public class TimerUtils {

    private TimerUtils() {
        throw new UnsupportedOperationException(getClass().getName() +
                " is a utility class and should not be instantiated");
    }

    /**
     * @return true if profiling is currently enabled
     */
    public static boolean isActive() {
        return UtilTimerStack.isActive();
    }

    /**
     * Creates a {@link Timer} which can be used to safely stop timing.
     * <p>
     * The normal usage pattern should look like this:
     * <code><pre>
     *
     * try (Timer timer = TimerUtils.start("some string")) {
     *     // Do something
     * }
     * </pre></code>
     * </p>
     *
     * @param name of the timer
     * @return a {@code Timer} callback
     */
    public static Timer start(@Nonnull String name) {
        return new DefaultTimer(name);
    }

    /**
     * Time the specified operation.
     * <p>
     * See also the example in {@link #start} for doing the same without having to construct an Operation object.
     *
     * @param name     of the timer
     * @param supplier which will be timed
     * @return the result of {@code operation}
     */
    public static <T, E extends Throwable> T time(String name, Operation<T, E> supplier) throws E {
        UtilTimerStack.push(name);
        try {
            return supplier.perform();
        } finally {
            UtilTimerStack.pop(name);
        }
    }

    private static class DefaultTimer implements Timer {

        private String currentName;
        private boolean stopped;

        private DefaultTimer(String currentName) {
            this.currentName = checkNotNull(currentName, "currentName");
            UtilTimerStack.push(currentName);
        }

        @Override
        public void close() {
            if (stopped) {
                if (DevModeUtils.isEnabled()) {
                    throw new IllegalStateException("Stop should only be called once");
                }
                return;
            }
            UtilTimerStack.pop(currentName);
            stopped = true;
        }

        @Override
        public void mark(@Nonnull String newName) {
            checkNotNull(newName, "newName");
            UtilTimerStack.pop(currentName);
            currentName = newName;
            UtilTimerStack.push(newName);
        }
    }
}
