package ch.qos.logback.classic.joran;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.gaffer.GafferUtil;
import ch.qos.logback.classic.util.EnvUtil;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.event.SaxEvent;
import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.status.StatusUtil;

public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {

    public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files.";
    static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point";
    static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration.";

    
    
    long birthdate = System.currentTimeMillis();
    List<ReconfigureOnChangeTaskListener> listeners;
    
    
    void addListener(ReconfigureOnChangeTaskListener listener) {
        if(listeners==null)
            listeners = new ArrayList<ReconfigureOnChangeTaskListener>();
        listeners.add(listener);
    }
    
    @Override
    public void run() {
        fireEnteredRunMethod();
        
        ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
        if (configurationWatchList == null) {
            addWarn("Empty ConfigurationWatchList in context");
            return;
        }

        List<File> filesToWatch = configurationWatchList.getCopyOfFileWatchList();
        if (filesToWatch == null || filesToWatch.isEmpty()) {
            addInfo("Empty watch file list. Disabling ");
            return;
        }

        if (!configurationWatchList.changeDetected()) {
            return;
        }

        fireChangeDetected();
        URL mainConfigurationURL = configurationWatchList.getMainURL();

        addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
        addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");

        LoggerContext lc = (LoggerContext) context;
        if (mainConfigurationURL.toString().endsWith("xml")) {
            performXMLConfiguration(lc, mainConfigurationURL);
        } else if (mainConfigurationURL.toString().endsWith("groovy")) {
            if (EnvUtil.isGroovyAvailable()) {
                lc.reset();
                // avoid directly referring to GafferConfigurator so as to avoid
                // loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
                GafferUtil.runGafferConfiguratorOn(lc, this, mainConfigurationURL);
            } else {
                addError("Groovy classes are not available on the class path. ABORTING INITIALIZATION.");
            }
        }
        fireDoneReconfiguring();
    }

    private void fireEnteredRunMethod() {
        if(listeners == null)
            return;
        
        for(ReconfigureOnChangeTaskListener listener: listeners)
            listener.enteredRunMethod();
    }

    private void fireChangeDetected() {
        if(listeners == null)
            return;
        
        for(ReconfigureOnChangeTaskListener listener: listeners)
            listener.changeDetected();
    }


    private void fireDoneReconfiguring() {
        if(listeners == null)
            return;
        
        for(ReconfigureOnChangeTaskListener listener: listeners)
            listener.doneReconfiguring();
    }

    private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) {
        JoranConfigurator jc = new JoranConfigurator();
        jc.setContext(context);
        StatusUtil statusUtil = new StatusUtil(context);
        List<SaxEvent> eventList = jc.recallSafeConfiguration();

        URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
        lc.reset();
        long threshold = System.currentTimeMillis();
        try {
            jc.doConfigure(mainConfigurationURL);
            if (statusUtil.hasXMLParsingErrors(threshold)) {
                fallbackConfiguration(lc, eventList, mainURL);
            }
        } catch (JoranException e) {
            fallbackConfiguration(lc, eventList, mainURL);
        }
    }

    private List<SaxEvent> removeIncludeEvents(List<SaxEvent> unsanitizedEventList) {
        List<SaxEvent> sanitizedEvents = new ArrayList<SaxEvent>();
        if (unsanitizedEventList == null)
            return sanitizedEvents;

        for (SaxEvent e : unsanitizedEventList) {
            if (!"include".equalsIgnoreCase(e.getLocalName()))
                sanitizedEvents.add(e);

        }
        return sanitizedEvents;
    }

    private void fallbackConfiguration(LoggerContext lc, List<SaxEvent> eventList, URL mainURL) {
        // failsafe events are used only in case of errors. Therefore, we must *not*
        // invoke file inclusion since the included files may be the cause of the error.

        List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList);
        JoranConfigurator joranConfigurator = new JoranConfigurator();
        joranConfigurator.setContext(context);
        ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context);
        ConfigurationWatchList newCWL = oldCWL.buildClone();
        
        if (failsafeEvents == null || failsafeEvents.isEmpty()) {
            addWarn("No previous configuration to fall back on.");
        } else {
            addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION);
            try {
                lc.reset();
                ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL);
                joranConfigurator.doConfigure(failsafeEvents);
                addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION);
                joranConfigurator.registerSafeConfiguration(eventList);
                
                addInfo("after registerSafeConfiguration: " + eventList);
            } catch (JoranException e) {
                addError("Unexpected exception thrown by a configuration considered safe.", e);
            }
        }
    }

    @Override
    public String toString() {
        return "ReconfigureOnChangeTask(born:" + birthdate + ")";
    }
}