package edu.sc.seis.gee.task;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import org.apache.log4j.Logger;
import edu.sc.seis.fissuresUtil.exceptionHandler.GlobalExceptionHandler;
import edu.sc.seis.gee.CommonAccess;
import edu.sc.seis.gee.FrameManager;
import edu.sc.seis.gee.NoNetworkException;
import edu.sc.seis.gee.configurator.ConfigurationException;
import edu.sc.seis.gee.configurator.DefaultParamNames;
import edu.sc.seis.gee.configurator.MenuHandler;
import edu.sc.seis.gee.configurator.ToolBarHandler;
import edu.sc.seis.gee.configurator.ToolBarItem;
import edu.sc.seis.gee.configurator.ToolItemHandler;

/**
 * TaskAction.java Created: Thu Nov 29 22:47:44 2001
 * 
 * @author <a href="mailto:">Philip Crotwell </a>
 * @version $Id: TaskAction.java 11645 2005-01-12 16:53:17Z groves $
 */
public class TaskAction extends AbstractAction implements DefaultParamNames {

    public TaskAction(String id, String classname, String name,
            HashMap configParam, ToolBarHandler tbh, boolean toggle,
            String iconName) {
        this(id, classname, name, configParam, tbh, toggle);
        this.iconName = iconName;
        String iconImageName = iconName;
        java.net.URL url = null;
        if(iconImageName.indexOf('/') == -1) {
            // no directories in imageName, try and find in data/images
            iconImageName = "edu/sc/seis/gee/data/images/" + iconImageName;
            url = getClass().getClassLoader().getResource(iconImageName);
        } else {
            try {
                url = new java.net.URL(iconImageName);
            } catch(java.net.MalformedURLException e) {
                System.out.println(iconName);
                logger.error("Icon Image URL is badly formed:" + iconImageName,
                             e);
            } // end of try-catch
        } // end of else
        if(url != null) {
            putValue(Action.SMALL_ICON, new ImageIcon(url, name));
        } // end of if (url != null)
    }

    public TaskAction(String id, String classname, String name,
            HashMap configParam, ToolBarHandler tbh, boolean toggle) {
        super(name);
        //test tooltip with icon
        //    String iconImageName =
        //         "edu/sc/seis/gee/data/images/transparent.png";
        //     java.net.URL url =
        // getClass().getClassLoader().getResource(iconImageName);
        //     putValue(Action.SMALL_ICON, new ImageIcon(url, name));
        this.classname = classname;
        this.id = id;
        this.params = configParam;
        this.toolBarHandler = tbh;
        this.toggle = toggle;
        configParam.put(TASK_ACTION, this);
        String tooltip = (String)configParam.get(TOOLTIP);
        if(tooltip != null && tooltip.length() != 0) {
            putValue(Action.SHORT_DESCRIPTION, tooltip);
        }
        String displayLoc = (String)configParam.get(DISPLAY_LOCATION);
        if(displayLoc == null) {
            configParam.put(DISPLAY_LOCATION, FRAME);
        } // end of if (displayLoc == null)
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return (String)getValue(Action.NAME);
    }

    public boolean isToggle() {
        return toggle;
    }

    public Icon getIcon() {
        return (Icon)getValue(Action.SMALL_ICON);
    }

    public String getIconLoc() {
        return iconName;
    }

    public ToolBarHandler getToolBarHandler() {
        return toolBarHandler;
    }

    /**
     * Gets the main tool bar for this task. There may be other copies of the
     * toolbar for use in child windows, but this is the original.
     */
    public JToolBar getToolBar() {
        if(jtoolbar == null) {
            try {
                jtoolbar = initToolBar();
            } catch(ConfigurationException e) {
                GlobalExceptionHandler.handle("Problem creating toolbar.", e);
            }
        }
        toolBarAdded = true;
        return jtoolbar;
    }

    /**
     * creates a copy of the toolbar, fir cases where a task has multiple
     * windows and needs for each window to have its own toolbar.
     */
    public JToolBar copyToolBar() throws ConfigurationException {
        JToolBar jtb = initToolBar();
        return jtb;
    }

    /**
     * returns the JFrame for this task, or null if there is not a frame.
     */
    public JFrame getJFrame() {
        return frame;
    }

    public void destroyJFrame() {
        if(frame != null) {
            frame.hide();
            //frame.dispose();
            frame = null;
        } // end of if (frame != null)
    }

    public JPanel getJPanel() {
        return panel;
    }

    /**
     * Calls check display location with a default setSize and tabbed value of
     * false
     */
    public void checkDisplayLocation() throws ConfigurationException {
        checkDisplayLocation(false, true);
    }

