package de.renew.gui.configure;

import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Properties;
import javax.swing.JOptionPane;

import de.renew.application.SimulatorPlugin;
import de.renew.draw.ui.api.MenuApi;
import de.renew.gui.CPNApplication;
import de.renew.gui.ConfigureSimulationTabController;
import de.renew.gui.GuiPlugin;
import de.renew.plugin.PluginManager;
import de.renew.simulator.api.SimulationManager;


/**
 * Manages a configuration dialog for the Renew simulation. The
 * configuration is based on and affects the properties of the
 * Renew Simulator plugin.
 * <p>
 * Several theme-specific option tabs can be added. Each tab has
 * to be managed by a {@link ConfigureSimulationTabController}
 * implementation.
 * </p>
 *
 * @author Michael Duvigneau
 * @since Renew 2.0
 */
public class ConfigureSimulationController {
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(ConfigureSimulationController.class);

    /**
     * Refers to the Swing dialog managed by this controller.
     **/
    private final ConfigureSimulationDialog dialog;

    /**
     * The displayed {@link ConfigureSimulationTabController} of the dialog
     */
    private final ConfigureSimulationTabController[] tabControllers;

    /**
     * Creates the controller and its associated dialog.
     * The frame of the given application is used as parent
     * for the dialog frame. Some well-known option tabs
     * are added to the dialog as well.
     *
     * @param app the application where the dialog should
     *            belong to.
     **/
    public ConfigureSimulationController(CPNApplication app) {
        tabControllers = GuiPlugin.getCurrent().getConfigTabController();
        Component[] initialTabs = new Component[tabControllers.length];
        for (int i = 0; i < tabControllers.length; i++) {
            initialTabs[i] = tabControllers[i].getTab();
        }

        this.dialog = new ConfigureSimulationDialog(app.getFrame(), initialTabs);
        this.dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent evt) {
                closeDialog();
            }
        });
        this.dialog.addCloseActionListener(evt -> closeDialog());
        this.dialog.addOkActionListener(evt -> {
            commitDialog();
            closeDialog();
        });
        this.dialog.addUpdateSimulationActionListener(evt -> updateDialogFromSimulation());
        this.dialog.addUpdateActionListener(evt -> updateDialog());
        this.dialog.addApplyActionListener(evt -> commitDialog());
    }

    /**
     * Displays the dialog. Also updates the dialog, if it has not been
     * visible beforehand.
     **/
    public void showDialog() {
        if (!dialog.isVisible()) {
            updateDialog();
            dialog.setVisible(true);
            MenuApi.registerWindowsMenuDialog(MenuApi.getWindowsCategoryNameTools(), dialog);
        }
    }

    /**
     * Hides the dialog from view.
     **/
    public void closeDialog() {
        MenuApi.unregisterWindowsMenuDialog(dialog);
        dialog.setVisible(false);
    }

    /**
     * Instructs all option tab controllers to write their
     * current settings to the simulator plugin's properties.
     * This method is intended to be called upon user request
     * only.
     **/
    public void commitDialog() {
        Properties props = getSimulatorPluginProperties();
        for (ConfigureSimulationTabController tabController : tabControllers) {
            tabController.commitTab(props);
        }
    }

    /**
     * Instructs all option tab controllers to bring their
     * current settings in line with the simulator plugin's
     * properties.
     * This method is intended to be called upon user request
     * or upon dialog initialisation.
     **/
    public void updateDialog() {
        Properties props = getSimulatorPluginProperties();
        for (ConfigureSimulationTabController tabController : tabControllers) {
            tabController.updateTab(props);
        }
    }

    /**
     * Instructs all option tab controllers to bring their
     * current settings in line with the current simulation's
     * properties.
     * This method is intended to be called upon user request
     * only.
     **/
    public void updateDialogFromSimulation() {
        Properties props = getSimulationProperties();
        if (props != null) {
            for (ConfigureSimulationTabController tabController : tabControllers) {
                tabController.updateTab(props);
            }
        } else {
            JOptionPane.showMessageDialog(
                dialog,
                "Could not update properties from current simulation:\n" + "No simulation running.",
                "Configure Simulation", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * Fetches the current properties of the Renew Simulator plugin.
     *
     * @return the properties object of the simulator plugin.
     **/
    private static Properties getSimulatorPluginProperties() {
        logger.debug(
            "ConfigureSimulationController: " + "Reading the simulator plugin's properties.");
        return getSimulatorPlugin().getProperties();
    }

    /**
     * Fetches the properties of the current simulation
     * environment. If there is no running simulation,
     * <code>null</code> is returned instead.
     *
     * @return the properties object of the current simulation or
     *         the <code>null</code>, depending on whether there
     *         is a running simulation.
     **/
    private static Properties getSimulationProperties() {
        Properties props = SimulationManager.getSimulationProperties();
        if (props != null) {
            logger.debug(
                "ConfigureSimulationController: " + "Read the current simulation's properties.");
        } else {
            logger.debug(
                "ConfigureSimulationController: "
                    + "No current simulation available to read properties.");
        }
        return props;
    }

    /**
     * Determines the Renew Simulator plugin instance.
     *
     * @return the first plugin providing <code>de.renew.simulator</code>.
     */
    private static SimulatorPlugin getSimulatorPlugin() {
        return (SimulatorPlugin) PluginManager.getInstance()
            .getPluginsProviding("de.renew.simulator").iterator().next();
    }
}