/*  Copyright (C) 2003-2011 JabRef contributors.
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.external;

import com.jgoodies.forms.builder.ButtonBarBuilder;
import net.sf.jabref.GUIGlobals;
import net.sf.jabref.Globals;
import net.sf.jabref.JabRefFrame;
import net.sf.jabref.MnemonicAwareAction;
import net.sf.jabref.oo.OpenOfficePanel;
import net.sf.jabref.plugin.PluginCore;
import net.sf.jabref.plugin.core.JabRefPlugin;
import net.sf.jabref.plugin.core.generated._JabRefPlugin;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

/**
 * Customized UI component for pushing to external applications. Has a selection popup
 * menu to change the selected external application.
 * This class implements the ActionListener interface. When actionPerformed() is
 * invoked, the currently selected PushToApplication is activated. The actionPerformed()
 * method can be called with a null argument.
 */
public class PushToApplicationButton implements ActionListener {

    public static List<PushToApplication> applications;

    private JabRefFrame frame;
    private List<PushToApplication> pushActions;
    private JPanel comp;
    private JButton pushButton, menuButton;
    private int selected = 0;
    private JPopupMenu popup = null;
    private HashMap<PushToApplication, PushToApplicationAction> actions = new HashMap<PushToApplication, PushToApplicationAction>();
    private final Dimension buttonDim = new Dimension(23, 23);
    private static final URL ARROW_ICON = GUIGlobals.class.getResource("/images/secondary_sorted_reverse.png");
    private MenuAction mAction = new MenuAction();
    private JPopupMenu optPopup = new JPopupMenu();
    private JMenuItem settings = new JMenuItem(Globals.lang("Settings"));

    /**
     * Set up the current available choices:
     */
    static {

        applications = new ArrayList<PushToApplication>();

        JabRefPlugin jabrefPlugin = JabRefPlugin.getInstance(PluginCore.getManager());
        if(jabrefPlugin != null){
		    List<_JabRefPlugin.PushToApplicationExtension> plugins = jabrefPlugin.getPushToApplicationExtensions();
		    for (_JabRefPlugin.PushToApplicationExtension extension : plugins) {
		        applications.add(extension.getPushToApp());
		    }
		
		    applications.add(new PushToLyx());
		    applications.add(new PushToEmacs());
		    applications.add(new PushToWinEdt());
		    applications.add(new PushToLatexEditor());
		    applications.add(new PushToVim());
            applications.add(OpenOfficePanel.getInstance());
            applications.add(new PushToTeXstudio());
		
		    // Finally, sort the entries:
		    //Collections.sort(applications, new PushToApplicationComparator());
        }
    }


    public PushToApplicationButton(JabRefFrame frame, List<PushToApplication> pushActions) {
        this.frame = frame;
        this.pushActions = pushActions;
        init();
    }

