package de.renew.netcomponents;

import java.awt.Font;
import java.awt.event.KeyEvent;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import CH.ifa.draw.DrawPlugin;
import CH.ifa.draw.util.Command;
import CH.ifa.draw.util.CommandMenu;
import de.renew.draw.ui.api.CommandApi;
import de.renew.draw.ui.api.MenuApi;
import de.renew.draw.ui.ontology.AbstractCommand;
import de.renew.gui.CPNApplication;
import de.renew.gui.GuiPlugin;
import de.renew.plugin.PluginManager;
import de.renew.plugin.PluginProperties;
import de.renew.plugin.PropertyHelper;
import de.renew.plugin.annotations.Inject;
import de.renew.plugin.di.DIPlugin;
import de.renew.windowmanagement.Workbench;


/**
 * The wrapper for the ComponentsTool.
 * <pre>
 *
 *
 *  0.2 user can choose toolsdir directory via GUI
 *      palettes can be removed.
 *
 *  0.3 remove problems solved
 *      the default palette is part of the list
 *
 *  0.3.2 status message for nothing to do while removing nothing.
 *
 *  0.3.3 adapted to log4j logging
 *
 *  0.4   separating general functionality from the mulan components
 *        latter reside in plugin MulanComponents, now
 *
 *  0.4.2 Fixed read/write problems.
 *
 *  0.4.6 Adapted changed signature of constructor of ComponentsTool.
 *
 *  0.5.0 Fixed deregistering of plugins.
 *        Plugin-plugins can provide other <code>ToolButton</code> now.
 *               Changed interface of <code>ComponentsPluginExtender</code> and
 *        some method signatures.
 *
 *  0.6.0 Added possible visual representation of the net component figure.
 *        Made initialization of repository more robust.
 *        Removed necessity to include generic image: 1.gif.
 *
 * </pre>
 *
 * @author Lawrence Cabac
 * @version 0.6.0
 */