    /**
     * This method takes the config paramater DISPLAY_LOCATION and if the task
     * is a GUITask or is a Component it puts it in that DISPLAY_LOCATION and
     * initializes its graphical elements
     */
    public void checkDisplayLocation(boolean tabbed)
            throws ConfigurationException {
        checkDisplayLocation(tabbed, true);
    }

    private void checkDisplayLocation(boolean tabbed, boolean bringToFront)
            throws ConfigurationException {
        if(task == null) {
            invoke(tabbed);
        }
        String displayLoc = (String)params.get(DISPLAY_LOCATION);
        if(displayLoc == null) {
            displayLoc = FRAME;
        } // end of if (displayLoc == null)
        if(task instanceof GUITask || task instanceof Component) {
            FrameManager manager = FrameManager.getManager();
            if(!displayed) {
                if(displayLoc.equalsIgnoreCase(MAIN)) {
                    frame = manager.getCurrentMainFrame();
                    logger.debug("main frame name " + frame.getName()
                            + "  for " + getName());
                    manager.resize(frame, getSize(new Dimension(800, 600)));
                    frame.addWindowListener(defWindowListener);
                    panel = CommonAccess.getCommonAccess().getDisplayArea();
                    if(tabbed) {
                        panel = manager.createNewTab(frame,
                                                     (String)params.get(TASK_NAME));
                    }
                    initPanel(panel);
                    frame.validate();
                } else if(displayLoc.equalsIgnoreCase(FRAME)) {
                    initFrame();
                } else if(displayLoc.equalsIgnoreCase(PALETTE)) {
                    initPalette();
                }
                displayed = true;
            }
            if(bringToFront) {
                manager.setCurrentPanel(panel);
            }
            if(panel != null) {
                panel.revalidate();
            }
        }
    }

    public void actionPerformed(ActionEvent act) {
        if(act != null && act.getActionCommand().equals("setSize")) {
            invoke(false);
        } else if(task != null) {
            invoke(false, true);
        } else {
            invoke(false);
        }
    }

    public void invoke(boolean tabbed) {
        invoke(tabbed, false);
    }

    public synchronized void invoke(boolean tabbed, boolean bringToFront) {
        if(configuringTask) { throw new IllegalStateException("Attempting to call invoke while the task was being configured!"); }
        GlobalExceptionHandler.append("Invoked Tasks", getId() + "  -  "
                + getName());
        try {
            getTask();
            if(isToggle()) {
                if(params.get(BUTTON_GROUP_ID) != null) {
                    ButtonGroup bg = ButtonGroupManager.get((String)params.get(BUTTON_GROUP_ID));
                    if(params.get(BUTTON_MODEL) != null) {
                        bg.setSelected((ButtonModel)params.get(BUTTON_MODEL),
                                       true);
                    }
                }
            }
            checkDisplayLocation(tabbed, bringToFront);
            task.invoke();
        } catch(NoNetworkException e) {
            // this comes from a direct user action (menu/button/etc) and so we
            // should report the no network condition
            e.showMessage();
        } catch(Throwable e) {
            GlobalExceptionHandler.handle(e);
        } // end of try-catch
    }

    public void addToToolBar(JComponent comp) {
        if(jtoolbar == null) {
            addedToolBarItems.add(comp);
        } else {
            addToToolBar(jtoolbar, comp);
            checkToolBar(panel, jtoolbar);
        } // end of else
    }

    public void addToToolBar(JToolBar jtb, JComponent comp) {
        for(int i = 0; i < jtb.getComponentCount(); i++) {
            if(jtb.getComponent(i) instanceof Box.Filler) {
                jtb.add(comp, i);
                return;
            }
        }
        jtb.add(comp);
    }

    public void addToToolBar(Action act) {
        if(jtoolbar == null) {
            addedToolBarItems.add(act);
            return;
        }
        addToToolBar(jtoolbar, act);
    }

