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

import icy.image.IcyBufferedImage;
import icy.image.ImageDataIterator;
import icy.roi.ROI;
import icy.type.DataIterator;
import icy.type.DataType;
import icy.type.rectangle.Rectangle5D;
import icy.type.rectangle.Rectangle5D.Integer;

import java.awt.Rectangle;
import java.util.NoSuchElementException;

/**
 * Sequence data iterator.<br>
 * This class permit to use simple iterator to read / write <code>Sequence</code> data<br>
 * as double in XYCZT <i>([T[Z[C[Y[X}}]]])</i> dimension order.<br>
 * Whatever is the internal {@link DataType} data is returned and set as double.<br>
 * <b>If the sequence size or type is modified during iteration the iterator
 * becomes invalid and can exception can happen.</b>
 * 
 * @author Stephane
 */
public class SequenceDataIterator implements DataIterator
{
    protected final Sequence sequence;
    protected final ROI roi;

    protected final Rectangle XYBounds;
    protected final int startC, endC;
    protected final int startZ, endZ;
    protected final int startT, endT;
    protected final boolean inclusive;

    /**
     * internals
     */
    protected int c, z, t;
    protected boolean done;
    protected ImageDataIterator imageIterator;

    /**
     * Create a new SequenceData iterator to iterate data through the specified 5D region
     * (inclusive).
     * 
     * @param sequence
     *        Sequence we want to iterate data from
     * @param bounds5D
     *        the 5D rectangular region we want to iterate
     */
    public SequenceDataIterator(Sequence sequence, Rectangle5D.Integer bounds5D)
    {
        super();

        this.sequence = sequence;
        roi = null;
        imageIterator = null;
        inclusive = true;

        if (sequence != null)
        {
            final Rectangle5D.Integer bounds = (Integer) bounds5D.createIntersection(sequence.getBounds5D());

            XYBounds = (Rectangle) bounds.toRectangle2D();

            startZ = bounds.z;
            endZ = (bounds.z + bounds.sizeZ) - 1;
            startT = bounds.t;
            endT = (bounds.t + bounds.sizeT) - 1;
            startC = bounds.c;
            endC = (bounds.c + bounds.sizeC) - 1;
        }
        else
        {
            XYBounds = null;
            startZ = 0;
            endZ = 0;
            startT = 0;
            endT = 0;
            startC = 0;
            endC = 0;
        }

        // start iterator
        reset();
    }

    /**
     * Create a new SequenceData iterator to iterate data through the specified dimensions
     * (inclusive).
     * 
     * @param sequence
     *        Sequence we want to iterate data from
     * @param XYBounds
     *        XY region to iterate
     * @param z
     *        Z position (stack) we want to iterate data
     * @param t
     *        T position (time) we want to iterate data
     * @param c
     *        C position (channel) we want to iterate data
     */
    public SequenceDataIterator(Sequence sequence, Rectangle XYBounds, int z, int t, int c)
    {
        this(sequence, new Rectangle5D.Integer(XYBounds.x, XYBounds.y, z, t, c, (XYBounds.x + XYBounds.width) - 1,
                (XYBounds.y + XYBounds.height) - 1, 1, 1, 1));
    }

    /**
     * @deprecated Use {@link #SequenceDataIterator(Sequence, Rectangle5D.Integer)} instead
     */
    @Deprecated
    public SequenceDataIterator(Sequence sequence, int startX, int endX, int startY, int endY, int startC, int endC,
            int startZ, int endZ, int startT, int endT)
    {
        this(sequence, new Rectangle5D.Integer(startX, startY, startZ, startT, startC, (endX - startX) + 1,
                (endY - startY) + 1, (endZ - startZ) + 1, (endT - startT) + 1, (endC - startC) + 1));
    }

    /**
     * @deprecated Use {@link #SequenceDataIterator(Sequence, Rectangle, int, int, int)} instead
     */
    @Deprecated
    public SequenceDataIterator(Sequence sequence, int startX, int endX, int startY, int endY, int c, int z, int t)
    {
        this(sequence, new Rectangle5D.Integer(startX, startY, z, t, c, (endX - startX) + 1, (endY - startY) + 1, 1, 1,
                1));
    }

    /**
     * Create a new SequenceData iterator to iterate data of specified channel.
     * 
     * @param sequence
     *        Sequence we want to iterate data from
     * @param z
     *        Z position (stack) we want to iterate data
     * @param t
     *        T position (time) we want to iterate data
     * @param c
     *        C position (channel) we want to iterate data
     */
    public SequenceDataIterator(Sequence sequence, int z, int t, int c)
    {
        this(sequence, new Rectangle5D.Integer(0, 0, z, t, c, sequence.getSizeX(), sequence.getSizeY(), 1, 1, 1));
    }

    /**
     * Create a new SequenceData iterator to iterate all data.
     * 
     * @param sequence
     *        Sequence we want to iterate data from.
     */
    public SequenceDataIterator(Sequence sequence)
    {
        this(sequence, new Rectangle5D.Integer(0, 0, 0, 0, 0, sequence.getSizeX(), sequence.getSizeY(),
                sequence.getSizeZ(), sequence.getSizeT(), sequence.getSizeC()));
    }