    private void init() {
        comp = new JPanel();
        comp.setLayout(new BorderLayout());

        menuButton = new JButton(new ImageIcon(ARROW_ICON));
        menuButton.setMargin(new Insets(0,0,0,0));
        menuButton.setPreferredSize(new Dimension(menuButton.getIcon().getIconWidth(),
                menuButton.getIcon().getIconHeight()));
        menuButton.addActionListener(new MenuButtonActionListener());
        menuButton.setToolTipText(Globals.lang("Select external application"));
        pushButton = new JButton();
        if (Globals.prefs.hasKey("pushToApplication")) {
            String appSelected = Globals.prefs.get("pushToApplication");
            for (int i=0; i<pushActions.size(); i++) {
                PushToApplication toApp = pushActions.get(i);
                if (toApp.getName().equals(appSelected)) {
                    selected = i;
                    break;
                }
            }
        }

        setSelected(selected);
        pushButton.addActionListener(this);
        pushButton.addMouseListener(new PushButtonMouseListener());
        pushButton.setOpaque(false);
        menuButton.setOpaque(false);
        comp.setOpaque(false);
        comp.add(pushButton, BorderLayout.CENTER);
        comp.add(menuButton, BorderLayout.EAST);
        //comp.setBorder(BorderFactory.createLineBorder(Color.gray));
        comp.setMaximumSize(comp.getPreferredSize());

        optPopup.add(settings);
        settings.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                PushToApplication toApp = pushActions.get(selected);
                JPanel options = toApp.getSettingsPanel();
                if (options != null) {
                    showSettingsDialog(frame, toApp, options);
                }

            }
        });

        buildPopupMenu();
    }

    /**
     * Create a selection menu for the available "Push" options.
     */
    private void buildPopupMenu() {
        popup = new JPopupMenu();
        int j=0;
        for (PushToApplication application : pushActions){
            JMenuItem item = new JMenuItem(application.getApplicationName(),
                    application.getIcon());
            item.setToolTipText(application.getTooltip());
            item.addActionListener(new PopupItemActionListener(j));
            popup.add(item);
            j++;
        }
    }

    /**
     * Update the PushButton to default to the given application.
     * @param i The List index of the application to default to.
     */
    private void setSelected(int i) {
        this.selected = i;
        PushToApplication toApp = pushActions.get(i);
        pushButton.setIcon(toApp.getIcon());
        pushButton.setToolTipText(toApp.getTooltip());
        pushButton.setPreferredSize(buttonDim);

        Globals.prefs.put("pushToApplication", toApp.getName());
        mAction.setTitle(toApp.getApplicationName());
    }

    /**
     * Get the toolbar component for the push button.
     * @return The component.
     */
    public Component getComponent() {
       return comp;
    }

    public Action getMenuAction() {
        return mAction;
    }

    public void actionPerformed(ActionEvent e) {
        PushToApplication toApp = pushActions.get(selected);

        // Lazy initialization of the push action:
        PushToApplicationAction action = actions.get(toApp);
        if (action == null) {
            action = new PushToApplicationAction(frame, toApp);
            actions.put(toApp, action);
        }
        action.actionPerformed(new ActionEvent(toApp, 0, "push"));
    }

    static class BooleanHolder {
        public BooleanHolder(boolean value) {
            this.value = value;
        }
        public boolean value;
    }

    public static void showSettingsDialog(Object parent, PushToApplication toApp, JPanel options) {
        
        final BooleanHolder okPressed = new BooleanHolder(false);
        JDialog dg;
        if (parent instanceof JDialog)
            dg = new JDialog((JDialog)parent, Globals.lang("Settings"), true);
        else
            dg = new JDialog((JFrame)parent, Globals.lang("Settings"), true);
        final JDialog diag = dg;
        options.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
        diag.getContentPane().add(options, BorderLayout.CENTER);
        ButtonBarBuilder bb = new ButtonBarBuilder();
        JButton ok = new JButton(Globals.lang("Ok"));
        JButton cancel = new JButton(Globals.lang("Cancel"));
        bb.addGlue();
        bb.addButton(ok);
        bb.addButton(cancel);
        bb.addGlue();
        bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
        diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH);
        ok.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                okPressed.value = true;
                diag.dispose();
            }
        });
        cancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                diag.dispose();
            }
        });
        // Key bindings:
        ActionMap am = bb.getPanel().getActionMap();
        InputMap im = bb.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        im.put(Globals.prefs.getKey("Close dialog"), "close");
        am.put("close", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                diag.dispose();
            }
        });
        diag.pack();
        if (parent instanceof JDialog)
            diag.setLocationRelativeTo((JDialog)parent);
        else
            diag.setLocationRelativeTo((JFrame)parent);
        // Show the dialog:
        diag.setVisible(true);
        // If the user pressed Ok, ask the PushToApplication implementation
        // to store its settings:
        if (okPressed.value) {
            toApp.storeSettings();
        }
    }

    class PopupItemActionListener implements ActionListener {
        private int index;
        public PopupItemActionListener(int index) {
            this.index = index;
        }

        public void actionPerformed(ActionEvent e) {
            // Change the selection:
            setSelected(index);
            // Invoke the selected operation (is that expected behaviour?):
            //PushToApplicationButton.this.actionPerformed(null);
            // It makes sense to transfer focus to the push button after the
            // menu closes:
            pushButton.requestFocus();
        }
    }


    class MenuButtonActionListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            // Lazy initialization of the popup menu:
            if (popup == null)
                buildPopupMenu();
            popup.show(comp, 0, menuButton.getHeight());
        }
    }

    class MenuAction extends MnemonicAwareAction {

        public MenuAction() {
            putValue(ACCELERATOR_KEY, Globals.prefs.getKey("Push to application"));
        }

        public void setTitle(String appName) {
            putValue(NAME, Globals.lang("Push entries to external application (%0)",
                    appName));
        }

        public void actionPerformed(ActionEvent e) {
            PushToApplicationButton.this.actionPerformed(null);
        }
    }

    class PushButtonMouseListener extends MouseAdapter {
        public void mousePressed(MouseEvent event) {
            if (event.isPopupTrigger())
                processPopupTrigger(event);
        }

        public void mouseClicked(MouseEvent event) {
            if (event.isPopupTrigger())
                processPopupTrigger(event);
        }

        public void mouseReleased(MouseEvent event) {
            if (event.isPopupTrigger())
                processPopupTrigger(event);
        }

        private void processPopupTrigger(MouseEvent e) {
            // We only want to show the popup if a settings panel exists for the selected
            // item:
            PushToApplication toApp = pushActions.get(selected);
            if (toApp.getSettingsPanel() != null)
                optPopup.show(pushButton, e.getX(), e.getY());

        }
    }

    /**
     * Comparator for sorting the selection according to name.
     */
    static class PushToApplicationComparator implements Comparator<PushToApplication> {

        public int compare(PushToApplication one, PushToApplication two) {
            return one.getName().compareTo(two.getName());
        }
    }
}