    public void addToToolBar(JToolBar jtb, Action act) {
        AbstractButton button;
        if(act instanceof TaskAction && ((TaskAction)act).isToggle()) {
            TaskAction tAct = (TaskAction)act;
            // toggles don't call invoke, so need to preload/configure
            if(tAct.params.get(BUTTON_GROUP_ID) != null) {
                button = new JToggleButton(tAct);
                ButtonGroup bg = ButtonGroupManager.get((String)tAct.params.get(BUTTON_GROUP_ID));
                if(tAct.getConfigParam(DefaultParamNames.BUTTON_MODEL) != null) {
                    button.setModel((ButtonModel)tAct.getConfigParam(DefaultParamNames.BUTTON_MODEL));
                } else {
                    tAct.putConfigParam(DefaultParamNames.BUTTON_MODEL,
                                        button.getModel());
                }
                bg.add(button);
            } else {
                button = new JCheckBox(tAct);
            } // end of else
            if(tAct.getConfigParam(DefaultParamNames.PRESELECTED) == DefaultParamNames.TRUE) button.setSelected(true);
            button.setActionCommand(tAct.getId());
        } else {
            button = new JButton();
            button.setAction(act);
        } // end of else
        if(act.getValue(Action.SMALL_ICON) != null) {
            button.setText("");
        } else {
            button.setIcon(singlePixelWide);
        }
        String toolTipText = (String)act.getValue(Action.SHORT_DESCRIPTION);
        if(toolTipText != null && toolTipText.length() != 0) {
            button.setToolTipText(toolTipText);
        } // end of if (toolTipText != null && toolTipText.length != 0)
        addToToolBar(jtb, button);
    }

    /**
     * Method initFrame
     * 
     * @throws ConfigurationException
     */
    private void initFrame() throws ConfigurationException {
        if(frame == null) {
            logger.debug("frame is null, recreating");
            panel = new JPanel();
            panel.setName(getName());
            panel.setMinimumSize(new Dimension(200, 100));
            initPanel(panel);
            Dimension size = null;
            boolean usePack = true;
            if(params.containsKey(WIDTHPER) || params.containsKey(WIDTH)
                    || params.containsKey(HEIGHTPER)
                    || params.containsKey(HEIGHT)) {
                // some size info in config file, so don't pack
                usePack = false;
                size = getSize();
                panel.setPreferredSize(size);
                logger.debug("size is " + size.toString());
            } else {
                Dimension dim = new Dimension(300, 300);
                panel.setPreferredSize(dim);
                panel.setMinimumSize(dim);
            }
            String justify = (String)params.get(JUSTIFY);
            FrameManager manager = FrameManager.getManager();
            JFrame motherFrame = manager.getCurrentMainFrame();
            if(justify == null) {
                if(usePack) {
                    frame = manager.createNewChild(getName(),
                                                   motherFrame,
                                                   panel);
                } else {
                    frame = manager.createNewChild(getName(),
                                                   motherFrame,
                                                   size,
                                                   panel);
                }
            } else {
                if(usePack) {
                    frame = manager.createNewChild(getName(),
                                                   motherFrame,
                                                   panel);
                    frame.setLocation(FrameManager.getLoc(justify,
                                                          frame.getSize()));
                } else {
                    frame = manager.createNewChild(getName(),
                                                   motherFrame,
                                                   size,
                                                   panel,
                                                   FrameManager.getLoc(justify,
                                                                       size));
                }
            }
            // add global menubar
            if(CHILDREN_GET_MENUBAR) manager.setMenuBar(frame);
        } // end of if (frame == null)
        frame.addWindowListener(defWindowListener);
        frame.show();
        frame.validate();
        if(frame.getState() == JFrame.ICONIFIED) {
            frame.setState(JFrame.NORMAL);
        }
    }

    private void initPanel(JPanel newPanel) throws ConfigurationException {
        FrameManager.getManager().clearComponent(panel); // make sure we start
        // with a clean empty
        // panel
        panel.removeAll();
        toolBarAdded = false;
        panel.setName((String)params.get(TASK_NAME));
        FrameManager.getManager().updateName(panel);
        panel.setLayout(new BorderLayout());
        if(task instanceof GUITask) {
            GUITask guiTask = (GUITask)task;
            try {
                panel.add(guiTask.getGUI(), BorderLayout.CENTER);
                if(guiTask.hasMoreOptions()) {
                    JButton more = new JButton("More Options");
                    more.addActionListener(new ActionListener() {

                        public void actionPerformed(ActionEvent e) {}
                    });
                    panel.add(more, BorderLayout.SOUTH);
                }
            } catch(Exception e) {
                throw new ConfigurationException("Problem getting GUI for task "
                                                         + getName(),
                                                 e);
            }
        } else {
            panel.add((JComponent)task, BorderLayout.CENTER);
        }
        jtoolbar = initToolBar();
        checkToolBar(panel, jtoolbar);
        panel.validate();
        panel.repaint();
    }

