package org.xbib.logging.log4j;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.status.StatusData;
import org.apache.logging.log4j.status.StatusListener;
import org.apache.logging.log4j.status.StatusLogger;
import org.xbib.logging.LogContext;
import org.xbib.logging.Logger;

/**
 * A status logger which logs to a LogManager Logger.
 */
public class XbibStatusListener implements StatusListener {

    private static final String NAME = "org.xbib.logging.log4j.status";

    private static final Logger.AttachmentKey<StatusListener> STATUS_LISTENER_KEY = new Logger.AttachmentKey<>();

    private final Logger logger;

    private final LevelTranslator levelTranslator;

    private XbibStatusListener(final Logger logger, final LevelTranslator levelTranslator) {
        this.logger = logger;
        this.levelTranslator = levelTranslator;
    }

    /**
     * Registers a status listener with the log context if one does not already exist.
     *
     * @param logContext the log context to possibly register the status listener with
     */
    public static void registerIfAbsent(final LogContext logContext) {
        final LevelTranslator levelTranslator = LevelTranslator.getInstance();
        Logger logger = logContext.getLoggerIfExists(NAME);
        if (logger == null) {
            logger = logContext.getLogger(NAME);
            logger.setLevel(levelTranslator.translateLevel(StatusLogger.getLogger().getFallbackListener().getStatusLevel()));
        }
        StatusListener listener = logger.getAttachment(STATUS_LISTENER_KEY);
        if (listener == null) {
            listener = new XbibStatusListener(logger, levelTranslator);
            if (attachIfAbsent(logger, listener) == null) {
                StatusLogger.getLogger().registerListener(listener);
            }
        }
    }

    /**
     * Removes the status listener from the log context.
     *
     * @param logContext the log context to remove the status listener from
     */
    public static void remove(final LogContext logContext) {
        final Logger logger = logContext.getLoggerIfExists(NAME);
        if (logger != null) {
            detach(logger);
        }
    }

    @Override
    public void log(final StatusData data) {
        // Verify we can log at this level
        if (getStatusLevel().isLessSpecificThan(data.getLevel())) {
            logger.log(
                    levelTranslator.translateLevel(data.getLevel()),
                    data.getMessage().getFormattedMessage(),
                    data.getThrowable());
        }
    }

    @Override
    public Level getStatusLevel() {
        return levelTranslator.translateLevel(logger.getLevel());
    }

    @Override
    public void close() {
        detach(logger);
    }

    private static StatusListener attachIfAbsent(final Logger logger, final StatusListener value) {
        return logger.attachIfAbsent(STATUS_LISTENER_KEY, value);
    }

    private static void detach(final Logger logger) {
        final StatusListener listener = logger.detach(STATUS_LISTENER_KEY);
        if (listener != null) {
            StatusLogger.getLogger().removeListener(listener);
        }
    }
}
