/**
 * 
 */
package icy.roi;

import icy.type.Position5DIterator;
import icy.type.point.Point5D;
import icy.type.rectangle.Rectangle5D;

import java.util.NoSuchElementException;

/**
 * ROI iterator.<br>
 * This class permit to use simple iterator to navigate each point of specified ROI in XYCZT
 * <i>([T[Z[C[Y[X]]]]])</i> dimension order.<br>
 * <b>If the ROI is modified during iteration the iterator becomes invalid and exception can
 * happen.</b>
 * 
 * @author Stephane
 */
public class ROIIterator implements Position5DIterator
{
    protected final ROI roi;
    protected final Rectangle5D.Integer bounds;
    protected final boolean inclusive;
    protected int c, z, t;
    protected boolean done;
    protected BooleanMask2DIterator maskIterator;

    /**
     * Create a new ROI iterator to iterate through each point of the specified ROI.
     * 
     * @param roi
     *        ROI defining the region to iterate.
     * @param region
     *        A 5D region to limit the ROI region area to iterate for.<br/>
     *        Keep it to <code>null</code> to iterate all over the ROI.
     * @param inclusive
     *        If true then all partially contained (intersected) pixels in the ROI are included.
     */
    public ROIIterator(ROI roi, Rectangle5D region, boolean inclusive)
    {
        super();

        this.roi = roi;
        // get final bounds
        if (region != null)
            bounds = region.createIntersection(roi.getBounds5D()).toInteger();
        else
            bounds = roi.getBounds5D().toInteger();
        this.inclusive = inclusive;

        // fix infinite dimensions
        if (bounds.isInfiniteZ())
        {
            bounds.z = -1;
            bounds.sizeZ = 1;
        }
        if (bounds.isInfiniteT())
        {
            bounds.t = -1;
            bounds.sizeT = 1;
        }
        if (bounds.isInfiniteC())
        {
            bounds.c = -1;
            bounds.sizeC = 1;
        }

        // start iterator
        reset();
    }

    /**
     * Create a new ROI iterator to iterate through each point of the specified ROI.
     * 
     * @param roi
     *        ROI defining the region to iterate.
     * @param inclusive
     *        If true then all partially contained (intersected) pixels in the ROI are included.
     */
    public ROIIterator(ROI roi, boolean inclusive)
    {
        this(roi, null, inclusive);
    }

    public int getMinZ()
    {
        return bounds.z;
    }

    public int getMaxZ()
    {
        return (bounds.z + bounds.sizeZ) - 1;
    }

    public int getMinT()
    {
        return bounds.t;
    }

    public int getMaxT()
    {
        return (bounds.t + bounds.sizeT) - 1;
    }

    public int getMinC()
    {
        return bounds.c;
    }

    public int getMaxC()
    {
        return (bounds.c + bounds.sizeC) - 1;
    }

    @Override
    public void reset()
    {
        done = bounds.isEmpty();

        if (!done)
        {
            z = getMinZ();
            t = getMinT();
            c = getMinC();

            prepareXY();
            nextXYIfNeeded();
        }
    }

    /**
     * Prepare for XY iteration.
     */
    protected void prepareXY()
    {
        maskIterator = new BooleanMask2DIterator(roi.getBooleanMask2D(z, t, c, inclusive));
    }

    @Override
    public void next()
    {
        if (done)
            throw new NoSuchElementException();

        maskIterator.next();
        nextXYIfNeeded();
    }

    /**
     * Advance one image position.
     */
    protected void nextXYIfNeeded()
    {
        while (maskIterator.done() && !done)
        {
            if (++c > getMaxC())
            {
                c = getMinC();

                if (++z > getMaxZ())
                {
                    z = getMinZ();

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

            prepareXY();
        }
    }

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

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

        return new Point5D.Integer(maskIterator.getX(), maskIterator.getY(), z, t, c);
    }

    @Override
    public int getX()
    {
        if (done)
            throw new NoSuchElementException();

        return maskIterator.getX();
    }

    @Override
    public int getY()
    {
        if (done)
            throw new NoSuchElementException();

        return maskIterator.getY();
    }

    @Override
    public int getC()
    {
        if (done)
            throw new NoSuchElementException();

        return c;
    }

    @Override
    public int getZ()
    {
        if (done)
            throw new NoSuchElementException();

        return z;
    }

    @Override
    public int getT()
    {
        if (done)
            throw new NoSuchElementException();

        return t;
    }

}