    private void initPalette() throws ConfigurationException {
        if(window == null) {
            window = new JDialog((JFrame)CommonAccess.getCommonAccess()
                    .getDisplayArea()
                    .getTopLevelAncestor(), getName(), false);
            panel = new JPanel();
            jtoolbar = new JToolBar();
            jtoolbar.setFloatable(false);
            if(toolBarHandler != null
                    && toolBarHandler.getToolItems().length > 1) {
                jtoolbar.setLayout(new GridLayout(toolBarHandler.getToolItems().length / 2,
                                                  2));
            }
            initPanel(panel);
            window.getContentPane().setLayout(new BorderLayout());
            window.getContentPane().add(panel);
            window.pack();
            window.validate();
            String justify = (String)params.get(JUSTIFY);
            if(justify == null) {
                justify = JUSTIFY_CENTER;
            }
            Toolkit toolkit = Toolkit.getDefaultToolkit();
            Dimension dimension = toolkit.getScreenSize();
            //set the location of the frame based on justification
            int x = 0, y = 0;
            int w = window.getSize().width;
            int h = window.getSize().height;
            if(justify.equals(JUSTIFY_CENTER)) {
                x = (dimension.width - w) / 2;
                y = (dimension.height - h) / 2;
            } else if(justify.equals(JUSTIFY_TOPLEFT)) {
                x = 0;
                y = 0;
            } else if(justify.equals(JUSTIFY_TOPRIGHT)) {
                x = dimension.width - w;
                y = 0;
            } else if(justify.equals(JUSTIFY_BOTTOMLEFT)) {
                x = 0;
                y = dimension.height - h - 40;
            } else if(justify.equals(JUSTIFY_BOTTOMRIGHT)) {
                x = dimension.width - w;
                y = dimension.height - h - 40;
            }
            window.setLocation(x, y);
        }
        window.setVisible(true);
    }

    private JToolBar initToolBar() throws ConfigurationException {
        JToolBar jtb = new JToolBar();
        jtb.setFloatable(false);
        if(toolBarHandler != null) {
            ToolBarItem[] toolBarItems = toolBarHandler.getToolItems();
            for(int i = 0; i < toolBarItems.length; i++) {
                if(toolBarItems[i] instanceof ToolItemHandler) {
                    addToToolBar(jtb,
                                 ((ToolItemHandler)toolBarItems[i]).getToolItem());
                } else if(toolBarItems[i] instanceof MenuHandler) {
                    MenuHandler menuHandler = (MenuHandler)toolBarItems[i];
                    final JButton menuButton = new JButton(menuHandler.getName(),
                                                           rightTriangle);
                    final JPopupMenu popup = menuHandler.getPopupMenu();
                    addToToolBar(jtb, menuButton);
                    menuButton.addMouseListener(new MouseAdapter() {

                        public void mousePressed(MouseEvent e) {
                            popup.show(menuButton, 0, menuButton.getHeight());
                        }
                    });
                }
            } // end of for (int i=0; i<toolBarItems.length; i++)
            toolBarHandlerAdded = true;
        }
        if(addedToolBarItems.size() != 0) {
            Iterator it = addedToolBarItems.iterator();
            Object obj;
            while(it.hasNext()) {
                obj = it.next();
                if(obj instanceof JComponent) {
                    addToToolBar(jtb, (JComponent)obj);
                } else if(obj instanceof Action) {
                    addToToolBar(jtb, (Action)obj);
                } // end of else
                //it.remove();
            } // end of while (it.hasNext())
        } // end of if (addedToolBarItems.size() != 0)
        if(jtb.getComponentCount() > 0 && statusIndicator == null) {
            jtb.add(Box.createHorizontalGlue());
            statusIndicator = new StatusIndicator();
            jtb.add(statusIndicator);
        }
        return jtb;
    }

    private void checkToolBar(JPanel newPanel, JToolBar jtb) {
        if(!toolBarAdded && jtoolbar.getComponentCount() != 0 && panel != null) {
            logger.debug("adding tool bar");
            toolBarAdded = true;
            panel.add(jtb, BorderLayout.NORTH);
        }
        jtoolbar.repaint();
    }

    public StatusIndicator getStatusIndicator() {
        return new StatusIndicator();
    }

    public synchronized Task getTask() throws ConfigurationException {
        if(task == null) {
            if(params.containsKey(TASK_INSTANCE)) {
                task = (Task)params.get(TASK_INSTANCE);
            } else {
                try {
                    Class c = Class.forName(classname);
                    task = (Task)c.newInstance();
                } catch(Exception ee) {
                    // can only throw ConfigurationException...
                    if(ee instanceof ConfigurationException) {
                        throw (ConfigurationException)ee;
                    } else {
                        throw new ConfigurationException("Couldn't load task! "
                                + classname, ee);
                    } // end of else
                }
            }
            configuringTask = true;
            task.configure(params);
            configuringTask = false;
            AllTasks.associateTaskWithTaskAction(this, task);
        }
        return task;
    }

