package com.srbenoit.microscopy;

import java.awt.image.BufferedImage;
import com.srbenoit.filter.AbstractFilter;
import com.srbenoit.filter.FilterException;
import com.srbenoit.filter.FilterInput;
import com.srbenoit.filter.FilterOutput;
import com.srbenoit.filter.FilterTreeExecutor;
import com.srbenoit.filter.Pipe;
import com.srbenoit.filter.items.ImageArrayPipeItem;

/**
 * A filter that merges all z planes in an image array, producing a new image array that has 1
 * plane per time point.
 */
public class ZPlaneMergerFilter extends AbstractFilter {

    /** version number for serialization */
    private static final long serialVersionUID = 4347190635894369722L;

    /**
     * Constructs a new <code>ZPlaneMergerFilter</code>.
     */
    public ZPlaneMergerFilter() {

        super("Z-plane merger", ZPlaneMergerFilter.class.getName());

        this.inputs.add(new FilterInput(ImageArrayPipeItem.class, "Images to be merged"));
        this.outputs.add(new FilterOutput(ImageArrayPipeItem.class, "Merged images",
                "merged_images"));
        makeRenderer();
    }

    /**
     * Duplicates the filter including all of its settings, but returns an independent object.
     *
     * @return  the duplicated object
     */
    @Override public AbstractFilter duplicate() {

        return new ZPlaneMergerFilter();
    }

    /**
     * Performs the filter operation.
     *
     * @param   executor  the <code>FilterTreeExecutor</code> that is executing the filter
     * @param   pipe      a pipe containing the input data items
     * @throws  FilterException  if the filter cannot complete
     */
    @Override public void filter(final FilterTreeExecutor executor, final Pipe pipe)
        throws FilterException {

        ImageArrayPipeItem input;
        ImageArrayPipeItem output;

        validateInputs(pipe);
        executor.indicateProgress(1);

        input = (ImageArrayPipeItem) pipe.get(this.inputs.get(0).getKey());
        executor.indicateProgress(2);

        output = new ImageArrayPipeItem(this.outputs.get(0).getKey(),
                "Z-plane merged images (PNG format)", pipe, input.getXLabel(), input.getYLabel(),
                input.getXSize(), 1, "png");
        pipe.add(output);
        executor.indicateProgress(3);

        runFilter(executor, input, output);

        executor.indicateProgress(80);

        if (!executor.isCancelled()) {
            pipe.save(executor);
        }

        executor.indicateProgress(100);
    }

    /**
     * Runs the filter, reading the source Metamorph TIF files and extracting an array of images,
     * the first dimension of which is time, and the second dimension of which is z plane.
     *
     * @param  executor  the <code>FilterTreeExecutor</code> that is executing the filter
     * @param  input     the images to process
     * @param  output    the image array in which to store the merged images
     */
    private void runFilter(final FilterTreeExecutor executor, final ImageArrayPipeItem input,
        final ImageArrayPipeItem output) {

        int width;
        BufferedImage[] src;
        BufferedImage merged;

        width = input.getXSize();

        for (int x = 0; x < width; x++) {

            if (executor.isCancelled()) {
                break;
            }

            executor.indicateProgress(5 + (75 * x / width));
            src = input.getImages(x);
            merged = mergeImages(src);
            output.setImage(x, 0, merged);
        }
    }

    /**
     * Merge the images in all planes into a composite merged image. The maximum intensity value
     * from the set of planes is accepted as the merged image intensity value.
     *
     * @param   source  the source images to be merged
     * @return  the merged image
     */
    public static BufferedImage mergeImages(final BufferedImage[] source) {

        BufferedImage result;
        int width;
        int height;
        int effWidth;
        int effHeight;
        int rgb;
        int red;
        int green;
        int blue;

        width = source[0].getWidth();
        height = source[0].getHeight();

        result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        // Determine the effective width and height of the merged image
        effWidth = width;
        effHeight = height;

        for (int i = 0; i < source.length; i++) {

            if (source[i].getWidth() < effWidth) {
                effWidth = source[i].getWidth();
            }

            if (source[i].getHeight() < effHeight) {
                effHeight = source[i].getHeight();
            }
        }

        // Perform the merge operation
        for (int x = 0; x < effWidth; x++) {

            for (int y = 0; y < effHeight; y++) {
                red = 0;
                green = 0;
                blue = 0;

                for (int i = 0; i < source.length; i++) {
                    rgb = source[i].getRGB(x, y);

                    if ((rgb & 0x000000FF) > red) {
                        red = rgb & 0x000000FF;
                    }

                    if ((rgb & 0x0000FF00) > green) {
                        green = rgb & 0x0000FF00;
                    }

                    if ((rgb & 0x00FF0000) > blue) {
                        blue = rgb & 0x00FF0000;
                    }
                }

                rgb = red | green | blue;
                result.setRGB(x, y, rgb);
            }
        }

        return result;
    }

    /**
     * Generates the string representation of the filter.
     *
     * @return  the string representation
     */
    @Override public String toString() {

        return "ZPlaneMergerFilter";
    }
}
