package org.xbib.logging;

import java.util.EnumMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * Implements the {@code System.LoggerFinder}. It will make an attempt to set the {@code java.util.logging.manager}
 * system property before a logger is accessed.
 */
public class LoggerFinder extends System.LoggerFinder {

    private static final Map<System.Logger.Level, java.util.logging.Level> LEVELS = new EnumMap<>(System.Logger.Level.class);

    private static final AtomicBoolean LOGGED = new AtomicBoolean(false);

    private static volatile boolean PROPERTY_SET = false;

    static {
        LEVELS.put(System.Logger.Level.ALL, Level.ALL);
        LEVELS.put(System.Logger.Level.TRACE, Level.TRACE);
        LEVELS.put(System.Logger.Level.DEBUG, Level.DEBUG);
        LEVELS.put(System.Logger.Level.INFO, Level.INFO);
        LEVELS.put(System.Logger.Level.WARNING, Level.WARN);
        LEVELS.put(System.Logger.Level.ERROR, Level.ERROR);
        LEVELS.put(System.Logger.Level.OFF, Level.OFF);
    }

    public LoggerFinder() {
        super();
    }

    @Override
    public System.Logger getLogger(final String name, final Module module) {
        if (!PROPERTY_SET) {
            synchronized (this) {
                if (!PROPERTY_SET) {
                    if (System.getProperty("java.util.logging.manager") == null) {
                        System.setProperty("java.util.logging.manager", "org.xbib.logging.LogManager");
                    }
                }
                PROPERTY_SET = true;
            }
        }
        final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(name);
        if (!logger.getClass().getName().equals("org.xbib.logging.Logger")) {
            if (LOGGED.compareAndSet(false, true)) {
                logger.log(Level.WARN,
                        "The LogManager accessed before the \"java.util.logging.manager\" system property was set to \"org.xbib.logging.LogManager\". Results may be unexpected.");
            }
        }
        return new SystemLogger(logger);
    }

    private static class SystemLogger implements System.Logger {
        private static final String LOGGER_CLASS_NAME = SystemLogger.class.getName();
        private final java.util.logging.Logger delegate;

        private SystemLogger(final java.util.logging.Logger delegate) {
            this.delegate = delegate;
        }

        @Override
        public String getName() {
            return delegate.getName();
        }

        @Override
        public boolean isLoggable(final Level level) {
            return delegate.isLoggable(LEVELS.getOrDefault(level, java.util.logging.Level.INFO));
        }

        @Override
        public void log(final Level level, final String msg) {
            log(level, null, msg, (Object[]) null);
        }

        @Override
        public void log(final Level level, final Supplier<String> msgSupplier) {
            if (isLoggable(level)) {
                log(level, null, msgSupplier.get(), (Object[]) null);
            }
        }

        @Override
        public void log(final Level level, final Object obj) {
            if (isLoggable(level)) {
                this.log(level, null, obj.toString(), (Object[]) null);
            }
        }

        @Override
        public void log(final Level level, final String msg, final Throwable thrown) {
            this.log(level, null, msg, thrown);
        }

        @Override
        public void log(final Level level, final Supplier<String> msgSupplier, final Throwable thrown) {
            if (isLoggable(level)) {
                this.log(level, null, msgSupplier.get(), thrown);
            }
        }

        @Override
        public void log(final Level level, final String format, final Object... params) {
            this.log(level, null, format, params);
        }

        @Override
        public void log(final Level level, final ResourceBundle bundle, final String msg, final Throwable thrown) {
            final ExtLogRecord record = new ExtLogRecord(LEVELS.getOrDefault(level, java.util.logging.Level.INFO), msg,
                    LOGGER_CLASS_NAME);
            record.setThrown(thrown);
            record.setResourceBundle(bundle);
            delegate.log(record);
        }

        @Override
        public void log(final Level level, final ResourceBundle bundle, final String format, final Object... params) {
            final ExtLogRecord record = new ExtLogRecord(LEVELS.getOrDefault(level, java.util.logging.Level.INFO), format,
                    ExtLogRecord.FormatStyle.MESSAGE_FORMAT, LOGGER_CLASS_NAME);
            record.setParameters(params);
            record.setResourceBundle(bundle);
            delegate.log(record);
        }
    }
}
