package de.renew.logging.gui;

import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import javax.swing.JFileChooser;
import javax.swing.JScrollPane;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;

import org.apache.log4j.Appender;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.config.PropertyPrinter;
import org.apache.log4j.spi.OptionHandler;

import CH.ifa.draw.DrawPlugin;
import de.renew.engine.common.SimulatorEventLogger;
import de.renew.gui.ConfigureSimulationTabController;


/**
 * An option panel controller to configure concurrent features of
 * the simulation engine.
 *
 * @author Sven Offermann
 **/
public class ConfigureLoggingController
    implements ConfigureSimulationTabController, TreeSelectionListener
{
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ConfigureLoggingController.class);

    private ConfigureLoggingTab _tab;
    private HashMap<Appender, GenericAppenderEditor> _appenders;

    /**
     * Configures a Tab and adds the {@link MutableTreeNode}.
     */
    public ConfigureLoggingController() {
        this._tab = new ConfigureLoggingTab(this);
        this._tab.setRootNode(createLoggerTreeNodes());
        this._appenders = new HashMap<>();
    }

    @Override
    public Component getTab() {
        return _tab;
    }

    private MutableTreeNode createLoggerTreeNodes() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("defined loggers");

        Enumeration<?> ls = LogManager.getCurrentLoggers();
        while (ls.hasMoreElements()) {
            Logger logger = (Logger) ls.nextElement();
            if (logger.getName().startsWith(SimulatorEventLogger.SIM_LOG_PREFIX)) {
                DefaultMutableTreeNode loggerNode =
                    new DefaultMutableTreeNode(new TreeNodeLoggerWrapper(logger));

                boolean hasAppender = false;
                Enumeration<?> as = logger.getAllAppenders();
                while (as.hasMoreElements()) {
                    hasAppender = true;
                    Appender a = (Appender) as.nextElement();
                    if (!(a instanceof org.apache.log4j.varia.NullAppender)) {
                        DefaultMutableTreeNode aNode =
                            new DefaultMutableTreeNode(new TreeNodeAppenderWrapper(logger, a));
                        loggerNode.add(aNode);
                    }
                }

                if (hasAppender) {
                    root.add(loggerNode);
                }
            }
        }

        return root;
    }

    @Override
    public void commitTab(Properties props) {
        Enumeration<?> ls = LogManager.getCurrentLoggers();
        while (ls.hasMoreElements()) {
            Logger logger = (Logger) ls.nextElement();
            if (logger.getName().startsWith(SimulatorEventLogger.SIM_LOG_PREFIX)) {
                Enumeration<?> as = logger.getAllAppenders();
                while (as.hasMoreElements()) {
                    Appender a = (Appender) as.nextElement();
                    GenericAppenderEditor ae = _appenders.get(a);
                    if (ae != null) {
                        ae.applyChanges();
                        if (a instanceof OptionHandler) {
                            ((OptionHandler) a).activateOptions();
                            Logger.getLogger(logger.getName()).addAppender(a);
                        }
                    }
                }
            }
        }
    }

    /**
     *
     * @param props the properties to get actual values from.
     *              This will most probably be the current
     *              simulation's properties.
     */
    @Override
    public void updateTab(Properties props) {
        this._appenders = new HashMap<>();
        updateTab();
    }

    private void updateTab() {
        this._tab.setRootNode(createLoggerTreeNodes());
    }

    /**
     * @return {@link ActionListener}
     */
    protected ActionListener createAddLoggerAction() {
        return new AddLoggerAction();
    }

    /**
     * @param loggerName name of the logger
     * @return {@link RemoveLoggerAction}
     */
    protected ActionListener createRemoveLoggerAction(String loggerName) {
        return new RemoveLoggerAction(loggerName);
    }

    /**
     *
     * @param loggerName name of the logger
     * @param appenderTypeName name of the typename
     * @return {@link AddAppenderAction}
     */
    protected ActionListener createAddAppenderAction(String loggerName, String appenderTypeName) {
        return new AddAppenderAction(loggerName, appenderTypeName);
    }

    /**
     *
     * @param loggerName name of the logger
     * @param appender {@link Appender}
     * @return {@link RemoveAppenderAction}
     */
    protected ActionListener createRemoveAppenderAction(String loggerName, Appender appender) {
        return new RemoveAppenderAction(loggerName, appender);
    }

    /**
     *
     * @return new {@link ExportConfigurationAction}
     */
    protected ActionListener createExportConfigurationAction() {
        return new ExportConfigurationAction();
    }


    // Implementation of TreeSelectionListener
    @Override
    public void valueChanged(TreeSelectionEvent e) {
        Object selected =
            ((DefaultMutableTreeNode) e.getPath().getLastPathComponent()).getUserObject();
        if (selected instanceof TreeNodeAppenderWrapper) {
            Appender appender = ((TreeNodeAppenderWrapper) selected).getAppender();
            if (!_appenders.containsKey(appender)) {
                _appenders.put(appender, new GenericAppenderEditor(appender));
            }
            JScrollPane rightComponent = new JScrollPane(_appenders.get(appender));
            rightComponent.setMinimumSize(new Dimension(300, 200));
            _tab.setRightSide(rightComponent);

        }
    }

    // Inner action classes.
    private class AddLoggerAction implements ActionListener {
        public AddLoggerAction() {}

        @Override
        public void actionPerformed(ActionEvent e) {
            boolean found = false;
            Logger logger = null;

            NewLoggerDialog newLoggerDialog = new NewLoggerDialog(getParentDialog());
            newLoggerDialog.setVisible(true);
            if (newLoggerDialog.isCommitted()) {
                while (!found) {
                    String loggerName =
                        SimulatorEventLogger.SIM_LOG_PREFIX + "." + newLoggerDialog.getLogger();

                    logger = Logger.getLogger(loggerName);
                    Enumeration<?> enumeration = logger.getAllAppenders();
                    if (!enumeration.hasMoreElements()) {
                        found = true;
                    }
                }
            }

            //NOTICE null
            if (logger != null) {
                logger.addAppender(new org.apache.log4j.varia.NullAppender());
            }

            updateTab();
        }

        private Dialog getParentDialog() {
            Component component = _tab;
            while (!(component instanceof Dialog)) {
                component = component.getParent();
            }
            return (Dialog) component;
        }
    }

    private class RemoveLoggerAction implements ActionListener {
        private String _loggerName;

        public RemoveLoggerAction(String loggerName) {
            this._loggerName = loggerName;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // get logger and remove all appenders;
            Logger logger = Logger.getLogger(_loggerName);
            logger.removeAllAppenders();

            updateTab();
        }
    }

    private class AddAppenderAction implements ActionListener {
        private String _loggerName;
        private String _appenderTypeName;

        public AddAppenderAction(String loggerName, String appenderTypeName) {
            this._loggerName = loggerName;
            this._appenderTypeName = appenderTypeName;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                Appender appender =
                    AppenderFactory.getInstance().createNewAppender(_appenderTypeName);

                Logger.getLogger(_loggerName).addAppender(appender);

                updateTab();
            } catch (Exception ex) {
                LOGGER.error(
                    "Can't create new appender instance of appender type " + _appenderTypeName);
                LOGGER.error(ex.getMessage(), ex);
            }
        }
    }

    private class RemoveAppenderAction implements ActionListener {
        private String _loggerName;
        private Appender _appender;

        public RemoveAppenderAction(String loggerName, Appender appender) {
            this._loggerName = loggerName;
            this._appender = appender;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Logger.getLogger(_loggerName).removeAppender(_appender);

            updateTab();
        }
    }

    private class ExportConfigurationAction implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent arg0) {
            File propertiesOutputFile = promptGenerateLocation();

            if (propertiesOutputFile != null) {
                // If file does not exist, try to create a new one.
                if (!propertiesOutputFile.exists()) {
                    try {
                        propertiesOutputFile.createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                PrintWriter pw;
                try {
                    pw = new PrintWriter(propertiesOutputFile);
                    PropertyPrinter pp = new PropertyPrinter(pw);
                    pp.print(pw);

                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * Prompt the user for a location where to generate the KBFiles to.
         * @return the location as "File" or null if the dialog is canceled or an error was created.
         */
        private File promptGenerateLocation() {
            File file = new File(DrawPlugin.getCurrent().getIOHelper().getLastPath(), "/");

            JFileChooser dialog = new JFileChooser();
            dialog.setCurrentDirectory(file);
            //dialog.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            dialog
                .setDialogTitle("Select a destination for the generated log4j configuration file.");

            if (dialog
                .showSaveDialog(DrawPlugin.getGui().getFrame()) == JFileChooser.APPROVE_OPTION) {
                file = dialog.getSelectedFile();
                DrawPlugin.getCurrent().getIOHelper().setLastPath(file.getParentFile());
            } else {
                return null;
            }
            return file;
        }
    }
}