/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.action;

import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.w3c.dom.Document;

import icy.clipboard.Clipboard;
import icy.file.FileUtil;
import icy.gui.dialog.MessageDialog;
import icy.gui.dialog.OpenDialog;
import icy.gui.dialog.SaveDialog;
import icy.gui.inspector.RoisPanel;
import icy.main.Icy;
import icy.preferences.GeneralPreferences;
import icy.resource.ResourceUtil;
import icy.resource.icon.IcyIcon;
import icy.roi.ROI;
import icy.roi.ROI2D;
import icy.roi.ROI3D;
import icy.roi.ROI4D;
import icy.roi.ROIUtil;
import icy.sequence.Sequence;
import icy.sequence.edit.ROIAddSequenceEdit;
import icy.sequence.edit.ROIAddsSequenceEdit;
import icy.sequence.edit.ROIReplacesSequenceEdit;
import icy.system.SystemUtil;
import icy.util.ClassUtil;
import icy.util.StringUtil;
import icy.util.XLSUtil;
import icy.util.XMLUtil;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import plugins.kernel.roi.roi2d.ROI2DRectangle;
import plugins.kernel.roi.roi3d.ROI3DStackRectangle;
import plugins.kernel.roi.roi4d.ROI4DStackRectangle;
import plugins.kernel.roi.roi5d.ROI5DStackRectangle;

/**
 * Roi actions (open / save / copy / paste / merge...)
 * 
 * @author Stephane
 */
public class RoiActions
{
    public static final String DEFAULT_ROI_DIR = "roi";
    public static final String DEFAULT_ROI_NAME = "roi.xml";