public class ComponentsToolPlugin extends DIPlugin {
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ComponentsToolPlugin.class);
    /** Class-wide {@link JFileChooser} initialized with Renew's working directory. */
    private static final JFileChooser CHOOSER = new JFileChooser(System.getProperty("user.dir"));

    private final Workbench _workbench;
    /*
     * Not used yet(?)
     */
    private CommandMenu _menu;

    /**
     * The default ComponentsTool.
     */
    private ComponentsTool _defCT;

    /**
     * The list of additional loaded ComponentsTools.
     */
    private Vector<ComponentsTool> _componentsToolList;
    static private URL _location;
    private HashMap<String, ComponentsPluginExtender> _pluginList;
    private Hashtable<AbstractCommand, ComponentsPluginExtender> _commandList;

    /**
     * Creates a new ComponentsToolPlugin with the given workbench and initializes other fields with empty Collections.
     * The default is set to null.
     *
     * @param workbench -
     *                an interface which provides different interactions methods for the two
     *                classes (WorkbenchImpl and WorkbenchProxy)
     */
    @Inject
    public ComponentsToolPlugin(Workbench workbench) {
        _componentsToolList = new Vector<ComponentsTool>();
        setDefaultCT(null);
        // setLocation(props.getURL());
        _pluginList = new HashMap<String, ComponentsPluginExtender>();
        _commandList = new Hashtable<>();
        _workbench = workbench;
    }

    /**
     * @see de.renew.plugin.IPlugin#init()
     */
    @Override
    public void init() {
        _menu = createMenu();

        //SeparatorFactory sepFac = new SeparatorFactory("de.renew.nc");
        //mm.registerMenu(DrawPlugin.TOOLS_MENU, sepFac.createSeparator());
        MenuApi.registerMenu(DrawPlugin.TOOLS_MENU, _menu);

        String toolDirProp = ComponentsTool.TOOLDIRPROPERTY;
        String toolDir = getProperties().getProperty(toolDirProp);
        if (toolDir != null) {
            System.setProperty(toolDirProp, toolDir);
            LOGGER.debug("NetComponents: " + toolDirProp + " set to " + toolDir);
            try {
                if (PropertyHelper.getBoolProperty(getProperties(), "nc.init")) {
                    LOGGER.info("Your init option: de.renew.nc.init is set to true.");
                    LOGGER.info("This option is not supported anymore, whatsoever.");
                }

                boolean init = PropertyHelper.getBoolProperty(getProperties(), "de.renew.nc.init");
                if (init) {
                    //since there is no default palette provided by this plugin
                    //the init option is ignored, here. Add an init option to
                    //(sub)plugin providing the palette.
                    //createPalette();
                    LOGGER.info("Your init option: de.renew.nc.init is set to true.");
                    LOGGER.info("However, this is not supported anymore.");
                    LOGGER.info(
                        "Please set the init option of the plugin providing your default palette.");
                }
            } catch (RuntimeException e) {
                // Ignore
            }
        } else {
            LOGGER.error("NetComponents: " + toolDirProp + " not set in plugin.cfg.");
        }
    }

    /**
     * Registers the given plugin.
     * Therefore, it adds the plugin to the registered plugins
     * and adds commands of the plugin to the menu and list of commands.
     *
     * @param plugin The plugin to be registered.
     */
    public void registerPlugin(ComponentsPluginExtender plugin) {
        _pluginList.put(plugin.getToolDirPath(), plugin);
        for (AbstractCommand command : plugin.getMenuCommands()) {
            _menu.add(command);
            _commandList.put(command, plugin);
            LOGGER.debug("Added command " + command.toString() + " of Plugin " + plugin);
        }
    }

    /**
     * Deregisters the given plugin.
     * Therefore, it removes the plugin from the registered plugins
     * and removes commands of the plugin from the menu and list of commands.
     *
     * @param plugin The plugin to be deregistered.
     */
    public void deregisterPlugin(ComponentsPluginExtender plugin) {
        Enumeration<AbstractCommand> it = _commandList.keys();

        // Removing all menu entries for the deregistering plugin.
        while (it.hasMoreElements()) {
            AbstractCommand command = it.nextElement();
            if (_commandList.get(command) == plugin) {
                _menu.remove(command);
                _commandList.remove(command);
            }
        }
        _pluginList.remove(plugin.getToolDirPath());

    }

    /**
     * Adds the given command to the menu and returns false.
     *
     * @param command The command to be added to the menu.
     * @return always false
     */
    public boolean addEntryToMenu(Command command) {
        _menu.add(command);
        return false;
    }

    /**
     * Removes the given command from the menu and returns false.
     *
     * @param command The command to be removed from the menu.
     * @return always false
     */
    public boolean removeEntryFromMenu(Command command) {
        _menu.remove(command);
        return false;
    }

    @Override
    public boolean cleanup() {
        unloadAllPalettes();
        MenuApi.unregisterMenu(_menu);
        return true;
    }

    private void unloadAllPalettes() {
        Iterator<ComponentsTool> it = _componentsToolList.iterator();
        while (it.hasNext()) {
            ComponentsTool ct = it.next();
            ct.remove();
        }
        _componentsToolList = new Vector<ComponentsTool>();
    }

    /**
     * Lets the user choose the tools' directory. Creates a new palette and
     * refreshes the menuFrame.
     *
     * @param name The name of the tools directory
     * @param paletteName The name of the palette to be created
     * @param plugin The plugin that offers the components
     *
     */
    public void createPalette(String name, String paletteName, ComponentsPluginExtender plugin) {
        File dir = null;
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(ComponentsToolPlugin.class.getSimpleName() + ": " + name);
        }
        try {
            dir = new File(new URI("file://" + name));
        } catch (URISyntaxException e) {
            LOGGER.error(
                ComponentsToolPlugin.class.getSimpleName() + "Could not find dierectory: " + name
                    + "/n" + e.getMessage());
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(
                    ComponentsToolPlugin.class.getSimpleName() + ": Could not find dierectory: "
                        + name + "/n" + e);
            }
        }
        String dirName = name;
        if (dir != null && dir.exists()) {
            GuiPlugin starter = GuiPlugin.getCurrent();
            if (starter == null) {
                LOGGER.error("no gui starter object! cannot create palette.");
                return; //NOTICE null added return statement
            }
            try {
                dirName = dir.getCanonicalPath();
            } catch (Exception se) {
                LOGGER.error(
                    "Security of FilePath violated. File: " + dir.getName() + "\n Exception: "
                        + se);
            }
            if (componentsToolListcontains(dirName)) {
                this.removePalette(dirName);
            }
            // logger.info("Looking for plugin in _pluginList. Name: "+ dirName);
            // logger.info("List is "+_pluginList);
            // ComponentsPluginExtender plugin = _pluginList.get(dirName);
            // logger.info("Found plugin "+ plugin);
            ComponentsTool componentsTool =
                new ComponentsTool(dirName, paletteName, plugin, _workbench);

            if (componentsTool.toolsDirIsValid()) {
                _componentsToolList.add(componentsTool);
            } else {
                JOptionPane.showMessageDialog(
                    starter.getGuiFrame(), "No valid net component directory was selected.",
                    "Abort", JOptionPane.WARNING_MESSAGE, new ImageIcon(
                        starter.getClass().getResource(CPNApplication.CPNIMAGES + "RENEW.gif")));
            }
        }
    }

    /**
     * Creates a new default palette and refreshes the menuFrame, if not already
     * loaded. If loaded the palette gets removed.
     */
    public void createPalette() {
        // toggel on/off
        try {
            GuiPlugin starter = GuiPlugin.getCurrent();
            if (starter == null) {
                LOGGER.error("no gui starter object! cannot create palette.");
                return; //NOTICEnull add return statement
            }
            if (getDefaultCT() == null) {
                ComponentsTool ct = new ComponentsTool(_workbench);

                //check whether a valid tools directory has been loaded
                if (ct.toolsDirIsValid()) {
                    //                    _site.menuFrame().pack();
                    _componentsToolList.add(ct);
                    setDefaultCT(ct);

                } else {
                    JOptionPane.showMessageDialog(
                        starter.getGuiFrame(),
                        "The '" + ComponentsTool.TOOLDIRPROPERTY
                            + "' propery is not set to a valid net component directory."
                            + "\n Set this property in \"renew.properties\" "
                            + "in the \"config\" directory of your renew installation and restart the program"
                            + "\nor use the menu entry \"Edit > Netcomponents > select from directory\"!",
                        "Abort", JOptionPane.WARNING_MESSAGE,
                        new ImageIcon(
                            starter.getClass()
                                .getResource(CPNApplication.CPNIMAGES + "RENEW.gif")));
                }
            } else {
                //get rid of the default palette and remove from List
                getDefaultCT().remove();
                _componentsToolList.remove(getDefaultCT());
                setDefaultCT(null);
            }
        } catch (Exception ee) {
            LOGGER.error(
                "Something went wrong while toggling the default palette. Palette is: "
                    + getDefaultCT() + " Message: " + ee);

            if (getDefaultCT() != null) {
                LOGGER.error("Directory is: " + getDefaultCT().getLabel());
            }
        }
    }

    /**
     * Lets the user choose the tools' directory. Creates a new palette and
     * refreshes the menuFrame.
     */
    public void selectDirAndCreatePalette() {
        File dir = selectDirectory();
        String dirName = "";

        if (dir != null) {
            GuiPlugin starter = GuiPlugin.getCurrent();
            if (starter == null) {
                LOGGER.error("no gui starter object! cannot create palette.");
                return; //NOTICEnull added return statement
            }
            try {
                dirName = dir.getCanonicalPath();
            } catch (Exception se) {
                LOGGER.error(
                    "Security of FilePath violated. File: " + dir.getName() + "\n Exception: "
                        + se);
            }

            String name = guessPalettenameFromDirName(dir);
            ComponentsTool componentsTool = new ComponentsTool(dirName, name, null, _workbench);

            if (componentsTool.toolsDirIsValid()) {
                _componentsToolList.add(componentsTool);
            } else {
                JOptionPane.showMessageDialog(
                    starter.getGuiFrame(), "No valid net component directory was selected.",
                    "Abort", JOptionPane.WARNING_MESSAGE, new ImageIcon(
                        starter.getClass().getResource(CPNApplication.CPNIMAGES + "RENEW.gif")));
            }
        }
    }

    /*
     * Tries to find the plugin properties.
     * It is assumed to be in a directory called "etc" at most 3 levels above the tools directory.
     * Returns "components" if nothing is found.
     */
    private String guessPalettenameFromDirName(File dir) {
        File currentDir = dir;
        File[] files;
        int maxDepth = 3;

        do {
            currentDir = currentDir.getParentFile();
            files = currentDir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        if (file.getName().endsWith("etc")) {
                            try {
                                Path path = Paths.get(file.getAbsolutePath(), "plugin.cfg");
                                File configFile = path.toFile();
                                URL url = configFile.toURI().toURL();

                                PluginProperties properties =
                                    new PluginProperties(url, url.openStream());
                                String result = properties.getName();
                                if (result != properties.UNKNOWN) {
                                    return result;
                                } else {
                                    return "Components";
                                }
                            } catch (java.io.IOException e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }
                }
            }
            maxDepth--;
        } while (currentDir.getParentFile() != null && maxDepth > 0);

        return "Components";
    }

    /*
     * @param componentsToolDirName
     * @return
     */
    private boolean componentsToolListcontains(String componentsToolDirName) {
        //boolean check = false;
        Iterator<ComponentsTool> it = _componentsToolList.iterator();
        while (it.hasNext()) {
            ComponentsTool ct = it.next();
            if (ct.getLabel().equals(componentsToolDirName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * removes the Palette that was last added from the components tools
     */
    public void removeLastPalette() {
        GuiPlugin starter = GuiPlugin.getCurrent();
        if (starter == null) {
            LOGGER.error("no gui starter object! cannot remove last palette.");
            return; //NOTICEnull added return statement
        }
        if (!_componentsToolList.isEmpty()) {
            ComponentsTool componentsTool = _componentsToolList.lastElement();

            if (componentsTool == getDefaultCT()) {
                setDefaultCT(null);
            }

            componentsTool.remove();
            _componentsToolList.remove(componentsTool);
            componentsTool = null;
            //            _site.menuFrame().pack();
        } else {
            starter.showStatus("Nothing to do.");
        }
    }

    /**
     * removes all palettes from the components tools
     */
    public void removePalette() {
        GuiPlugin starter = GuiPlugin.getCurrent();
        if (starter == null) {
            LOGGER.error("no gui starter object! cannot remove palette.");
            return; //NOTICEnull added return statement
        }
        if (!_componentsToolList.isEmpty()) {
            new RemoveToolsControl(this);
        } else {
            starter.showStatus("Nothing to do.");
        }
    }

    /**
     * Removes the Palette that belongs the tools dir path
     * @param toolDirPath the path to the palette we want to remove
     */
    public void removePalette(String toolDirPath) {
        Iterator<ComponentsTool> it = _componentsToolList.iterator();
        ComponentsTool toRemove = null;
        while (it.hasNext()) {
            ComponentsTool ct = it.next();
            if (toolDirPath.equals(ct.getLabel())) {
                toRemove = ct;
            }
        }
        if (toRemove != null) {
            _componentsToolList.remove(toRemove);
            toRemove.remove();
            toRemove = null;
        }
    }

    /**
     * Opens a directory chooser dialog and returns the user-selected directory.
     *
     * @return the selected directory as a {@link File}, or {@code null} if no directory
     *         was selected or the dialog could not be shown
     */
    public File selectDirectory() {
        GuiPlugin starter = GuiPlugin.getCurrent();
        if (starter == null) {
            LOGGER.error("GUI context not available; cannot open directory chooser.");
            return null;
        }

        CHOOSER.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

        int result = CHOOSER.showOpenDialog(starter.getGuiFrame());
        return result == JFileChooser.APPROVE_OPTION ? CHOOSER.getSelectedFile() : null;
    }

    /**
     * Get the value of CTList.
     *
     * @return value of CTList.
     */
    public Vector<ComponentsTool> getCTList() {
        return _componentsToolList;
    }

    /**
     * Get the value of defCT.
     *
     * @return value of defCT.
     */
    public ComponentsTool getDefaultCT() {
        return _defCT;
    }

    /**
     * Set the value of defCT.
     *
     * @param ct -
     *           Value to assign to default ComponentsTool.
     */
    public void setDefaultCT(ComponentsTool ct) {
        this._defCT = ct;
    }

    /**
     * gets the location as URL
     * @return the location as URL
     */
    static public URL getLocation() {
        return _location;
    }

    /**
     * sets the location
     * @param url the location as URL
     */
    static public void setLocation(URL url) {
        _location = url;
    }

    private CommandMenu createMenu() {
        CommandMenu menu = new CommandMenu("Netcomponents");

        //_menu.add(new ShowNetComponentsToolCommand(this));
        menu.add(new SelectNetComponentsToolCommand(this));
        menu.add(new RemoveNetComponentsToolCommand(this));
        menu.add(new RemoveLastNetComponentsToolCommand(this));
        menu.add(new GroupCommand(), KeyEvent.VK_1);
        menu.add(new UngroupCommand(), KeyEvent.VK_3);
        menu.addSeparator();
        menu.add(CommandApi.createVersionInfoCommand(this));
        menu.addSeparator();

        JMenuItem palettesHeading = menu.add("Available Palettes");
        palettesHeading.setEnabled(false);
        palettesHeading.setFont(palettesHeading.getFont().deriveFont(Font.BOLD));

        menu.putClientProperty(MenuApi.ID_PROPERTY, "de.renew.gui.nc");

        return menu;
    }

    /**
     * gets the current instance of the plugin NetComponents
     * @return the current instance of the plugin NetComponents
     */
    static public ComponentsToolPlugin getCurrent() {
        return (ComponentsToolPlugin) PluginManager.getInstance()
            .getPluginByName("Renew NetComponents");
    }
}