    public Object getConfigParam(String key) {
        return params.get(key);
    }

    public void putConfigParam(String key, Object value) {
        params.put(key, value);
    }

    public HashMap getConfigParams() {
        return params;
    }

    /**
     * resets this taskaction and its task to a pre invoked state and destroys
     * the JFrame associated with it
     */
    public void reset() {
        reset(true);
    }

    /**
     * resets this TaskAction and its task to a pre invoked state
     * 
     * @param destroyFrame -
     *            if true, the frame associated with this taskAction is
     *            destroyed else it is only set to null in this TaskAction
     */
    public void reset(boolean destroyFrame) {
        reset(destroyFrame, true);
    }

    /**
     * resets this TaskAction and its task to a pre invoked state
     * 
     * @param destroyFrame -
     *            if true, the frame associated with this taskAction is
     *            destroyed else it is only set to null in this TaskAction.
     * @param nullifyTask
     *            sets the task object reference to null so that it will be
     *            recreated from scratch on next invocation.
     */
    public void reset(boolean destroyFrame, boolean nullifyTask) {
        if(destroyFrame) {
            destroyJFrame();
        } else {
            frame = null;
            displayed = false;
        }
        if(panel != null) {
            panel = null;
        } // end of if (panel != null)
        if(task != null) {
            task.destroy();
            if(nullifyTask) {
                task = null;
            }
        } // end of if (task != null)
        if(jtoolbar != null) {
            jtoolbar = null;
        } // end of if (jtoolbar != null)
        if(window != null) {
            window.hide();
            window.dispose();
            window = null;
        }
        if(statusIndicator != null) {
            statusIndicator.destroy();
            statusIndicator = null;
        }
        addedToolBarItems.clear();
        toolBarHandlerAdded = false;
        toolBarAdded = false;
        setEnabled(true);
    }

    private Dimension getSize() {
        return getSize(new Dimension(400, 400));
    }

    private Dimension getSize(Dimension defaultDim) {
        int width = defaultDim.width, height = defaultDim.height;
        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension screenSize = tk.getScreenSize();
        String widthPer = (String)params.get(WIDTHPER);
        if(widthPer != null) {
            double percentage = Integer.parseInt(widthPer) / 100.0;
            width = (int)(screenSize.width * percentage);
        }
        String widthPix = (String)params.get(WIDTH);
        if(widthPix != null) {
            width = Integer.parseInt(widthPix);
        }
        String heightPer = (String)params.get(HEIGHTPER);
        if(heightPer != null) {
            double percentage = Integer.parseInt(heightPer) / 100.0;
            height = (int)(screenSize.height * percentage);
        }
        String heightPix = (String)params.get(HEIGHT);
        if(heightPix != null) {
            height = Integer.parseInt(heightPix);
        }
        return new Dimension(width, height);
    }

    private boolean displayed;

    private String classname;

    private String id;

    private String iconName;

    private ToolBarHandler toolBarHandler;

    protected boolean toolBarHandlerAdded = false;

    private JToolBar jtoolbar = null;

    private LinkedList addedToolBarItems = new LinkedList();

    private boolean toggle;

    private HashMap params;

    private Task task = null;

    private boolean configuringTask = false;

    private JFrame frame = null;

    private JDialog window = null;

    private JPanel panel = null;

    private StatusIndicator statusIndicator;

    private boolean toolBarAdded = false;

    private static Logger logger = Logger.getLogger(TaskAction.class);

    private WindowListener defWindowListener = new MyWindowListener();

    public static boolean CHILDREN_GET_MENUBAR = false;

    public static final Dimension MIN_SIZE = new Dimension(200, 100);

    private class MyWindowListener extends WindowAdapter {

        public void windowClosed(WindowEvent e) {
            frame = null;
            displayed = false;
            reset(false, false);
        }
    }

    private static ClassLoader loader = TaskAction.class.getClassLoader();

    private static String triangle = "edu/sc/seis/gee/data/images/arrow-right.png";

    private static String placeholder = "edu/sc/seis/gee/data/images/blank-tallboy.png";

    private static ImageIcon rightTriangle = new ImageIcon(loader.getResource(triangle));

    private static ImageIcon singlePixelWide = new ImageIcon(loader.getResource(placeholder));
}// TaskAction
