/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.gui.panel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

import net.sf.jaxodraw.gui.JaxoDialogs;
import net.sf.jaxodraw.gui.JaxoTab;
import net.sf.jaxodraw.gui.launch.JaxoLauncher;
import net.sf.jaxodraw.gui.menu.JaxoMenuBar;
import net.sf.jaxodraw.gui.panel.button.JaxoButtonPanel;
import net.sf.jaxodraw.util.JaxoPrefs;
import net.sf.jaxodraw.util.JaxoColor;
import net.sf.jaxodraw.util.JaxoConstants;
import net.sf.jaxodraw.util.JaxoInfo;
import net.sf.jaxodraw.util.JaxoLanguage;
import net.sf.jaxodraw.util.JaxoLocalized;
import net.sf.jaxodraw.util.JaxoLog;
import net.sf.jaxodraw.util.JaxoLooknFeel;
import net.sf.jaxodraw.util.JaxoUtils;


/**
 * The main panel. Holds the menu bar, the button panel, the flag panel, the canvas panel,
 * the tool bar and the grid bar. Responsible for the interaction between those components.
 *
 * @since 2.0
 */
public final class JaxoMainPanel extends JFrame implements JaxoCommunicator,
    JaxoLocalized {
    private static final long serialVersionUID = 7526471155622776147L;

    /** The button panel of this JaxoMainPanel. */
    private final JaxoButtonPanel buttonPanel;

    /** The menu bar of this JaxoMainPanel. */
    private final JaxoMenuBar jaxomenubar;

    /** The tool bar of this JaxoMainPanel. */
    private final JaxoToolBar toolBar;

    /** The status bar of this JaxoMainPanel. */
    private final JaxoStatusBar statusBar;

    /** The Grid panel of this JaxoMainPanel. */
    private final JaxoGridBar gridBar;

    private JaxoPrefsPanel preferencesPanel;
    private transient JaxoPluginManagerPanel pluginManagerPanel;

    /** The panel that holds the tabs*/
    private final transient JaxoTabbedCanvasPanel canvasPanel;

    /** The initialisation method of the panel: sets up the menus, the button panels,
     * the flag panel and the canvas. Start by showing one tab with an empty graph.
     */
    public JaxoMainPanel() {
        this(null);
    }

    /**
     * The initialisation method of the panel: sets up the menus, the button panels,
     * the flag panel and the canvas. Start by showing one tab with an empty graph.
     *
     * @param inputFileNames inputFileNames Array of file names to be opened at startup.
     *      May be null.
     * @since 2.1
     */
    public JaxoMainPanel(final String[] inputFileNames) {
        super();
        // First set the right look and feel - so that all components are
        // created with the right one - otherwise the layout may get confused.
        applyLookAndFeel(getLookAndFeelClassName(), false);

        // Also set the right locale early.
        JaxoLooknFeel.applyLocale(JaxoLanguage.locale());

        this.buttonPanel = new JaxoButtonPanel(this);
        this.addPropertyChangeListener(buttonPanel);

        this.toolBar = new JaxoToolBar(this);
        this.addPropertyChangeListener("Jaxo.watchMode", toolBar);

        this.jaxomenubar = new JaxoMenuBar(this);
        this.addPropertyChangeListener(jaxomenubar);

        this.canvasPanel = new JaxoTabbedCanvasPanel(this, this);
        this.addPropertyChangeListener(canvasPanel);
        this.addPropertyChangeListener(canvasPanel.getCanvas()); // FIXME

        this.statusBar = new JaxoStatusBar(canvasPanel.getSelectedTab());
        this.addPropertyChangeListener(statusBar);
        canvasPanel.getCanvas().addPropertyChangeListener(statusBar); // necessary?

        this.gridBar = new JaxoGridBar(this);
        this.addPropertyChangeListener(gridBar);

        // The final layout
        setGlassPane(new JaxoGlassPane(this, (JComponent) canvasPanel.getCanvas().asComponent()));
        ((JPanel) getContentPane()).setBorder(JaxoButtonPanel.RAISED_ETCHED_BORDER);
        getContentPane().add(getBarsPanel(toolBar, gridBar), BorderLayout.PAGE_START);
        getContentPane().add(canvasPanel.getRoot(), BorderLayout.CENTER);
        setJMenuBar(jaxomenubar);
        getContentPane().add(buttonPanel, BorderLayout.LINE_START);
        getContentPane().add(statusBar, BorderLayout.PAGE_END);

        setupInitialState();

        applyPrefs();

        setIconImage(JaxoUtils.newImage("frame.png"));
        setTitle(JaxoInfo.VERSION);

        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        this.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(final WindowEvent we) {
                    canvasPanel.performAction(JaxoConstants.QUIT);
                }
            });

        this.addWindowFocusListener(new WindowAdapter() {
            @Override
            public void windowLostFocus(final WindowEvent e) {
                firePropertyChange("Jaxo.mouseLocation", null, null);
                firePropertyChange("Jaxo.mainPanelFocused", true, false);
            }
            @Override
            public void windowGainedFocus(final WindowEvent e) {
                firePropertyChange("Jaxo.mainPanelFocused", false, true);
            }
        });

        openOrImport(inputFileNames);
    }

    /** {@inheritDoc} */
    public void distributePropertyChange(final String name, final Object oldValue, final Object newValue) {
        firePropertyChange(name, oldValue, newValue);
    }

    /** {@inheritDoc} */
    public void distributePropertyChange(final String name,
            final boolean oldValue, final boolean newValue) {
        firePropertyChange(name, oldValue, newValue);
    }

    /** {@inheritDoc} */
    public void distributePropertyChange(final String name, final int oldValue, final int newValue) {
        firePropertyChange(name, oldValue, newValue);
    }

    private String getLookAndFeelClassName() {
        return JaxoPrefs.getStringPref(JaxoPrefs.PREF_LOOKNFEEL);
    }

    /**
     * Open given file names, reusing an current unused tab.
     *
     * @param inputFileNames Array of file names to be opened.
     */
    private void openOrImport(final String[] inputFileNames) {
        if (inputFileNames == null) {
            return;
        }

        for (int i = 0; i < inputFileNames.length; i++) {
            JaxoLog.info(JaxoLanguage.message("Reading_file__0", inputFileNames[i]));

            canvasPanel.openOrImport(inputFileNames[i]);
        }
    }

    /** Update all sub-components with the new language.
     * @param languageString The new language to set.
     */
    private void setLanguage(final String languageString) {
        final String old = JaxoPrefs.getStringPref(JaxoPrefs.PREF_LANGUAGE);
        if (!languageString.equals(old)) {
            final Dimension oldPreferredSize = getPreferredSize();

            JaxoPrefs.setStringPref(JaxoPrefs.PREF_LANGUAGE, languageString);
            updateLanguage();
            firePropertyChange("Jaxo.language", old, languageString);

            // Increase size to fit the preferred size, but only if the
            // window was not shrunk by hand (independently for each direction)
            final Dimension d = getSize();
            final Dimension s = getPreferredSize();

            if (d.width >= oldPreferredSize.width) {
                d.width = Math.max(d.width, s.width);
            }
            if (d.height >= oldPreferredSize.height) {
                d.height = Math.max(d.height, s.height);
            }

            setSize(d.width, d.height);
        }
    }

    /** {@inheritDoc} */
    public void updateLanguage() {
        jaxomenubar.updateLanguage();
        gridBar.updateLanguage();
        toolBar.updateLanguage();
        buttonPanel.updateLanguage();
        statusBar.updateLanguage();
        canvasPanel.updateLanguage();

        if (preferencesPanel != null) {
            preferencesPanel.dispose();
            preferencesPanel = null;
        }

        if (pluginManagerPanel != null) {
            pluginManagerPanel.updateLanguage();
        }

        JaxoLooknFeel.applyLocale(JaxoLanguage.locale());
    }

    /** Called when an option is chosen from the options menu.
     * @param i Integer specifying the option event.
     */
    private void optionEvent(final int i) {
        switch (i) {
            case JaxoConstants.TOOLBAR:
                setToolBarVisible(!toolBar.isVisible());
                break;
            case JaxoConstants.STATUSBAR:
                setStatusBarVisible(!statusBar.isVisible());
                break;
            case JaxoConstants.GRIDBAR:
                setGridBarVisible(!gridBar.isVisible());
                break;
            case JaxoConstants.ANTIALIAS:
                setAntialiasEnabled(!canvasPanel.isAntialiasEnabled());
                break;
            case JaxoConstants.ARROW:
                setWithArrow(!JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_ARROW));
                break;
            case JaxoConstants.GRID_ONOFF:
                setGridOn(!canvasPanel.getSelectedTab().getGrid().isPainted());
                break;
            case JaxoConstants.SNAP_ONOFF:
                setSnap(!canvasPanel.getSelectedTab().isSnappingToGrid());
                break;
            case JaxoConstants.LOOKNFEEL:
                final JaxoLookAndFeelPanel laf = new JaxoLookAndFeelPanel();
                laf.setToUIManagerValue();

                JaxoDialogs.showMiniDialog(this,
                    JaxoLanguage.translate("Look_And_Feel"), laf.getRoot(),
                    new ActionListener() {
                        public void actionPerformed(final ActionEvent e) {
                            applyLookAndFeel(getLookAndFeelClassName(), true);
                        }
                    });


                break;
            case JaxoConstants.PREFERENCES:
                if (preferencesPanel == null) {
                    preferencesPanel = new JaxoPrefsPanel(this);
                    preferencesPanel.addComponentListener(new ComponentAdapter() {
                        @Override
                        public void componentHidden(final ComponentEvent e) {
                            applyPrefs();
                        }
                    });
                }
                preferencesPanel.setFromPrefs();

                preferencesPanel.setVisible(true);
                break;
            case JaxoConstants.PLUGINS:
                if (pluginManagerPanel == null) {
                    pluginManagerPanel = new JaxoPluginManagerPanel(this);
                }
                pluginManagerPanel.setVisible(true);
                break;
            default:
                break;
        }
    }

    private void setupInitialState() {
        toolBar.setVisible(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SHOWTOOL));
        statusBar.setVisible(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SHOWSTATUS));
        gridBar.setVisible(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SHOWGRIDBAR));
        firePropertyChange("Jaxo.gridOn", true,
                JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_GRIDONOFF));
        firePropertyChange("Jaxo.snap", false,
                JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SNAPONOFF));
        firePropertyChange("Jaxo.antialiasEnabled", false,
                JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_ANTIALIAS));
        firePropertyChange("selectedTab", null, canvasPanel.getSelectedTab());
    }

    /** Applies the preferences. */
    private void applyPrefs() {
        setLanguage(JaxoPrefs.getStringPref(JaxoPrefs.PREF_LANGUAGE));

        setToolBarVisible(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SHOWTOOL));
        setStatusBarVisible(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SHOWSTATUS));
        setGridBarVisible(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SHOWGRIDBAR));
        setGridOn(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_GRIDONOFF));
        setSnap(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_SNAPONOFF));
        setAntialiasEnabled(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_ANTIALIAS));
        setWithArrow(JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_ARROW));

        setDefaultAction(JaxoPrefs.getIntPref(JaxoPrefs.PREF_DEFAULTACTION));
        buttonPanel.setDefault(getDefaultMode());

        setCanvasBackground(JaxoColor.getColor(JaxoPrefs.getStringPref(
            JaxoPrefs.PREF_CANVASBACKGROUND), JaxoColor.ALL_COLORS_MODE));

        canvasPanel.getCanvas().setMinimumCanvasSize(new Dimension(JaxoPrefs.getIntPref(
                    JaxoPrefs.PREF_SCREENSIZEX),
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_SCREENSIZEY)));
        canvasPanel.getCanvas().setMaximumCanvasSize(JaxoInfo.SCREEN_SIZE);

        applyLookAndFeel(getLookAndFeelClassName(), false);
    }

    private void applyLookAndFeel(final String lookAndFeelClassName, final boolean warning) {
        final boolean success = JaxoLooknFeel.applyLookAndFeel(lookAndFeelClassName);
        if (!success && warning) {
            final String message =
                    JaxoLooknFeel.info(lookAndFeelClassName).getName()
                    + JaxoLanguage.translate(
                        "_Look_and_Feel_is_not_supported_on_this_platform!");
                JaxoDialogs.showErrorDialog(null, message);
        }
    }

    private void setCanvasBackground(final Color value) {
        firePropertyChange("Jaxo.canvasBackground", null, value);
    }

    /** Switches the toolBar on or off.
     * @param show A boolean variable that indicates whether the toolBar is visible or not.
     */
    private void setToolBarVisible(final boolean show) {
        if (show != toolBar.isVisible()) {
            toolBar.setVisible(show);

            firePropertyChange("Jaxo.toolBarVisible", !show, show);
        }
    }

    /** Switches the status bar on or off.
     * @param show A boolean variable that indicates whether the status bar is visible or not.
     */
    private void setStatusBarVisible(final boolean show) {
        if (show != statusBar.isVisible()) {
            statusBar.setVisible(show);

            firePropertyChange("Jaxo.statusBarVisible", !show, show);
        }
    }

    /** Switches the gridBar on or off.
     * @param show A boolean variable that indicates whether the toolBar is visible or not.
     */
    private void setGridBarVisible(final boolean show) {
        if (show != gridBar.isVisible()) {
            gridBar.setVisible(show);

            firePropertyChange("Jaxo.gridBarVisible", !show, show);
        }
    }

    /** Switches antialiasing on or off.
     * @param on A boolean variable that indicates whether antialising is on or not.
     */
    private void setAntialiasEnabled(final boolean on) {
        if (on != canvasPanel.isAntialiasEnabled()) {
            canvasPanel.setAntialiasEnabled(on);

            firePropertyChange("Jaxo.antialiasEnabled", !on, on);
        }
    }

    /** Switches the grid on or off.
     * @param on A boolean variable that indicates whether the grid is on or not.
     */
    private void setGridOn(final boolean on) {
        firePropertyChange("Jaxo.gridOn", !on, on);
    }

    /** Determines whether to draw arrows on lines, arcs and loops.
     * @param on A boolean variable that indicates whether to draw arrows on
     * lines, arcs and loops.
     */
    private void setWithArrow(final boolean on) {
        if (on != JaxoPrefs.getBooleanPref(JaxoPrefs.PREF_ARROW)) {
            firePropertyChange("Jaxo.withArrow", !on, on);
            JaxoPrefs.setBooleanPref(JaxoPrefs.PREF_ARROW, on);
        }
    }

    /** Sets the snap mode.
     * @param setSnap The new snap mode.
     */
    private void setSnap(final boolean setSnap) {
        firePropertyChange("Jaxo.snap", !setSnap, setSnap);
    }

    /**
     * Returns the current default mode.
     * @return The current default mode.
     */
    private int getDefaultMode() {
        return JaxoConstants.defaultActionToMode(
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_DEFAULTACTION));
    }

    /**
     * Sets the default action. If value is not a valid default mode,
     * it is set to JaxoConstants.DEF_NONE.
     * @param value The default action to set.
     */
    private void setDefaultAction(final int value) {
        int newValue = value;

        if (!JaxoConstants.isDefaultMode(newValue)) {
            newValue = JaxoConstants.DEF_NONE;
        }

        final int old = JaxoPrefs.getIntPref(JaxoPrefs.PREF_DEFAULTACTION);

        if (newValue != old) {
            final int oldMode = getDefaultMode();
            JaxoPrefs.setIntPref(JaxoPrefs.PREF_DEFAULTACTION, newValue);

            firePropertyChange("Jaxo.defaultAction", old, newValue);
            firePropertyChange("Jaxo.defaultMode", oldMode, getDefaultMode());
        }
    }

    /** Adds a new entry to the file menu, with a shortcut to
     * a recently opened graph.
     * @param saveFileName The full path to the saved graph.
     */
    private void addRecentFile(final String saveFileName) {
        firePropertyChange("Jaxo.recentFile", null, saveFileName);
    }

    /**
     * Removes the given file from the list of recent files.
     * @param saveFileName The file to remove.
     */
    private void removeRecentFile(final String saveFileName) {
        firePropertyChange("Jaxo.recentFile", saveFileName, null);
    }

    /**
     * Performs an action in reply to an action event on either
     * the menu bar, tool bar, grid bar, tabbed canvas panel or button panel.
     * NOTE: This should be the central method where all input events
     * (except drawing events on the Canvas) should be processed.
     * The MainPanel is the only component that has direct access
     * to all sub-components, and therefore it's easiest to trigger
     * identical events from different input sources.
     *
     * <p>This method throws an Exception if the source of the ActionEvent
     * is not a Component.</p>
     *
     * @param evt The action event that triggers an action.
     * The ActionCommand of the event has to be an integer, encapsulated
     * as a String, that identifies the event according to the modes
     * in {@link JaxoConstants}.
     */
    public void actionPerformed(final ActionEvent evt) {
        if ("setGlassPaneVisible".equals(evt.getActionCommand())) {
            getGlassPane().setVisible(true);
            return;
        } else if ("setGlassPaneInvisible".equals(evt.getActionCommand())) {
            getGlassPane().setVisible(false);
            return;
        }

        final Component source = (Component) evt.getSource();
        final int mode = JaxoConstants.getModeAsInt(evt.getActionCommand());

        if (mode == JaxoConstants.RECENT_FILE) {
            final AbstractButton sourceButton = (AbstractButton) source;
            final String fileName = sourceButton.getName();

            JaxoTab t = canvasPanel.getTabWithSaveFileName(fileName);

            if (t != null) {
                canvasPanel.setSelectedTab(t);
                addRecentFile(fileName);
                return;
            }

            canvasPanel.openOrImport(fileName);

            t = canvasPanel.getTabWithSaveFileName(fileName);

            if (t == null) {
                removeRecentFile(fileName);
            }
        } else {
            performAction(mode);
        }
    }

    /**
     * Performs the action corresponding to the given mode.
     * @param mode A mode as defined in {@link JaxoConstants}.
     */
    private void performAction(final int mode) {
        if (JaxoConstants.isZoomFactorMode(mode)) {
            firePropertyChange("zoomFactor", -1, mode);
        } else if (JaxoConstants.isLanguageMode(mode)) {
            setLanguage(JaxoLanguage.getLanguageFor(mode));
        } else if (JaxoConstants.isOptionMode(mode)) {
            optionEvent(mode);
        } else if (JaxoConstants.isDefaultMode(mode)) {
            setDefaultAction(mode);
        } else {
            canvasPanel.performAction(mode);
        }
    }

    /**
     * Dispose the main frame and call JaxoLauncher.shutdown(exitStatus).
     *
     * @param exitStatus The exit status code.
     */
    public void shutdown(final int exitStatus) {
        if (exitStatus == 0) {
            try {
                JaxoPrefs.saveRecentFiles();
            } catch (Exception e) {
                JaxoLog.debug(e);
                JaxoDialogs.showI18NErrorDialog(this, "Cannot_save_preferences!");
            }
        }

        setVisible(false);
        dispose();

        JaxoLauncher.shutdown(exitStatus);
    }

    private JPanel getBarsPanel(final JaxoToolBar tB, final JaxoGridBar gB) {
        final JPanel barP = JaxoDialogs.newPageBoxLayoutPanel();

        barP.add(tB);
        barP.add(gB);

        return barP;
    }
}
