/*
 * 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.gui.dialog;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import icy.common.exception.UnsupportedFormatException;
import icy.file.Loader;
import icy.file.SequenceFileImporter;
import icy.gui.component.RangeComponent;
import icy.gui.component.Region2DComponent;
import icy.gui.component.SpecialValueSpinner;
import icy.gui.component.ThumbnailComponent;
import icy.gui.component.model.SpecialValueSpinnerModel;
import icy.resource.ResourceUtil;
import icy.sequence.MetaDataUtil;
import icy.system.thread.ThreadUtil;
import icy.util.StringUtil;
import loci.formats.ome.OMEXMLMetadataImpl;

public class LoaderOptionPanel extends JPanel
{
    private class PreviewUpdater extends Thread
    {
        final String fileId;
        int z;
        int t;
        int c;
        boolean imageChangeOnly;

        public PreviewUpdater(String fileId, int z, int t, int c)
        {
            super("Image preview");

            this.fileId = fileId;
            this.z = z;
            this.t = t;
            this.c = c;

            // on first preview update, we should have Z,T,C set to -1
            imageChangeOnly = (z != -1) || (t != -1) || (c != -1);
        }

        public PreviewUpdater(String fileId)
        {
            super("Image preview");

            this.fileId = fileId;

            // on first preview update, we should have Z,T,C set to -1
            imageChangeOnly = false;
        }

        @Override
        public void run()
        {
            // interrupt process
            if (isInterrupted())
                return;

            // only need to update image
            if (imageChangeOnly)
            {
                try
                {
                    // use last - 1 resolution
                    final int res = Math.max(0, resolutionSlider.getMaximum() - 1);

                    // not defined --> use middle
                    if (z == -1)
                        z = MetaDataUtil.getSizeZ(metadata, 0) / 2;
                    // not defined --> use middle
                    if (t == -1)
                        t = MetaDataUtil.getSizeT(metadata, 0) / 2;

                    final List<SequenceFileImporter> importers = Loader.getSequenceFileImporters(fileId);

                    for (SequenceFileImporter importer : importers)
                    {
                        // interrupt process
                        if (isInterrupted())
                            break;

                        try
                        {
                            if (importer.open(fileId, 0))
                            {
                                try
                                {
                                    // default position --> use thumbnail
                                    if ((z == 0) && (t == 0) && (c == -1))
                                        preview.setImage(importer.getThumbnail(0));
                                    // all channel
                                    else if (c == -1)
                                        preview.setImage(importer.getImage(0, res, z, t));
                                    // specific channel
                                    else
                                        preview.setImage(importer.getImage(0, res, z, t, c));
                                }
                                finally
                                {
                                    importer.close();
                                }
                            }
                        }
                        catch (UnsupportedFormatException e)
                        {
                            // try next importer...
                        }
                        catch (RuntimeException e)
                        {
                            // try next importer...
                        }
                        catch (IOException e)
                        {
                            // try next importer...
                        }

                        // done
                        break;

                    }
                }
                catch (Throwable t)
                {
                    // ignore
                    System.out.println("truc");
                }

                // image updated
                return;
            }

            boolean metaDataDone = false;
            boolean thumbnailDone = false;

            metadata = null;

            if (StringUtil.isEmpty(fileId))
            {
                preview.setImage(null);
                preview.setInfos("");

                metadata = new OMEXMLMetadataImpl();

                metaDataDone = true;
                thumbnailDone = true;

                return;
            }

            preview.setImage(ResourceUtil.ICON_WAIT);
            preview.setInfos("loading...");

            try
            {
                // use Callable as we can get interrupted here...
                ThreadUtil.invokeNow(new Callable<Boolean>()
                {
                    @Override
                    public Boolean call() throws Exception
                    {
                        // update panel
                        updatePanel();

                        return Boolean.TRUE;
                    }
                });

                final List<SequenceFileImporter> importers = Loader.getSequenceFileImporters(fileId);

                for (SequenceFileImporter importer : importers)
                {
                    // interrupt process
                    if (isInterrupted())
                        break;

                    try
                    {
                        if (importer.open(fileId, 0))
                        {
                            // interrupt process
                            if (isInterrupted())
                                break;

                            try
                            {
                                if (!metaDataDone)
                                {
                                    metadata = importer.getMetaData();

                                    // update it as soon as possible (use Callable as we can get interrupted here...)
                                    ThreadUtil.invokeNow(new Callable<Boolean>()
                                    {
                                        @Override
                                        public Boolean call() throws Exception
                                        {
                                            // update panel
                                            updatePanel();

                                            return Boolean.TRUE;
                                        }
                                    });

                                    // now the different "metadata" fields are up to date
                                    metadataFieldsOk = true;

                                    // initial preview --> update default range and channel positions
                                    pZMin = getZMin();
                                    pZMax = getZMax();
                                    pTMin = getTMin();
                                    pTMax = getTMax();
                                    pCh = getChannel();

                                    final int sizeC = MetaDataUtil.getSizeC(metadata, 0);

                                    // load metadata first
                                    preview.setInfos(MetaDataUtil.getSizeX(metadata, 0) + " x "
                                            + MetaDataUtil.getSizeY(metadata, 0) + " - "
                                            + MetaDataUtil.getSizeZ(metadata, 0) + "Z x "
                                            + MetaDataUtil.getSizeT(metadata, 0) + "T - " + sizeC + " ch ("
                                            + MetaDataUtil.getDataType(metadata, 0) + ")");

                                    metaDataDone = true;
                                }

                                // interrupt process
                                if (isInterrupted())
                                    break;

                                if (!thumbnailDone)
                                {
                                    // initial preview --> use thumbnail
                                    preview.setImage(importer.getThumbnail(0));

                                    thumbnailDone = true;
                                }
                            }
                            finally
                            {
                                importer.close();
                            }
                        }
                    }
                    catch (UnsupportedFormatException e)
                    {
                        // try next importer...
                    }
                    catch (RuntimeException e)
                    {
                        // try next importer...
                    }
                    catch (IOException e)
                    {
                        // try next importer...
                    }

                    // we correctly loaded both metadata and thumbnail --> exit
                    if (metaDataDone && thumbnailDone)
                        break;
                }
            }
            catch (InterruptedException t)
            {
                // no need to do more here...
                return;
            }
            catch (Throwable t)
            {
                // ignore
            }

            try
            {
                // cannot read metadata
                if (!metaDataDone)
                {
                    preview.setInfos("Cannot read file");

                    // use Callable as we can get interrupted here...
                    ThreadUtil.invokeNow(new Callable<Boolean>()
                    {
                        @Override
                        public Boolean call() throws Exception
                        {
                            // update panel
                            updatePanel();

                            return Boolean.TRUE;
                        }
                    });
                }

                // cannot get thumbnail
                if (!thumbnailDone)
                    preview.setImage(ResourceUtil.ICON_DELETE);
            }
            catch (Throwable t)
            {
                // probably interrupted...
            }
        }
    }

    /**
     * 
     */
    private static final long serialVersionUID = 4180367632912879286L;

    /**
     * GUI
     */
    protected ThumbnailComponent preview;
    protected JPanel optionsPanel;
    protected JLabel resolutionLevelLabel;
    protected JLabel loadInSeparatedLabel;
    protected JLabel autoOrderLabel;
    protected JCheckBox separateSeqCheck;
    protected JCheckBox autoOrderCheck;
    protected JSlider resolutionSlider;
    protected JLabel zRangeLabel;
    protected JLabel tRangeLabel;
    protected JLabel channelLabel;
    protected RangeComponent zRangeComp;
    protected RangeComponent tRangeComp;
    protected SpecialValueSpinner channelSpinner;
    protected JLabel resolutionFixLabel;
    protected JLabel xyRegionLabel;
    protected Region2DComponent xyRegionComp;
    protected JCheckBox xyRegionLoadingCheck;

    // internals
    protected boolean autoOrderEnable;
    protected boolean metadataFieldsOk;
    protected PreviewUpdater previewThread;
    protected OMEXMLMetadataImpl metadata;
    protected int pZMin;
    protected int pZMax;
    protected int pTMin;
    protected int pTMax;
    protected int pCh;
    protected boolean multiFile;
    protected boolean updatingPanel;

    /**
     * Create the panel.
     */
    public LoaderOptionPanel(boolean separate, boolean autoOrder)
    {
        super();

        autoOrderEnable = true;
        previewThread = null;
        metadataFieldsOk = false;
        metadata = null;
        pZMin = -1;
        pZMax = -1;
        pTMin = -1;
        pTMax = -1;
        pCh = -1;
        updatingPanel = false;

        initialize(separate, autoOrder);

        // default
        multiFile = true;
        setMultiFile(false);
        updatePanel();
    }

    private void initialize(boolean separate, boolean autoOrder)
    {
        setBorder(BorderFactory.createTitledBorder((Border) null));
        setLayout(new BorderLayout());

        preview = new ThumbnailComponent(false);
        preview.setShortDisplay(true);

        add(preview, BorderLayout.CENTER);

        optionsPanel = new JPanel();
        add(optionsPanel, BorderLayout.SOUTH);
        GridBagLayout gbl_optionsPanel = new GridBagLayout();
        gbl_optionsPanel.columnWidths = new int[] {0, 50, 0, 0, 0};
        gbl_optionsPanel.rowHeights = new int[] {0, 0, 0, 0, 0, 0, 0, 0};
        gbl_optionsPanel.columnWeights = new double[] {0.0, 1.0, 1.0, 0.0, Double.MIN_VALUE};
        gbl_optionsPanel.rowWeights = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
        optionsPanel.setLayout(gbl_optionsPanel);
        GridBagConstraints gbc_loadInSeparatedLabel = new GridBagConstraints();
        gbc_loadInSeparatedLabel.gridwidth = 3;
        gbc_loadInSeparatedLabel.anchor = GridBagConstraints.WEST;
        gbc_loadInSeparatedLabel.insets = new Insets(0, 0, 5, 5);
        gbc_loadInSeparatedLabel.gridx = 0;
        gbc_loadInSeparatedLabel.gridy = 0;
        loadInSeparatedLabel = new JLabel("Load in separated sequences");
        loadInSeparatedLabel.setToolTipText("All images are opened in its own sequence");
        optionsPanel.add(loadInSeparatedLabel, gbc_loadInSeparatedLabel);

        // setting GUI
        separateSeqCheck = new JCheckBox();
        separateSeqCheck.setToolTipText("All images are opened in its own sequence");
        loadInSeparatedLabel.setLabelFor(separateSeqCheck);
        separateSeqCheck.setSelected(separate);
        separateSeqCheck.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                updateAutoOrderEnableState();
            }
        });
        GridBagConstraints gbc_separateSeqCheck = new GridBagConstraints();
        gbc_separateSeqCheck.insets = new Insets(0, 0, 5, 0);
        gbc_separateSeqCheck.anchor = GridBagConstraints.NORTHEAST;
        gbc_separateSeqCheck.gridx = 3;
        gbc_separateSeqCheck.gridy = 0;
        optionsPanel.add(separateSeqCheck, gbc_separateSeqCheck);

        autoOrderLabel = new JLabel("Automatic ordering");
        autoOrderLabel.setToolTipText("Try to automatically set Z, T, C position of an image from their file name");
        GridBagConstraints gbc_autoOrderLabel = new GridBagConstraints();
        gbc_autoOrderLabel.gridwidth = 3;
        gbc_autoOrderLabel.anchor = GridBagConstraints.WEST;
        gbc_autoOrderLabel.insets = new Insets(0, 0, 5, 5);
        gbc_autoOrderLabel.gridx = 0;
        gbc_autoOrderLabel.gridy = 1;
        optionsPanel.add(autoOrderLabel, gbc_autoOrderLabel);

        autoOrderCheck = new JCheckBox("");
        autoOrderCheck.setToolTipText("Try to automatically set Z, T, C position of an image from their file name");
        autoOrderLabel.setLabelFor(autoOrderCheck);
        autoOrderCheck.setSelected(autoOrder);
        GridBagConstraints gbc_autoOrderCheck = new GridBagConstraints();
        gbc_autoOrderCheck.anchor = GridBagConstraints.EAST;
        gbc_autoOrderCheck.insets = new Insets(0, 0, 5, 0);
        gbc_autoOrderCheck.gridx = 3;
        gbc_autoOrderCheck.gridy = 1;

        optionsPanel.add(autoOrderCheck, gbc_autoOrderCheck);

        resolutionFixLabel = new JLabel("Resolution");
        resolutionFixLabel.setToolTipText("Select resolution level to open");
        GridBagConstraints gbc_resolutionFixLabel = new GridBagConstraints();
        gbc_resolutionFixLabel.anchor = GridBagConstraints.WEST;
        gbc_resolutionFixLabel.insets = new Insets(0, 0, 5, 5);
        gbc_resolutionFixLabel.gridx = 0;
        gbc_resolutionFixLabel.gridy = 2;
        optionsPanel.add(resolutionFixLabel, gbc_resolutionFixLabel);

        resolutionLevelLabel = new JLabel("");
        resolutionLevelLabel.setToolTipText("Select resolution level to open");
        GridBagConstraints gbc_resolutionLevelLabel = new GridBagConstraints();
        gbc_resolutionLevelLabel.anchor = GridBagConstraints.WEST;
        gbc_resolutionLevelLabel.insets = new Insets(0, 0, 5, 5);
        gbc_resolutionLevelLabel.gridx = 1;
        gbc_resolutionLevelLabel.gridy = 2;
        optionsPanel.add(resolutionLevelLabel, gbc_resolutionLevelLabel);

        resolutionSlider = new JSlider();
        resolutionSlider.setSnapToTicks(true);
        resolutionSlider.setMinimumSize(new Dimension(36, 22));
        resolutionSlider.setMaximumSize(new Dimension(100, 22));
        resolutionSlider.setPreferredSize(new Dimension(100, 22));
        resolutionSlider.setToolTipText("Select resolution level to open");
        resolutionSlider.setValue(0);
        resolutionSlider.setMaximum(10);
        resolutionSlider.setFocusable(false);
        resolutionSlider.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                updateFinaleResolution();
            }
        });
        GridBagConstraints gbc_resolutionSlider = new GridBagConstraints();
        gbc_resolutionSlider.fill = GridBagConstraints.BOTH;
        gbc_resolutionSlider.gridwidth = 2;
        gbc_resolutionSlider.insets = new Insets(0, 0, 5, 0);
        gbc_resolutionSlider.gridx = 2;
        gbc_resolutionSlider.gridy = 2;
        optionsPanel.add(resolutionSlider, gbc_resolutionSlider);

        xyRegionLabel = new JLabel("XY region");
        xyRegionLabel.setToolTipText("Rectangular region to load (X,Y,W,H) in original resolution coordinates (pixel)");
        GridBagConstraints gbc_xyRegionLabel = new GridBagConstraints();
        gbc_xyRegionLabel.anchor = GridBagConstraints.WEST;
        gbc_xyRegionLabel.insets = new Insets(0, 0, 5, 5);
        gbc_xyRegionLabel.gridx = 0;
        gbc_xyRegionLabel.gridy = 3;
        optionsPanel.add(xyRegionLabel, gbc_xyRegionLabel);

        xyRegionComp = new Region2DComponent();
        xyRegionComp.setInteger(true);
        xyRegionComp.setToolTipText("Rectangular region to load (X,Y,W,H) in original resolution coordinates");
        GridBagConstraints gbc_xyRegionComp = new GridBagConstraints();
        gbc_xyRegionComp.gridwidth = 2;
        gbc_xyRegionComp.insets = new Insets(0, 0, 5, 5);
        gbc_xyRegionComp.fill = GridBagConstraints.HORIZONTAL;
        gbc_xyRegionComp.gridx = 1;
        gbc_xyRegionComp.gridy = 3;
        optionsPanel.add(xyRegionComp, gbc_xyRegionComp);

        xyRegionLoadingCheck = new JCheckBox("");
        xyRegionLoadingCheck.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                xyRegionComp.setEnabled(xyRegionLoadingCheck.isSelected());
            }
        });
        xyRegionLoadingCheck.setToolTipText("Enable region loading");
        GridBagConstraints gbc_xyRegionLoadingCheck = new GridBagConstraints();
        gbc_xyRegionLoadingCheck.insets = new Insets(0, 0, 5, 0);
        gbc_xyRegionLoadingCheck.gridx = 3;
        gbc_xyRegionLoadingCheck.gridy = 3;
        optionsPanel.add(xyRegionLoadingCheck, gbc_xyRegionLoadingCheck);

        zRangeLabel = new JLabel("Z range  ");
        zRangeLabel.setToolTipText("Z interval to load");
        GridBagConstraints gbc_zRangeLabel = new GridBagConstraints();
        gbc_zRangeLabel.anchor = GridBagConstraints.WEST;
        gbc_zRangeLabel.insets = new Insets(0, 0, 5, 5);
        gbc_zRangeLabel.gridx = 0;
        gbc_zRangeLabel.gridy = 4;
        optionsPanel.add(zRangeLabel, gbc_zRangeLabel);

        zRangeComp = new RangeComponent();
        zRangeComp.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                rangeChanged();
            }
        });
        zRangeComp.setToolTipText("Z interval to load");
        zRangeComp.setMinimumSize(new Dimension(130, 22));
        zRangeComp.setMaximumSize(new Dimension(180, 22));
        zRangeComp.getHighSpinner().setPreferredSize(new Dimension(50, 20));
        zRangeComp.getHighSpinner().setMaximumSize(new Dimension(50, 20));
        zRangeComp.getLowSpinner().setMaximumSize(new Dimension(50, 20));
        zRangeComp.getLowSpinner().setPreferredSize(new Dimension(50, 20));
        zRangeComp.setPreferredSize(new Dimension(180, 22));
        zRangeComp.getSlider().setPreferredSize(new Dimension(100, 22));
        zRangeComp.setSliderVisible(true);
        GridBagConstraints gbc_zRangeComp = new GridBagConstraints();
        gbc_zRangeComp.gridwidth = 3;
        gbc_zRangeComp.insets = new Insets(0, 0, 5, 0);
        gbc_zRangeComp.fill = GridBagConstraints.BOTH;
        gbc_zRangeComp.gridx = 1;
        gbc_zRangeComp.gridy = 4;
        optionsPanel.add(zRangeComp, gbc_zRangeComp);

        tRangeLabel = new JLabel("T range  ");
        tRangeLabel.setToolTipText("T interval to load");
        GridBagConstraints gbc_tRangeLabel = new GridBagConstraints();
        gbc_tRangeLabel.anchor = GridBagConstraints.WEST;
        gbc_tRangeLabel.insets = new Insets(0, 0, 5, 5);
        gbc_tRangeLabel.gridx = 0;
        gbc_tRangeLabel.gridy = 5;
        optionsPanel.add(tRangeLabel, gbc_tRangeLabel);

        tRangeComp = new RangeComponent();
        tRangeComp.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                rangeChanged();
            }
        });
        tRangeComp.setToolTipText("T interval to load");
        tRangeComp.setMinimumSize(new Dimension(130, 22));
        tRangeComp.setMaximumSize(new Dimension(180, 22));
        tRangeComp.getLowSpinner().setPreferredSize(new Dimension(50, 20));
        tRangeComp.getHighSpinner().setPreferredSize(new Dimension(50, 20));
        tRangeComp.getHighSpinner().setMaximumSize(new Dimension(50, 20));
        tRangeComp.getLowSpinner().setMaximumSize(new Dimension(50, 20));
        tRangeComp.setPreferredSize(new Dimension(180, 22));
        tRangeComp.getSlider().setPreferredSize(new Dimension(100, 22));
        tRangeComp.setSliderVisible(true);
        GridBagConstraints gbc_tRangeComp = new GridBagConstraints();
        gbc_tRangeComp.gridwidth = 3;
        gbc_tRangeComp.insets = new Insets(0, 0, 5, 0);
        gbc_tRangeComp.fill = GridBagConstraints.BOTH;
        gbc_tRangeComp.gridx = 1;
        gbc_tRangeComp.gridy = 5;
        optionsPanel.add(tRangeComp, gbc_tRangeComp);

        channelLabel = new JLabel("Channel  ");
        channelLabel.setToolTipText("Channel to load");
        GridBagConstraints gbc_channelLabel = new GridBagConstraints();
        gbc_channelLabel.anchor = GridBagConstraints.WEST;
        gbc_channelLabel.insets = new Insets(0, 0, 0, 5);
        gbc_channelLabel.gridx = 0;
        gbc_channelLabel.gridy = 6;
        optionsPanel.add(channelLabel, gbc_channelLabel);

        channelSpinner = new SpecialValueSpinner(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL"));
        channelSpinner.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                rangeChanged();
            }
        });
        channelSpinner.setToolTipText("Channel to load");
        channelSpinner.setPreferredSize(new Dimension(50, 22));
        channelSpinner.setMinimumSize(new Dimension(50, 22));
        channelSpinner.setMaximumSize(new Dimension(50, 22));
        GridBagConstraints gbc_channelSpinner = new GridBagConstraints();
        gbc_channelSpinner.gridwidth = 2;
        gbc_channelSpinner.fill = GridBagConstraints.VERTICAL;
        gbc_channelSpinner.anchor = GridBagConstraints.EAST;
        gbc_channelSpinner.gridx = 2;
        gbc_channelSpinner.gridy = 6;
        optionsPanel.add(channelSpinner, gbc_channelSpinner);
    }

    protected void rangeChanged()
    {
        if (updatingPanel)
            return;

        int v;
        int z = -1;
        int t = -1;
        int c = -1;

        // detect change on z, t, c position in preview
        v = getZMin();
        if (pZMin != v)
        {
            z = v;
            pZMin = v;
        }
        v = getZMax();
        if (pZMax != v)
        {
            z = v;
            pZMax = v;
        }

        v = getTMin();
        if (pTMin != v)
        {
            t = v;
            pTMin = v;
        }
        v = getTMax();
        if (pTMax != v)
        {
            t = v;
            pTMax = v;
        }

        v = getChannel();
        if (pCh != v)
        {
            c = v;
            pCh = v;
        }

        // changed ? --> update preview...
        if ((z != -1) || (t != -1) || (c != -1))
            updatePreview(z, t, c);
    }

    void updateAutoOrderEnableState()
    {
        autoOrderCheck.setEnabled(autoOrderEnable && !isSeparateSequenceSelected());
    }

    void updateXYRegion()
    {
        if (metadata != null)
        {
            final Rectangle2D r = xyRegionComp.getRegion();
            final int sizeX = MetaDataUtil.getSizeX(metadata, 0);
            final int sizeY = MetaDataUtil.getSizeY(metadata, 0);

            xyRegionLoadingCheck.setEnabled(true);
            // xyRegionLoadingCheck.setSelected(false);
            xyRegionComp.setEnabled(xyRegionLoadingCheck.isSelected());
            // re-init region if out of bounds
            if ((r.getMaxX() > sizeX) || (r.getMaxY() > sizeY))
                xyRegionComp.setRegion(0, 0, sizeX, sizeY);
        }
        else
        {
            xyRegionComp.setEnabled(false);
            xyRegionLoadingCheck.setEnabled(false);
        }
    }

    void updateZRange()
    {
        if (metadata != null)
        {
            final int sizeZ = Math.max(MetaDataUtil.getSizeZ(metadata, 0), 1);

            zRangeComp.setMinMaxStep(0d, sizeZ - 1d, 1d);
            zRangeComp.setLowHigh(0d, sizeZ - 1d);
            zRangeComp.setEnabled(sizeZ > 1);
        }
        else
        {
            zRangeComp.setMinMaxStep(0d, 0d, 1d);
            zRangeComp.setEnabled(false);
        }
    }

    void updateTRange()
    {
        if (metadata != null)
        {
            final int sizeT = Math.max(MetaDataUtil.getSizeT(metadata, 0), 1);

            tRangeComp.setMinMaxStep(0d, sizeT - 1d, 1d);
            tRangeComp.setLowHigh(0d, sizeT - 1d);
            tRangeComp.setEnabled(sizeT > 1);
        }
        else
        {
            tRangeComp.setMinMaxStep(0d, 0d, 1d);
            tRangeComp.setEnabled(false);
        }
    }

    void updateChannelRange()
    {
        if (metadata != null)
        {
            final int sizeC = Math.max(MetaDataUtil.getSizeC(metadata, 0), 1);

            channelSpinner.setModel(new SpecialValueSpinnerModel(-1, -1, sizeC - 1, 1, -1, "ALL"));
            channelSpinner.setEnabled(sizeC > 1);
        }
        else
        {
            channelSpinner.setModel(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL"));
            channelSpinner.setEnabled(false);
        }
    }

    void updateResolutionSlider()
    {
        // min sub resolution to open (full resolution by default)
        int minRes = 0;
        // max sub resolution to open
        int maxRes = 0;

        if (metadata != null)
        {
            long sizeXY;

            // // size of XY plane
            // sizeXY = (long) MetaDataUtil.getSizeX(metadata, 0) * (long) MetaDataUtil.getSizeY(metadata, 0);
            //
            // // we can't handle that plane size
            // while (sizeXY > Integer.MAX_VALUE)
            // {
            // // reduce resolution until XY plane size is acceptable
            // minRes++;
            // sizeXY /= 4;
            // }

            // size of XY plane
            sizeXY = (long) MetaDataUtil.getSizeX(metadata, 0) * (long) MetaDataUtil.getSizeY(metadata, 0);

            // no need to get lower than 128x128
            while (sizeXY > 16384)
            {
                // increase max sub resolution until XY plane is too low
                maxRes++;
                sizeXY /= 4;
            }
        }

        // apply
        resolutionSlider.setMinimum(minRes);
        resolutionSlider.setMaximum(maxRes);
        resolutionSlider.setValue(minRes);

        // no need to enable it
        resolutionSlider.setEnabled(maxRes > 0);

        updateFinaleResolution();
    }

    void updateFinaleResolution()
    {
        if (metadata != null)
        {
            final int res = resolutionSlider.getValue();
            final int baseX = MetaDataUtil.getSizeX(metadata, 0);
            final int baseY = MetaDataUtil.getSizeY(metadata, 0);
            final double diviser = Math.pow(2d, res);

            resolutionLevelLabel.setText(res + " (" + Integer.toString((int) (baseX / diviser)) + " x "
                    + Integer.toString((int) (baseY / diviser)) + ")");
        }
        else
            resolutionLevelLabel.setText("");
    }

    void updatePanel()
    {
        updatingPanel = true;
        try
        {
            updateResolutionSlider();
            updateXYRegion();
            updateTRange();
            updateZRange();
            updateChannelRange();
        }
        finally
        {
            updatingPanel = false;
        }
    }

    void setAutoOrderEnabled(boolean value)
    {
        autoOrderEnable = value;
        updateAutoOrderEnableState();
    }

    public boolean isSeparateSequenceSelected()
    {
        return separateSeqCheck.isVisible() && separateSeqCheck.isSelected();
    }

    public boolean isAutoOrderSelected()
    {
        return autoOrderCheck.isVisible() && autoOrderCheck.isSelected();
    }

    public int getResolutionLevel()
    {
        if (resolutionSlider.isVisible())
            return resolutionSlider.getValue();

        return 0;
    }

    public Rectangle getXYRegion()
    {
        if (xyRegionComp.isVisible() && xyRegionComp.isEnabled())
            return (Rectangle) xyRegionComp.getRegion();

        return null;
    }

    public void setXYRegion(Rectangle region)
    {
        // enable region loading
        xyRegionLoadingCheck.setSelected(true);
        xyRegionComp.setEnabled(true);
        xyRegionComp.setRegion(region);
    }

    public int getZMin()
    {
        if (zRangeComp.isVisible())
            return (int) zRangeComp.getLow();

        return 0;
    }

    public int getZMax()
    {
        if (zRangeComp.isVisible())
            return (int) zRangeComp.getHigh();

        return 0;
    }

    public int getTMin()
    {
        if (tRangeComp.isVisible())
            return (int) tRangeComp.getLow();

        return 0;
    }

    public int getTMax()
    {
        if (tRangeComp.isVisible())
            return (int) tRangeComp.getHigh();

        return 0;
    }

    public int getChannel()
    {
        if (channelSpinner.isVisible())
            return ((Integer) channelSpinner.getValue()).intValue();

        // all by default
        return -1;
    }

    /**
     * Asynchronous image preview refresh only ({@link #updatePreview(String)} should have be called once before to give
     * the fileId)
     */
    public void updatePreview(int z, int t, int c)
    {
        // interrupt previous preview refresh
        cancelPreview();

        // only if we had a previous load
        if (previewThread != null)
        {
            previewThread = new PreviewUpdater(previewThread.fileId, z, t, c);
            previewThread.start();
        }
    }

    /**
     * Asynchronous preview refresh
     */
    public void updatePreview(String fileId)
    {
        // interrupt previous preview refresh
        cancelPreview();

        metadata = null;
        metadataFieldsOk = false;

        previewThread = new PreviewUpdater(fileId);
        previewThread.start();
    }

    /**
     * Cancel preview refresh
     */
    public void cancelPreview()
    {
        // brutal interruption of previous execution
        if (previewThread != null)
            previewThread.interrupt();
    }

    public boolean isMultiFile()
    {
        // we can use visible state for this one
        return separateSeqCheck.isVisible();
    }

    /**
     * Enable multi file mode for option panel
     */
    public void setMultiFile(boolean value)
    {
        // no change
        if (multiFile == value)
            return;

        multiFile = value;

        final GridBagLayout layout = (GridBagLayout) optionsPanel.getLayout();
        final int margin = value ? 5 : 0;

        // layout modification
        layout.getConstraints(loadInSeparatedLabel).insets.bottom = margin;
        layout.getConstraints(separateSeqCheck).insets.bottom = margin;
        layout.getConstraints(autoOrderLabel).insets.bottom = margin;
        layout.getConstraints(autoOrderCheck).insets.bottom = margin;

        layout.getConstraints(resolutionFixLabel).insets.bottom = 5 - margin;
        layout.getConstraints(resolutionLevelLabel).insets.bottom = 5 - margin;
        layout.getConstraints(resolutionSlider).insets.bottom = 5 - margin;
        layout.getConstraints(xyRegionLabel).insets.bottom = 5 - margin;
        layout.getConstraints(xyRegionComp).insets.bottom = 5 - margin;
        layout.getConstraints(xyRegionLoadingCheck).insets.bottom = 5 - margin;
        layout.getConstraints(zRangeLabel).insets.bottom = 5 - margin;
        layout.getConstraints(zRangeComp).insets.bottom = 5 - margin;
        layout.getConstraints(tRangeLabel).insets.bottom = 5 - margin;
        layout.getConstraints(tRangeComp).insets.bottom = 5 - margin;
        layout.getConstraints(channelLabel).insets.bottom = 5 - margin;
        layout.getConstraints(channelSpinner).insets.bottom = 5 - margin;

        loadInSeparatedLabel.setVisible(value);
        separateSeqCheck.setVisible(value);
        autoOrderLabel.setVisible(value);
        autoOrderCheck.setVisible(value);

        resolutionFixLabel.setVisible(!value);
        resolutionLevelLabel.setVisible(!value);
        resolutionSlider.setVisible(!value);
        xyRegionLabel.setVisible(!value);
        xyRegionComp.setVisible(!value);
        xyRegionLoadingCheck.setVisible(!value);
        zRangeLabel.setVisible(!value);
        zRangeComp.setVisible(!value);
        tRangeLabel.setVisible(!value);
        tRangeComp.setVisible(!value);
        channelLabel.setVisible(!value);
        channelSpinner.setVisible(!value);

        // by default we enable auto ordering with multi file
        setAutoOrderEnabled(value);

        // multiple file --> no preview
        if (value)
        {
            cancelPreview();
            metadata = null;
            metadataFieldsOk = true;
            if (previewThread != null)
            {
                try
                {
                    previewThread.join();
                }
                catch (InterruptedException e)
                {
                    // ignore
                }
            }
            
            preview.setImage(ResourceUtil.ICON_PICTURE);
            preview.setInfos("Multiple selection...");
        }
    }

    /**
     * We are closing the Open Dialog.
     * Ensure metadata are correctly loaded (it's important that this method is called from EDT)
     */
    public void closingFromEDT()
    {
        if (previewThread == null)
            return;

        // interrupt process from thread
        previewThread.interrupt();

        // need update metadata fields
        if (!metadataFieldsOk)
        {
            // get fileId
            final String fileId = previewThread.fileId;
            final List<SequenceFileImporter> importers = Loader.getSequenceFileImporters(fileId);

            for (SequenceFileImporter importer : importers)
            {
                try
                {
                    if (importer.open(fileId, 0))
                    {
                        try
                        {
                            metadata = importer.getMetaData();

                            // update panel (we are on EDT)
                            updatePanel();

                            // done
                            return;
                        }
                        finally
                        {
                            importer.close();
                        }
                    }
                }
                catch (UnsupportedFormatException e)
                {
                    // try next importer...
                }
                catch (RuntimeException e)
                {
                    // try next importer...
                }
                catch (IOException e)
                {
                    // try next importer...
                }

                // next importer
            }
        }
    }

}