    public static IcyAbstractAction loadAction = new IcyAbstractAction("Load", new IcyIcon(ResourceUtil.ICON_OPEN),
            "Load ROI(s) from file", "Load ROI(s) from a XML file and add them to the active sequence")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 2378084039864016238L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final String filename = OpenDialog.chooseFile("Load roi(s)...", DEFAULT_ROI_DIR, DEFAULT_ROI_NAME);
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if ((filename != null) && (sequence != null))
            {
                final Document doc = XMLUtil.loadDocument(filename);

                if (doc != null)
                {
                    final List<ROI> rois = ROI.loadROIsFromXML(XMLUtil.getRootElement(doc));

                    sequence.beginUpdate();
                    try
                    {
                        // add to sequence
                        for (ROI roi : rois)
                            sequence.addROI(roi);
                    }
                    finally
                    {
                        sequence.endUpdate();
                    }

                    // add to undo manager
                    sequence.addUndoableEdit(new ROIAddsSequenceEdit(sequence, rois)
                    {
                        @Override
                        public String getPresentationName()
                        {
                            if (getROIs().size() > 1)
                                return "ROIs loaded from XML file";

                            return "ROI loaded from XML file";
                        };
                    });

                    return true;
                }
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction saveAction = new IcyAbstractAction("Save", new IcyIcon(ResourceUtil.ICON_SAVE),
            "Save selected ROI(s) to file", "Save the selected ROI(s) from active sequence into a XML file")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 349358870716619748L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final String filename = SaveDialog.chooseFile("Save roi(s)...", DEFAULT_ROI_DIR, DEFAULT_ROI_NAME);
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if ((filename != null) && (sequence != null))
            {
                final List<ROI> rois = sequence.getSelectedROIs();

                if (rois.size() > 0)
                {
                    final Document doc = XMLUtil.createDocument(true);

                    if (doc != null)
                    {
                        ROI.saveROIsToXML(XMLUtil.getRootElement(doc), rois);
                        XMLUtil.saveDocument(doc, filename);
                        return true;
                    }
                }
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction copyAction = new IcyAbstractAction("Copy", new IcyIcon(ResourceUtil.ICON_COPY),
            "Copy selected ROI to clipboard (Ctrl+C)", KeyEvent.VK_C, SystemUtil.getMenuCtrlMask())
    {
        /**
         * 
         */
        private static final long serialVersionUID = -4716027958152503425L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                final List<ROI> rois = sequence.getSelectedROIs();

                if (rois.size() > 0)
                {
                    // need to get a copy of the ROI (as it can change meanwhile)
                    for (int i = 0; i < rois.size(); i++)
                    {
                        final ROI roi = rois.get(i).getCopy();

                        if (roi != null)
                            rois.set(i, roi);
                    }

                    // save in the Icy clipboard
                    Clipboard.put(Clipboard.TYPE_ROILIST, rois);
                    // clear system clipboard
                    Clipboard.clearSystem();

                    pasteAction.setEnabled(true);

                    return true;
                }
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            return super.isEnabled() && (sequence != null) && (sequence.getSelectedROIs().size() > 0);
        }
    };

    public static IcyAbstractAction copyLinkAction = new IcyAbstractAction("Copy link",
            new IcyIcon(ResourceUtil.ICON_LINK_COPY), "Copy link of selected ROI to clipboard (Alt+C)", KeyEvent.VK_C,
            InputEvent.ALT_MASK)
    {
        /**
         * 
         */
        private static final long serialVersionUID = -4716027958152503425L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if (roisPanel != null)
            {
                final List<ROI> rois = roisPanel.getSelectedRois();

                if (rois.size() > 0)
                {
                    // save in the Icy clipboard
                    Clipboard.put(Clipboard.TYPE_ROILINKLIST, rois);
                    // clear system clipboard
                    Clipboard.clearSystem();

                    pasteLinkAction.setEnabled(true);

                    return true;
                }
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            return super.isEnabled() && (sequence != null) && (sequence.getSelectedROIs().size() > 0);
        }
    };

    public static IcyAbstractAction pasteAction = new IcyAbstractAction("Paste", new IcyIcon(ResourceUtil.ICON_PASTE),
            "Paste ROI from clipboard (Ctrl+V)", KeyEvent.VK_V, SystemUtil.getMenuCtrlMask())
    {
        /**
         * 
         */
        private static final long serialVersionUID = 4878585451006567513L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                @SuppressWarnings("unchecked")
                final List<ROI> rois = (List<ROI>) Clipboard.get(Clipboard.TYPE_ROILIST);

                if ((rois != null) && (rois.size() > 0))
                {
                    final List<ROI> copyRois = new ArrayList<ROI>();
                    sequence.beginUpdate();
                    try
                    {
                        // unselect all rois
                        sequence.setSelectedROI(null);

                        // add copy to sequence (so we can do the paste operation severals time)
                        for (ROI roi : rois)
                        {
                            final ROI newROI = roi.getCopy();

                            if (newROI != null)
                            {
                                copyRois.add(newROI);

                                // select the ROI
                                newROI.setSelected(true);
                                // and add it
                                sequence.addROI(newROI);
                            }
                        }
                    }
                    finally
                    {
                        sequence.endUpdate();
                    }

                    // add to undo manager
                    sequence.addUndoableEdit(new ROIAddsSequenceEdit(sequence, copyRois)
                    {
                        @Override
                        public String getPresentationName()
                        {
                            if (getROIs().size() > 1)
                                return "ROIs added from clipboard";

                            return "ROI added from clipboard";
                        };
                    });

                    return true;
                }
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null)
                    && Clipboard.getType().equals(Clipboard.TYPE_ROILIST);
        }
    };

    public static IcyAbstractAction pasteLinkAction = new IcyAbstractAction("Paste link",
            new IcyIcon(ResourceUtil.ICON_LINK_PASTE), "Paste ROI link from clipboard (Alt+V)", KeyEvent.VK_V,
            InputEvent.ALT_MASK)
    {
        /**
         * 
         */
        private static final long serialVersionUID = 4878585451006567513L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                @SuppressWarnings("unchecked")
                final List<ROI> rois = (List<ROI>) Clipboard.get(Clipboard.TYPE_ROILINKLIST);

                if ((rois != null) && (rois.size() > 0))
                {
                    sequence.beginUpdate();
                    try
                    {
                        // add to sequence
                        for (ROI roi : rois)
                            sequence.addROI(roi);
                    }
                    finally
                    {
                        sequence.endUpdate();
                    }

                    // add to undo manager
                    sequence.addUndoableEdit(new ROIAddsSequenceEdit(sequence, rois)
                    {
                        @Override
                        public String getPresentationName()
                        {
                            if (getROIs().size() > 1)
                                return "ROIs linked from clipboard";

                            return "ROI linked from clipboard";
                        };
                    });

                    return true;
                }
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null)
                    && Clipboard.getType().equals(Clipboard.TYPE_ROILINKLIST);
        }
    };

    // public static IcyAbstractAction clearClipboardAction = new IcyAbstractAction("Clear", new
    // IcyIcon(
    // ResourceUtil.ICON_CLIPBOARD_CLEAR), "Remove ROI saved in clipboard")
    // {
    // /**
    // *
    // */
    // private static final long serialVersionUID = 4878585451006567513L;
    //
    // @Override
    // public boolean doAction(ActionEvent e)
    // {
    // Clipboard.remove(ID_ROI_COPY_CLIPBOARD, false);
    // pasteAction.setEnabled(false);
    // }
    //
    // @Override
    // public boolean isEnabled()
    // {
    // return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null)
    // && Clipboard.hasObjects(RoiActions.ID_ROI_COPY_CLIPBOARD, false);
    // }
    // };

    public static IcyAbstractAction selectAllAction = new IcyAbstractAction("SelectAll", (IcyIcon) null,
            "Select all ROI(s)")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 3219000949426093919L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                sequence.setSelectedROIs((List<ROI>) sequence.getROIs());
                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            return super.isEnabled() && (sequence != null) && (sequence.getROIs().size() > 0);
        }
    };

    public static IcyAbstractAction unselectAction = new IcyAbstractAction("Unselect", (IcyIcon) null,
            "Unselect ROI(s)", KeyEvent.VK_ESCAPE)
    {
        /**
         * 
         */
        private static final long serialVersionUID = -6136680076368815566L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                sequence.setSelectedROI(null);
                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction deleteAction = new IcyAbstractAction("Delete",
            new IcyIcon(ResourceUtil.ICON_DELETE), "Delete selected ROI(s)",
            "Delete selected ROI(s) from the active sequence", KeyEvent.VK_DELETE, 0)
    {
        /**
         * 
         */
        private static final long serialVersionUID = 9079403002834893222L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                sequence.removeSelectedROIs(false, true);
                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            return super.isEnabled() && (sequence != null) && (sequence.getSelectedROIs().size() > 0);
        }
    };

    public static IcyAbstractAction boolNotAction = new IcyAbstractAction("Inversion",
            new IcyIcon(ResourceUtil.ICON_ROI_NOT), "Boolean inversion operation",
            "Create a new ROI representing the inverse of selected ROI", true, "Computing inverse...")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 6360796066188754099L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if ((sequence != null) && (roisPanel != null))
            {
                // NOT operation
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROI = roisPanel.getSelectedRois();

                    // work only on single ROI
                    if (selectedROI.size() != 1)
                        return false;

                    final ROI roi = selectedROI.get(0);
                    final ROI seqRoi;

                    switch (roi.getDimension())
                    {
                        case 2:
                            final ROI2D roi2d = (ROI2D) roi;
                            final ROI2DRectangle seqRoi2d = new ROI2DRectangle(sequence.getBounds2D());
                            // set on same position
                            seqRoi2d.setZ(roi2d.getZ());
                            seqRoi2d.setT(roi2d.getT());
                            seqRoi2d.setC(roi2d.getC());
                            seqRoi = seqRoi2d;
                            break;

                        case 3:
                            final ROI3D roi3d = (ROI3D) roi;
                            final ROI3DStackRectangle seqRoi3d = new ROI3DStackRectangle(
                                    sequence.getBounds5D().toRectangle3D());
                            // set on same position
                            seqRoi3d.setT(roi3d.getT());
                            seqRoi3d.setC(roi3d.getC());
                            seqRoi = seqRoi3d;
                            break;

                        case 4:
                            final ROI4D roi4d = (ROI4D) roi;
                            final ROI4DStackRectangle seqRoi4d = new ROI4DStackRectangle(
                                    sequence.getBounds5D().toRectangle4D());
                            // set on same position
                            seqRoi4d.setC(roi4d.getC());
                            seqRoi = seqRoi4d;
                            break;

                        case 5:
                            seqRoi = new ROI5DStackRectangle(sequence.getBounds5D());
                            break;

                        default:
                            seqRoi = null;
                            break;
                    }

                    if (seqRoi != null)
                    {
                        // we do the NOT operation by subtracting current ROI to sequence bounds ROI
                        final ROI mergeROI = ROIUtil.subtract(seqRoi, roi);

                        if (mergeROI != null)
                        {
                            mergeROI.setName("Inverse");

                            sequence.addROI(mergeROI);
                            sequence.setSelectedROI(mergeROI);

                            // add to undo manager
                            sequence.addUndoableEdit(new ROIAddSequenceEdit(sequence, mergeROI, "ROI Inverse"));
                        }
                    }
                    else
                        MessageDialog.showDialog("Operation not supported", "Input ROI has incorrect dimension !",
                                MessageDialog.ERROR_MESSAGE);
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.getLocalizedMessage(),
                            MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction boolOrAction = new IcyAbstractAction("Union", new IcyIcon(ResourceUtil.ICON_ROI_OR),
            "Boolean union operation", "Create a new ROI representing the union of selected ROIs", true,
            "Computing union...")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 1861052712498233441L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if ((sequence != null) && (roisPanel != null))
            {
                // OR operation
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROIs = roisPanel.getSelectedRois();
                    final ROI mergeROI = ROIUtil.getUnion(selectedROIs);

                    if (mergeROI != null)
                    {
                        mergeROI.setName("Union");

                        sequence.addROI(mergeROI);
                        sequence.setSelectedROI(mergeROI);

                        // add to undo manager
                        sequence.addUndoableEdit(new ROIAddSequenceEdit(sequence, mergeROI, "ROI Union"));
                    }
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.getLocalizedMessage(),
                            MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction boolAndAction = new IcyAbstractAction("Intersection",
            new IcyIcon(ResourceUtil.ICON_ROI_AND), "Boolean intersection operation",
            "Create a new ROI representing the intersection of selected ROIs", true, "Computing intersection...")
    {
        /**
         * 
         */
        private static final long serialVersionUID = -9103158044679039413L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if ((sequence != null) && (roisPanel != null))
            {
                // AND operation
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROIs = roisPanel.getSelectedRois();
                    final ROI mergeROI = ROIUtil.getIntersection(selectedROIs);

                    if (mergeROI != null)
                    {
                        mergeROI.setName("Intersection");

                        sequence.addROI(mergeROI);
                        sequence.setSelectedROI(mergeROI);

                        // add to undo manager
                        sequence.addUndoableEdit(new ROIAddSequenceEdit(sequence, mergeROI, "ROI Intersection"));
                    }
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.getLocalizedMessage(),
                            MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction boolXorAction = new IcyAbstractAction("Exclusive union",
            new IcyIcon(ResourceUtil.ICON_ROI_XOR), "Boolean exclusive union operation",
            "Create a new ROI representing the exclusive union of selected ROIs", true, "Computing exclusive union...")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 1609345474914807703L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if ((sequence != null) && (roisPanel != null))
            {
                // XOR operation
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROIs = roisPanel.getSelectedRois();
                    final ROI mergeROI = ROIUtil.getExclusiveUnion(selectedROIs);

                    if (mergeROI != null)
                    {
                        mergeROI.setName("Exclusive union");

                        sequence.addROI(mergeROI);
                        sequence.setSelectedROI(mergeROI);

                        // add to undo manager
                        sequence.addUndoableEdit(new ROIAddSequenceEdit(sequence, mergeROI, "ROI Exclusive Union"));
                    }
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.getLocalizedMessage(),
                            MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction boolSubtractAction = new IcyAbstractAction("Subtraction",
            new IcyIcon(ResourceUtil.ICON_ROI_SUB), "Boolean subtraction",
            "Create 2 ROIs representing the result of (A - B) and (B - A)", true, "Computing subtraction...")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 9094641559971542667L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if ((sequence != null) && (roisPanel != null))
            {
                // SUB operation
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROI = roisPanel.getSelectedRois();
                    final List<ROI> generatedROIs = new ArrayList<ROI>();

                    // Subtraction work only when 2 ROI are selected
                    if (selectedROI.size() != 2)
                        return false;

                    final ROI subtractAB = ROIUtil.subtract(selectedROI.get(0), selectedROI.get(1));
                    final ROI subtractBA = ROIUtil.subtract(selectedROI.get(1), selectedROI.get(0));

                    subtractAB.setName("Subtract A-B");
                    subtractBA.setName("Subtract B-A");

                    generatedROIs.add(subtractAB);
                    generatedROIs.add(subtractBA);

                    sequence.beginUpdate();
                    try
                    {
                        for (ROI roi : generatedROIs)
                            sequence.addROI(roi);

                        sequence.setSelectedROIs(generatedROIs);

                        // add to undo manager
                        sequence.addUndoableEdit(new ROIAddsSequenceEdit(sequence, generatedROIs, "ROI Subtraction"));
                    }
                    finally
                    {
                        sequence.endUpdate();
                    }
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.getLocalizedMessage(),
                            MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction xlsExportAction = new IcyAbstractAction("Export",
            new IcyIcon(ResourceUtil.ICON_XLS_EXPORT), "ROI Excel export",
            "Export the content of the ROI table into a XLS/CSV file", true, "Exporting ROI informations...")
    {
        /**
         * 
         */
        private static final long serialVersionUID = 9094641559971542667L;

        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if ((sequence != null) && (roisPanel != null))
            {
                final String content = roisPanel.getCSVFormattedInfos();

                if (StringUtil.isEmpty(content) || roisPanel.getVisibleRois().isEmpty())
                {
                    MessageDialog.showDialog("Nothing to export !", MessageDialog.INFORMATION_MESSAGE);
                    return true;
                }

                // get the global result folder
                final String dir = GeneralPreferences.getResultFolder();
                // create it if needed
                FileUtil.createDir(dir);

                final String filename = SaveDialog.chooseFile("Export ROIs...", dir, "result", ".xls");

                if (filename != null)
                {
                    // update result folder
                    GeneralPreferences.setResultFolder(FileUtil.getDirectory(filename));

                    // CSV format wanted ?
                    if (FileUtil.getFileExtension(filename, false).equalsIgnoreCase("csv"))
                    {
                        try
                        {
                            // just write CSV content
                            final PrintWriter out = new PrintWriter(filename);
                            out.println(content);
                            out.close();
                        }
                        catch (FileNotFoundException e1)
                        {
                            MessageDialog.showDialog("Error", e1.getMessage(), MessageDialog.ERROR_MESSAGE);
                        }
                    }
                    // XLS export
                    else
                    {
                        try
                        {
                            final WritableWorkbook workbook = XLSUtil.createWorkbook(filename);
                            final WritableSheet sheet = XLSUtil.createNewPage(workbook, "ROIS");
                            final BufferedReader br = new BufferedReader(new StringReader(content));

                            String line;
                            int y = 0;
                            while ((line = br.readLine()) != null)
                            {
                                int x = 0;

                                // use tab as separator
                                for (String col : line.split("\t"))
                                {
                                    XLSUtil.setCellString(sheet, x, y, col);
                                    x++;
                                }

                                y++;
                            }

                            XLSUtil.saveAndClose(workbook);
                        }
                        catch (Exception e1)
                        {
                            MessageDialog.showDialog("Error", e1.getMessage(), MessageDialog.ERROR_MESSAGE);
                        }
                    }
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            return super.isEnabled() && (sequence != null);
        }
    };

    public static IcyAbstractAction settingAction = new IcyAbstractAction("Preferences",
            new IcyIcon(ResourceUtil.ICON_COG), "ROI table preferences")
    {
        @Override
        public boolean doAction(ActionEvent e)
        {
            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();

            if (roisPanel != null)
            {
                roisPanel.showSettingPanel();

                return true;
            }

            return false;
        }
    };

    public static IcyAbstractAction convertToStackAction = new IcyAbstractAction("to 3D stack",
            new IcyIcon(ResourceUtil.ICON_LAYER_V2), "Convert to 3D stack ROI",
            "Convert selected 2D ROI to 3D stack ROI by stacking it along the Z axis")
    {
        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                final int maxZ = sequence.getSizeZ() - 1;

                // ROI Z stack conversion
                sequence.beginUpdate();
                try
                {
                    final List<ROI2D> selectedROIs = sequence.getSelectedROI2Ds();
                    final List<ROI> removedROIs = new ArrayList<ROI>();
                    final List<ROI> addedROIs = new ArrayList<ROI>();

                    for (ROI2D roi : selectedROIs)
                    {
                        final ROI stackedRoi = ROIUtil.convertToStack(roi, 0, maxZ);

                        if (stackedRoi != null)
                        {
                            // select it by default
                            stackedRoi.setSelected(true);

                            sequence.removeROI(roi);
                            sequence.addROI(stackedRoi);

                            // add to undo manager
                            removedROIs.add(roi);
                            addedROIs.add(stackedRoi);
                        }
                    }

                    if (!addedROIs.isEmpty())
                        sequence.addUndoableEdit(new ROIReplacesSequenceEdit(sequence, removedROIs, addedROIs,
                                (addedROIs.size() > 1) ? "ROIs 3D stack conversion" : "ROI 3D stack conversion"));
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.toString(), MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction convertToMaskAction = new IcyAbstractAction("to Mask",
            new IcyIcon(ResourceUtil.ICON_BOOL_MASK), "Convert Shape ROI to Mask ROI",
            "Convert selected Shape ROI to Mask ROI by using their boolean mask")
    {
        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                // ROI mask conversion
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROIs = sequence.getSelectedROIs();
                    final List<ROI> removedROIs = new ArrayList<ROI>();
                    final List<ROI> addedROIs = new ArrayList<ROI>();

                    for (ROI roi : selectedROIs)
                    {
                        final ROI maskRoi = ROIUtil.convertToMask(roi);

                        if (maskRoi != null)
                        {
                            // select it by default
                            maskRoi.setSelected(true);

                            sequence.removeROI(roi);
                            sequence.addROI(maskRoi);

                            // add to undo manager
                            removedROIs.add(roi);
                            addedROIs.add(maskRoi);
                        }
                    }

                    if (!addedROIs.isEmpty())
                        sequence.addUndoableEdit(new ROIReplacesSequenceEdit(sequence, removedROIs, addedROIs,
                                (addedROIs.size() > 1) ? "ROIs mask conversion" : "ROI mask conversion"));
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.toString(), MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction convertToShapeAction = new IcyAbstractAction("to Shape",
            new IcyIcon(ResourceUtil.ICON_ROI_POLYGON), "Convert Mask ROI to Polygon shape ROI",
            "Convert selected Mask ROI to Shape ROI using polygon approximation")
    {
        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                // ROI shape conversion
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROIs = sequence.getSelectedROIs();
                    final List<ROI> removedROIs = new ArrayList<ROI>();
                    final List<ROI> addedROIs = new ArrayList<ROI>();

                    for (ROI roi : selectedROIs)
                    {
                        final ROI shapeRoi = ROIUtil.convertToShape(roi, -1);

                        if (shapeRoi != null)
                        {
                            // select it by default
                            shapeRoi.setSelected(true);

                            sequence.removeROI(roi);
                            sequence.addROI(shapeRoi);

                            // add to undo manager
                            removedROIs.add(roi);
                            addedROIs.add(shapeRoi);
                        }
                    }

                    if (!addedROIs.isEmpty())
                        sequence.addUndoableEdit(new ROIReplacesSequenceEdit(sequence, removedROIs, addedROIs,
                                (addedROIs.size() > 1) ? "ROIs shape conversion" : "ROI shape conversion"));
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.toString(), MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    public static IcyAbstractAction separateObjectsAction = new IcyAbstractAction("Separate",
            new IcyIcon(ResourceUtil.ICON_ROI_COMP), "Separate objects from selected Mask ROI",
            "Separate objects (connected components) from selected Mask ROI.")
    {
        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                sequence.beginUpdate();
                try
                {
                    final List<ROI> selectedROIs = sequence.getSelectedROIs();
                    final List<ROI> removedROIs = new ArrayList<ROI>();
                    final List<ROI> addedROIs = new ArrayList<ROI>();

                    for (ROI roi : selectedROIs)
                    {
                        final List<ROI> components = ROIUtil.getConnectedComponents(roi);

                        // nothing to do if we obtain only 1 component
                        if (components.size() > 1)
                        {
                            sequence.removeROI(roi);
                            removedROIs.add(roi);

                            for (ROI component : components)
                            {
                                sequence.addROI(component);
                                // add to undo manager
                                addedROIs.add(component);
                            }
                        }
                    }

                    if (!removedROIs.isEmpty())
                        sequence.addUndoableEdit(new ROIReplacesSequenceEdit(sequence, removedROIs, addedROIs,
                                (removedROIs.size() > 1) ? "ROIs separate objects" : "ROI separate objects"));
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.toString(), MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    // public static IcyAbstractAction manualCutAction = new IcyAbstractAction("Manual cut",
    // new IcyIcon(ResourceUtil.ICON_CUT), "Manual cut/split ROI",
    // "Manual cut/split ROI by drawing a straight 2D line over it.")
    // {
    // @Override
    // public boolean doAction(ActionEvent e)
    // {
    // // we do nothing here, ROI cut is done simulating a specific ROI
    // final Viewer viewer = Icy.getMainInterface().getActiveViewer();
    // if (viewer == null) return false;
    //
    //
    //
    //
    // return false;
    // }
    //
    // @Override
    // public boolean isEnabled()
    // {
    // return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
    // }
    // };

    public static IcyAbstractAction autoSplitAction = new IcyAbstractAction("Auto split",
            new IcyIcon("split_roi", true), "Automatic split selected ROI",
            "Automatic split selected ROI using shape and size information.")
    {
        @Override
        public boolean doAction(ActionEvent e)
        {
            final Sequence sequence = Icy.getMainInterface().getActiveSequence();

            if (sequence != null)
            {
                sequence.beginUpdate();
                try
                {
                    final List<ROI2D> selectedROIs = sequence.getSelectedROI2Ds();
                    final List<ROI> removedROIs = new ArrayList<ROI>();
                    final List<ROI> addedROIs = new ArrayList<ROI>();

                    for (ROI2D roi : selectedROIs)
                    {
                        // --> TODO
                        // final List<ROI> components = ROIUtil.split(roi);
                        //
                        // nothing to do if we obtain only 1 component
                        // if (components.size() > 1)
                        // {
                        // sequence.removeROI(roi);
                        // removedROIs.add(roi);
                        //
                        // for (ROI component : components)
                        // {
                        // sequence.addROI(component);
                        // // add to undo manager
                        // addedROIs.add(component);
                        // }
                        // }
                    }

                    if (!removedROIs.isEmpty())
                        sequence.addUndoableEdit(new ROIReplacesSequenceEdit(sequence, removedROIs, addedROIs,
                                (removedROIs.size() > 1) ? "ROIs automatic split" : "ROI automatic split"));
                }
                catch (UnsupportedOperationException ex)
                {
                    MessageDialog.showDialog("Operation not supported", ex.toString(), MessageDialog.ERROR_MESSAGE);
                }
                finally
                {
                    sequence.endUpdate();
                }

                return true;
            }

            return false;
        }

        @Override
        public boolean isEnabled()
        {
            return super.isEnabled() && (Icy.getMainInterface().getActiveSequence() != null);
        }
    };

    /**
     * Return all actions of this class
     */
    public static List<IcyAbstractAction> getAllActions()
    {
        final List<IcyAbstractAction> result = new ArrayList<IcyAbstractAction>();

        for (Field field : RoiActions.class.getFields())
        {
            final Class<?> type = field.getType();

            try
            {
                if (ClassUtil.isSubClass(type, IcyAbstractAction[].class))
                    result.addAll(Arrays.asList(((IcyAbstractAction[]) field.get(null))));
                else if (ClassUtil.isSubClass(type, IcyAbstractAction.class))
                    result.add((IcyAbstractAction) field.get(null));
            }
            catch (Exception e)
            {
                // ignore
            }
        }

        return result;
    }
}