/*
 * Copyright 2010, 2011 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 plugins.tutorial.roi;

import icy.canvas.IcyCanvas;
import icy.canvas.IcyCanvas2D;
import icy.gui.dialog.MessageDialog;
import icy.gui.frame.progress.AnnounceFrame;
import icy.gui.viewer.Viewer;
import icy.gui.viewer.ViewerEvent;
import icy.gui.viewer.ViewerEvent.ViewerEventType;
import icy.gui.viewer.ViewerListener;
import icy.image.IcyBufferedImage;
import icy.painter.Overlay;
import icy.plugin.abstract_.PluginActionable;
import icy.roi.BooleanMask2D;
import icy.roi.ROI2D;
import icy.sequence.Sequence;
import icy.sequence.SequenceEvent;
import icy.sequence.SequenceEvent.SequenceEventSourceType;
import icy.sequence.SequenceListener;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.Arrays;

/**
 * This class show how we can use ROI to do localized operation on image
 * 
 * @author Stephane
 */
public class ProcessingFromROI extends PluginActionable implements SequenceListener, ViewerListener
{
    private Viewer viewer;
    private Sequence sequence;
    private Overlay overlay;
    BufferedImage img;
    private int[] imgData;

    @Override
    public void run()
    {
        viewer = getActiveViewer();

        // no viewer has been found ?
        if (viewer == null)
        {
            // display an information message as we need an opened sequence
            MessageDialog.showDialog("This example needs a sequence to start. Please load an image file.",
                    MessageDialog.INFORMATION_MESSAGE);
            return;
        }

        // we should avoid direct sequence reference but it really help here
        sequence = viewer.getSequence();

        // display an announcement with Plugin description
        new AnnounceFrame("This example show how do localized operation on image from ROI");
        // no ROI2D in sequence
        if (!sequence.hasROI(ROI2D.class))
            new AnnounceFrame("Add a ROI to the sequence to see plugin action");

        // define an overlay to just draw the image over the sequence
        overlay = new Overlay("Mask")
        {
            @Override
            public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
            {
                // check if we are dealing with a canvas 2D and we have a valid Graphics object
                if ((canvas instanceof IcyCanvas2D) && (g != null))
                {
                    // just draw the image over the sequence
                    g.drawImage(img, null, 0, 0);
                }
            }
        };

        // add the overlay to the sequence
        sequence.addOverlay(overlay);

        // build an ARGB image with same dimension than sequence
        img = new BufferedImage(sequence.getSizeX(), sequence.getSizeY(), BufferedImage.TYPE_INT_ARGB);
        // get internal image data reference
        imgData = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();

        // listen viewer changes so we know when current displayed image change
        viewer.addListener(this);
        // listen sequence changes so we know when sequence rois are modified
        sequence.addListener(this);

        refresh();
    }

    // we call this method when we want to terminate plugin execution
    private void close()
    {
        // remove viewer listener
        viewer.removeListener(this);
        // remove sequence listener
        sequence.removeListener(this);
        // remove the overlay from the sequence (same as painter.detachFromAll())
        sequence.removeOverlay(overlay);

        // free sequence reference
        viewer = null;
        sequence = null;
    }

    private void refresh()
    {
        // clear image
        Arrays.fill(imgData, 0);

        final IcyBufferedImage currentImage = getActiveImage();

        if (currentImage != null)
        {
            // get image bounds (rectangle defining height and width of image)
            final Rectangle imageBounds = currentImage.getBounds();

            BooleanMask2D globalMask = null;

            // compute global boolean mask of all ROI2D contained in the sequence
            for (ROI2D roi : sequence.getROI2Ds())
            {
                // get intersection between image and roi bounds
                final Rectangle intersect = roi.getBounds().intersection(imageBounds);
                // get the boolean mask of roi (optimized from intersection bounds)
                final boolean[] mask = roi.getBooleanMask(intersect, false);

                // update global mask
                if (globalMask == null)
                    globalMask = new BooleanMask2D(intersect, mask);
                else
                    globalMask.getUnion(intersect, mask);
            }

            // process only if global mask is not empty
            if ((globalMask != null) && (!globalMask.bounds.isEmpty()))
            {
                final Rectangle bounds = globalMask.bounds;
                final boolean[] mask = globalMask.mask;

                // calculate offset
                int offMsk = 0;
                int offImg = (bounds.y * imageBounds.width) + bounds.x;

                // do process only on data contained in ROI
                for (int y = 0; y < bounds.height; y++)
                {
                    // override all contained pixels with half transparent green
                    for (int x = 0; x < bounds.width; x++)
                        if (mask[offMsk + x])
                            imgData[offImg + x] = 0x8000FF00;

                    offMsk += bounds.width;
                    offImg += imageBounds.width;
                }
            }
        }

        // notify that our overlay has changed (image data modified)
        // this automatically refresh the display
        overlay.painterChanged();
    }

    // called when sequence has changed
    @Override
    public void sequenceChanged(SequenceEvent sequenceEvent)
    {
        // sequence roi(s) changed --> refresh
        if (sequenceEvent.getSourceType() == SequenceEventSourceType.SEQUENCE_ROI)
            refresh();
    }

    // called when sequence is closed (last viewer containing sequence has changed
    @Override
    public void sequenceClosed(Sequence sequence)
    {
    }

    @Override
    public void viewerChanged(ViewerEvent event)
    {
        // we want to know about navigation change (current displayed image change)
        if (event.getType() == ViewerEventType.POSITION_CHANGED)
            // refresh
            refresh();
    }

    @Override
    public void viewerClosed(Viewer viewer)
    {
        // end plugin execution
        close();
    }
}