package ij.plugin;
import ij.*;
import ij.gui.GenericDialog;
import ij.process.ImageProcessor;
import ij.process.LUT;
import java.util.ArrayList;
import java.util.List;
import java.awt.Color;

/**
 * This plugin is used by the Image/Stacks/Tools/Make Substack
 * command to create substacks of hyperstacks.
 *  
 * @author Curtis Rueden
 */
public class SubHyperstackMaker implements PlugIn {

	public void run(final String arg) {
		// verify input image is appropriate
		final ImagePlus input = WindowManager.getCurrentImage();
		if (input == null) {
			IJ.showMessage("No image open.");
			return;
		}
		if (input.getStackSize() == 1) {
			IJ.showMessage("Image is not a stack.");
			return;
		}
		final int cCount = input.getNChannels();
		final int zCount = input.getNSlices();
		final int tCount = input.getNFrames();
		final boolean hasC = cCount > 1;
		final boolean hasZ = zCount > 1;
		final boolean hasT = tCount > 1;

		// prompt for C, Z and T ranges
		final GenericDialog gd = new GenericDialog("Subhyperstack Maker");
		gd.addMessage("Enter a range (e.g. 2-14), a range with increment\n"
			+ "(e.g. 1-100-2) or a list (e.g. 7,9,25,27)", null, Color.darkGray);
		if (hasC) gd.addStringField("Channels:", "1-" + cCount, 40);
		if (hasZ) gd.addStringField("Slices:", "1-" + zCount, 40);
		if (hasT) gd.addStringField("Frames:", "1-" + tCount, 40);
		gd.showDialog();
		if (gd.wasCanceled()) return;
		final String cString = hasC ? gd.getNextString() : "1";
		final String zString = hasZ ? gd.getNextString() : "1";
		final String tString = hasT ? gd.getNextString() : "1";

		// compute subhyperstack
		final ImagePlus output =
			makeSubhyperstack(input, cString, zString, tString);

		// display result
		output.show();
	}

	public static ImagePlus makeSubhyperstack(final ImagePlus input,
		final String cString, final String zString, final String tString)
	{
		final ArrayList<Integer> cList = parseList(cString, input.getNChannels());
		final ArrayList<Integer> zList = parseList(zString, input.getNSlices());
		final ArrayList<Integer> tList = parseList(tString, input.getNFrames());
		return makeSubhyperstack(input, cList, zList, tList);
	}

	public static ImagePlus makeSubhyperstack(final ImagePlus input,
		final List<Integer> cList, final List<Integer> zList,
		final List<Integer> tList)
	{
		// validate inputs
		if (cList.size() == 0) {
			throw new IllegalArgumentException("Must specify at least one channel");
		}
		if (zList.size() == 0) {
			throw new IllegalArgumentException("Must specify at least one slice");
		}
		if (tList.size() == 0) {
			throw new IllegalArgumentException("Must specify at least one frame");
		}

		final ImageStack inputStack = input.getImageStack();

		final int cCount = input.getNChannels();
		final int zCount = input.getNSlices();
		final int tCount = input.getNFrames();

		for (final int c : cList)
			check("C", c, cCount);
		for (final int z : zList)
			check("Z", z, zCount);
		for (final int t : tList)
			check("T", t, tCount);

		// create output image
		final String title = WindowManager.getUniqueName(input.getTitle());
		ImagePlus output =
			input.createHyperStack(title, cList.size(), zList.size(), tList.size(),
				input.getBitDepth());
		final ImageStack outputStack = output.getImageStack();

		// add specified planes to subhyperstack
		int oc = 0, oz, ot;
		for (final int c : cList) {
			oc++;
			oz = 0;
			for (final int z : zList) {
				oz++;
				ot = 0;
				for (final int t : tList) {
					ot++;
					final int i = input.getStackIndex(c, z, t);
					final int oi = output.getStackIndex(oc, oz, ot);
					final String label = inputStack.getSliceLabel(i);
					final ImageProcessor ip = inputStack.getProcessor(i);
					outputStack.setSliceLabel(label, oi);
					outputStack.setPixels(ip.getPixels(), oi);
				}
			}
		}

		// propagate composite image settings, if appropriate
		if (input instanceof CompositeImage) {
			final CompositeImage compositeInput = (CompositeImage) input;
			final CompositeImage compositeOutput =
				new CompositeImage(output, compositeInput.getMode());
			oc = 0;
			for (final int c : cList) {
				oc++;
				final LUT table = compositeInput.getChannelLut(c);
				compositeOutput.setChannelLut(table, oc);
				compositeOutput.setPositionWithoutUpdate(oc, 1, 1);
				compositeInput.setPositionWithoutUpdate(c, 1, 1);
				final double min = compositeInput.getDisplayRangeMin();
				final double max = compositeInput.getDisplayRangeMax();
				compositeOutput.setDisplayRange(min, max);
			}
			output = compositeOutput;
		}

		// return result
		return output;
	}

	private static void
		check(final String name, final int index, final int count)
	{
		if (index < 1 || index > count) {
			throw new IllegalArgumentException("Invalid " + name + " index: " +
				index);
		}
	}

	private static ArrayList<Integer> parseList(final String planeString,
		int count)
	{
		final ArrayList<Integer> list = new ArrayList<Integer>();
		for (final String token : planeString.split("\\s*,\\s*")) {
			final int dash1 = token.indexOf("-");
			final int dash2 = token.lastIndexOf("-");
			if (dash1 < 0) {
				// single number
				final int index;
				try {
					index = Integer.parseInt(token);
				}
				catch (final NumberFormatException exc) {
					throw new IllegalArgumentException("Invalid number: " + token);
				}
				if (index < 1 || index > count) {
					throw new IllegalArgumentException("Invalid number: " + token);
				}
				list.add(Integer.parseInt(token));
			}
			else {
				// range, with or without increment
				final int min, max, step;
				try {
					min = Integer.parseInt(token.substring(0, dash1));
					if (dash1 == dash2) {
						// range (e.g. 2-14)
						max = Integer.parseInt(token.substring(dash1 + 1));
						step = 1;
					}
					else {
						// range with increment (e.g. 1-100-2)
						max = Integer.parseInt(token.substring(dash1 + 1, dash2));
						step = Integer.parseInt(token.substring(dash2 + 1));
					}
				}
				catch (final NumberFormatException exc) {
					throw new IllegalArgumentException("Invalid range: " + token);
				}
				if (min < 1 || min > max || max > count || step < 1) {
					throw new IllegalArgumentException("Invalid range: " + token);
				}
				for (int index = min; index <= max; index += step) {
					list.add(index);
				}
			}
		}
		return list;
	}

}
