package com.atlassian.bitbucket.mesh.logback;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.Context;
import com.atlassian.bitbucket.mesh.request.RequestManager;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static java.util.Objects.requireNonNull;

public class LogbackUtils {

    private static final String OBJ_REQUEST_MANAGER = RequestManager.class.getName();

    private LogbackUtils() {
        throw new UnsupportedOperationException(getClass().getSimpleName() + " is not intended to be instantiated");
    }

    /**
     * Binds the {@link RequestManager} to the {@link #getLoggerContext() LoggerContext} so it can be
     * {@link #getRequestManager retrieved} by logging code to attach request details to entries.
     *
     * @param requestManager the manager to bind
     */
    public static void bind(@Nonnull RequestManager requestManager) {
        requireNonNull(requestManager, "requestManager");

        getLoggerContext().putObject(OBJ_REQUEST_MANAGER, requestManager);
    }

    @Nonnull
    public static Level getEffectiveLevel(@Nonnull String loggerName) {
        return getLogger(loggerName).getEffectiveLevel();
    }

    @Nonnull
    public static LoggerContext getLoggerContext() {
        return (LoggerContext) LoggerFactory.getILoggerFactory();
    }

    @Nonnull
    public static Logger getLogger(@Nonnull String loggerName) {
        return getLoggerContext().getLogger(loggerName);
    }

    /**
     * Retrieves the {@link RequestManager} that has been {@link #bind(RequestManager) bound} to the
     * {@link #getLoggerContext LoggerContext}, if any.
     *
     * @return the {@link RequestManager}, or {@code null} if it has not been bound
     */
    @Nullable
    public static RequestManager getRequestManager() {
        return getRequestManager(getLoggerContext());
    }

    /**
     * Retrieves the {@link RequestManager} that has been {@link #bind(RequestManager) bound} to the
     * provided {@link Context}, if any.
     *
     * @param context the logging context to retrieve the {@link RequestManager} from
     * @return the {@link RequestManager}, or {@code null} if it has not been bound
     */
    @Nullable
    public static RequestManager getRequestManager(@Nonnull Context context) {
        return (RequestManager) requireNonNull(context, "context").getObject(OBJ_REQUEST_MANAGER);
    }

    @Nonnull
    public static Logger getRootLogger() {
        return getLogger(Logger.ROOT_LOGGER_NAME);
    }

    public static void setLevel(@Nonnull String loggerName, @Nullable String levelName) {
        Logger logger = getLogger(loggerName);

        Level effectiveLevel = logger.getEffectiveLevel();    //Get the logger's current effective level
        Level newLevel = toLevel(levelName, effectiveLevel);  //If the level isn't valid, use the current level
        if (!effectiveLevel.equals(newLevel)) { //Only set the level if it's different
            logger.setLevel(newLevel);
            logger.info("Switched to log level [{}]", levelName);
        }
    }

    @Nonnull
    public static Level toLevel(@Nullable String levelName) {
        return toLevel(levelName, Level.DEBUG);
    }

    //Nullability is based on the default. If the default is null, this method can return null; otherwise,
    //this method will always return non-null
    public static Level toLevel(@Nullable String levelName, @Nullable Level defaultLevel) {
        if ("false".equals(levelName)) {
            return Level.OFF;
        }

        return Level.toLevel(levelName, defaultLevel);
    }
}