    /**
     * Create a new SequenceData iterator to iterate data through the specified ROI.
     * 
     * @param sequence
     *        Sequence we want to iterate data from.
     * @param roi
     *        ROI defining the region to iterate.
     * @param inclusive
     *        If true then all partially contained (intersected) pixels in the ROI are included.
     * @param z
     *        The specific Z position (slice) we want to iterate or <code>-1</code> to iterate over
     *        the whole ROI Z dimension.
     * @param t
     *        The specific T position (frame) we want to iterate or <code>-1</code> to iterate over
     *        the whole ROI T dimension.
     * @param c
     *        The specific C position (channel) we want to iterate or <code>-1</code> to iterate
     *        over the whole ROI C dimension.
     */
    public SequenceDataIterator(Sequence sequence, ROI roi, boolean inclusive, int z, int t, int c)
    {
        super();

        this.sequence = sequence;
        this.roi = roi;
        this.inclusive = inclusive;
        XYBounds = null;

        if ((sequence != null) && (roi != null))
        {
            final Rectangle5D bounds5D = roi.getBounds5D();

            // force Z position
            if (z != -1)
            {
                bounds5D.setZ(z);
                bounds5D.setSizeZ(1d);
            }
            // force T position
            if (t != -1)
            {
                bounds5D.setT(t);
                bounds5D.setSizeT(1d);
            }
            // force C position
            if (c != -1)
            {
                bounds5D.setC(c);
                bounds5D.setSizeC(1d);
            }

            // get final bounds
            final Rectangle5D.Integer bounds = (Integer) sequence.getBounds5D().createIntersection(bounds5D);

            startZ = bounds.z;
            endZ = (bounds.z + bounds.sizeZ) - 1;
            startT = bounds.t;
            endT = (bounds.t + bounds.sizeT) - 1;
            startC = bounds.c;
            endC = (bounds.c + bounds.sizeC) - 1;
        }
        else
        {
            startZ = 0;
            endZ = 0;
            startT = 0;
            endT = 0;
            startC = 0;
            endC = 0;
        }

        // start iterator
        reset();
    }

    /**
     * Create a new SequenceData iterator to iterate data through the specified ROI.
     * 
     * @param sequence
     *        Sequence we want to iterate data from.
     * @param roi
     *        ROI defining the region to iterate.
     * @param inclusive
     *        If true then all partially contained (intersected) pixels in the ROI are included.
     */
    public SequenceDataIterator(Sequence sequence, ROI roi, boolean inclusive)
    {
        this(sequence, roi, inclusive, -1, -1, -1);
    }

    /**
     * Create a new SequenceData iterator to iterate data through the specified ROI.
     * 
     * @param sequence
     *        Sequence we want to iterate data from.
     * @param roi
     *        ROI defining the region to iterate.
     */
    public SequenceDataIterator(Sequence sequence, ROI roi)
    {
        this(sequence, roi, false);
    }

    @Override
    public void reset()
    {
        done = (sequence == null) || (startT > endT) || (startZ > endZ) || (startC > endC);

        if (!done)
        {
            t = startT;
            z = startZ;
            c = startC;

            // prepare XY data
            prepareDataXY();
            nextImageifNeeded();
        }
    }

    /**
     * Prepare data for XY iteration.
     */
    protected void prepareDataXY()
    {
        final IcyBufferedImage img = sequence.getImage(t, z);

        // get the 2D mask for specified C
        if (roi != null)
        {
            switch (roi.getDimension())
            {
                case 2:
                    // ignore Z, T and C roi informations (wanted for fixed Z, T and C positions)
                    imageIterator = new ImageDataIterator(img, roi.getBooleanMask2D(-1, -1, -1, inclusive), c);
                    break;

                case 3:
                    // ignore T and C roi informations (wanted for fixed T and C positions)
                    imageIterator = new ImageDataIterator(img, roi.getBooleanMask2D(z, -1, -1, inclusive), c);
                    break;

                case 4:
                    // ignore C roi information (wanted for fixed C position)
                    imageIterator = new ImageDataIterator(img, roi.getBooleanMask2D(z, t, -1, inclusive), c);
                    break;

                // assume 5D
                default:
                    imageIterator = new ImageDataIterator(img, roi.getBooleanMask2D(z, t, c, inclusive), c);
            }
        }
        else
            imageIterator = new ImageDataIterator(img, XYBounds, c);
    }

    @Override
    public void next()
    {
        imageIterator.next();
        nextImageifNeeded();
    }

    /**
     * Advance one image position.
     */
    protected void nextImageifNeeded()
    {
        while (imageIterator.done() && !done)
        {
            if (++c > endC)
            {
                c = startC;

                if (++z > endZ)
                {
                    z = startZ;

                    if (++t > endT)
                    {
                        done = true;
                        return;
                    }
                }
            }

            prepareDataXY();
        }
    }

    @Override
    public boolean done()
    {
        return done;
    }

    @Override
    public double get()
    {
        if (done)
            throw new NoSuchElementException(null);

        return imageIterator.get();
    }

    @Override
    public void set(double value)
    {
        if (done)
            throw new NoSuchElementException(null);

        imageIterator.set(value);
    }

    /**
     * Return current X position.
     */
    public int getPositionX()
    {
        if (imageIterator != null)
            return imageIterator.getPositionX();

        return 0;
    }

    /**
     * Return current Y position.
     */
    public int getPositionY()
    {
        if (imageIterator != null)
            return imageIterator.getPositionY();

        return 0;
    }

    /**
     * Return current C position.
     */
    public int getPositionC()
    {
        return c;
    }

    /**
     * Return current Z position.
     */
    public int getPositionZ()
    {
        return z;
    }

    /**
     * Return current T position.
     */
    public int getPositionT()
    {
        return t;
    }

}