package be.iminds.ilabt.jfed.logging;

import be.iminds.ilabt.jfed.preferences.CorePreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.URL;

/**
 * JFedLoggerManager
 */
@Singleton
public class JFedLoggerManager {
    private static final Logger LOG = LoggerFactory.getLogger(JFedLoggerManager.class);

    private final JFedGuiPreferences preferences;

    @Inject
    public JFedLoggerManager(JFedGuiPreferences preferences) {
        this.preferences = preferences;
        tryInitializeBugReportAppender();
        updateLoggers();
    }

    private static Level getLevel(final String levelName) {
        if (levelName == null)
            return null;
        if (levelName.equalsIgnoreCase("trace"))
            return Level.TRACE;
        if (levelName.equalsIgnoreCase("debug"))
            return Level.DEBUG;
        if (levelName.equalsIgnoreCase("info"))
            return Level.INFO;
        if (levelName.equalsIgnoreCase("warn"))
            return Level.WARN;
        if (levelName.equalsIgnoreCase("error"))
            return Level.ERROR;
        return null;
    }

    public void testLogging() {
        LOG.trace("TRACE test");
        LOG.debug("DEBUG test");
        LOG.info("INFO test");
        LOG.warn("WARN test");
        LOG.error("ERROR test");
    }

    public BugReportAppender getBugReportMemoryAppender() {
        org.slf4j.Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
        if (!(rootLogger instanceof ch.qos.logback.classic.Logger)) {
            LOG.error("rootLogger is not instance of ch.qos.logback.classic.Logger but of " + rootLogger.getClass().getName());
            return null;
        }
        ch.qos.logback.classic.Logger logbackRoot = (ch.qos.logback.classic.Logger) rootLogger;

        Appender appender = logbackRoot.getAppender(BugReportAppender.DEFAULT_NAME);
        if (appender == null) {
            LOG.error("BUGREPORT appender is not found");
            return null;
        }

        if (!(appender instanceof BugReportAppender)) {
            LOG.error("BUGREPORT appender is not a BugReportAppender but a " + appender.getClass().getName() + "");
            return null;
        }

        return (BugReportAppender) appender;
    }

    public void updateLoggers() {
        if (!preferences.getBoolean(CorePreferenceKey.PREF_LOGGING_OVERWRITE)) {
            LOG.info("jFed is configured not to change the automatic logging configuration: Will reload default config.");
            LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            ContextInitializer ci = new ContextInitializer(loggerContext);
            URL url = ci.findURLOfDefaultConfigurationFile(true);

            try {
                JoranConfigurator configurator = new JoranConfigurator();
                configurator.setContext(loggerContext);
                loggerContext.reset();
                configurator.doConfigure(url);
            } catch (JoranException je) {
                // StatusPrinter will handle this
            }
            StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);

        } else {

            LOG.info("jFed will update the logging configuration.");

            org.slf4j.Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
            if (!(rootLogger instanceof ch.qos.logback.classic.Logger)) {
                LOG.error("rootLogger is not instance of ch.qos.logback.classic.Logger but of " + rootLogger.getClass().getName());
                System.out.println("NOTE: logging cannot be modified by jFed: rootLogger is not instance of ch.qos.logback.classic.Logger but of " + rootLogger.getClass().getName());
                return;
            }
            ch.qos.logback.classic.Logger logbackRoot = (ch.qos.logback.classic.Logger) rootLogger;

            //loggers
            Level rootLevel = getLevel(preferences.getString(CorePreferenceKey.PREF_LOGGING_ROOT_LEVEL));
            Level terminalLevel = getLevel(preferences.getString(CorePreferenceKey.PREF_LOGGING_EXTTERM_LEVEL));

            if (rootLevel != null) {
                Level origLevel = logbackRoot.getLevel();
                if (origLevel.toInt() != rootLevel.toInt()) {
                    LOG.info("jFed changes the root log level from " + origLevel.toString() + " to " + rootLevel.toString());
                    logbackRoot.setLevel(rootLevel);
                } else
                    LOG.info("jFed leaves the root log level unchanged at " + rootLevel);
            } else
                LOG.info("No root level specified in config: " + preferences.getString(CorePreferenceKey.PREF_LOGGING_ROOT_LEVEL));

            if (terminalLevel != null) {
                org.slf4j.Logger logger = LoggerFactory.getLogger("be.iminds.ilabt.jfed.ssh_terminal_tool");

                if (logger instanceof ch.qos.logback.classic.Logger) {
                    ch.qos.logback.classic.Logger termLogger = (ch.qos.logback.classic.Logger) logger;
                    Level origLevel = termLogger.getLevel();
                    if (origLevel.toInt() != terminalLevel.toInt()) {
                        LOG.debug("Changing external terminal log level from " + origLevel + " to " + terminalLevel);
                        termLogger.setLevel(terminalLevel);
                    } else
                        LOG.debug("Leaving external terminal log level unchanged at " + terminalLevel);
                } else {
                    LOG.error("external terminal Logger is not instance of ch.qos.logback.classic.Logger but of " + logger.getClass().getName() + " -> Cannot be changed.");
                }
            }

            //special, if debug CPU is on, use rootLevel for all connection logging
            Level lowlevelConnectionLevel = preferences.getBoolean(CorePreferenceKey.PREF_DEBUG_CPU) ? rootLevel : /*Level.INFO*/null;
            if (lowlevelConnectionLevel != null) {
                org.slf4j.Logger logger = LoggerFactory.getLogger("be.iminds.ilabt.jfed.lowlevel.connection");

                if (logger instanceof ch.qos.logback.classic.Logger) {
                    ch.qos.logback.classic.Logger termLogger = (ch.qos.logback.classic.Logger) logger;
                    Level origLevel = termLogger.getLevel();
                    if (origLevel.toInt() != lowlevelConnectionLevel.toInt()) {
                        LOG.debug("Changing lowlevel connection log level from " + origLevel + " to " + lowlevelConnectionLevel);
                        termLogger.setLevel(lowlevelConnectionLevel);
                    } else
                        LOG.debug("Leaving lowlevel connection log level unchanged at " + lowlevelConnectionLevel);
                } else {
                    LOG.error("lowlevel connection Logger is not instance of ch.qos.logback.classic.Logger but of " + logger.getClass().getName() + " -> Cannot be changed.");
                }
            }
        }

        tryInitializeBugReportAppender();
    }

    private void tryInitializeBugReportAppender() {
        org.slf4j.Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

        if (!(rootLogger instanceof ch.qos.logback.classic.Logger)) {
            LOG.error("rootLogger is not instance of ch.qos.logback.classic.Logger but of " + rootLogger.getClass().getName());
            System.out.println("NOTE: logging cannot be modified by jFed: rootLogger is not instance of ch.qos.logback.classic.Logger but of " + rootLogger.getClass().getName());
            return;
        }
        final ch.qos.logback.classic.Logger logbackRootLogger = (ch.qos.logback.classic.Logger) rootLogger;

        //check if the bugreport-appender already exists
        if (logbackRootLogger.getAppender(BugReportAppender.DEFAULT_NAME) != null) {
            LOG.trace("An existing BugReportAppender has been found. Not registering again");
            return;
        }

        BugReportAppender bugReportAppender = new BugReportAppender();


        bugReportAppender.setContext(logbackRootLogger.getLoggerContext());
        logbackRootLogger.addAppender(bugReportAppender);

        bugReportAppender.start();

        LOG.debug("Registered bugReportAppender on the rootLogger");
    }

}
