package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.Calibration;
import ij.macro.Interpreter;
import ij.io.FileInfo;
import java.awt.*;
import java.util.ArrayList;

/** Implements the AddSlice, DeleteSlice and "Stack to Images" commands. */
public class StackEditor implements PlugIn {
	ImagePlus imp;
	int nSlices, width, height;

	public void run(String arg) {
		imp = IJ.getImage();
    	nSlices = imp.getStackSize();
    	width = imp.getWidth();
    	height = imp.getHeight();
    	
    	if (arg.equals("add"))
    		addSlice();
    	else if (arg.equals("delete"))
    		deleteSlice();
    	else if (arg.equals("toimages"))
    		convertStackToImages(imp);
	}

	void addSlice() {
		if (imp.isHyperStack() || imp.isComposite()) {
			addHyperstackChannelSliceOrFrame();
			return;
		}
 		if (!imp.lock()) return;
		int id = 0;
		ImageStack stack = imp.getStack();
		if (stack.size()==1)
			id = imp.getID();
		ImageProcessor ip = imp.getProcessor();
		int n = imp.getCurrentSlice();
		if (IJ.altKeyDown()) n--; // insert in front of current slice
		stack.addSlice(null, ip.createProcessor(width, height), n);
		imp.setStack(null, stack);
		imp.setSlice(n+1);
		imp.unlock();
		imp.changes = true;
		if (id!=0) IJ.selectWindow(id); // prevents macros from failing
	}
	
	void deleteSlice() {
		if (nSlices<2)
			{IJ.error("\"Delete Slice\" requires a stack"); return;}
		if (imp.isHyperStack() || (imp.isComposite() && nSlices==imp.getNChannels())) {
			deleteHyperstackChannelSliceOrFrame();
			return;
		}
		if (!imp.lock()) return;
		ImageStack stack = imp.getStack();
		int n = imp.getCurrentSlice();
 		stack.deleteSlice(n);
 		if (stack.size()==1) {
			String label = stack.getSliceLabel(1);
 			if (label!=null) imp.setProp("Slice_Label", label);
 		}
		imp.setStack(null, stack);
 		if (n--<1) n = 1;
		imp.setSlice(n);
		imp.unlock();
		imp.changes = true;
	}

	void addHyperstackChannelSliceOrFrame() {
		int channels = imp.getNChannels();
		int slices = imp.getNSlices();
		int frames = imp.getNFrames();
		int c1 = imp.getChannel();
		int z1 = imp.getSlice();
		int t1 = imp.getFrame();
		String[] choices = {"channel", "slice", "frame"};
		String choice = choices[0];
		if (frames>1 && slices==1)
			choice = "frame";
		else if (slices>1)
			choice = "slice";
		GenericDialog gd = new GenericDialog("Add");
		gd.addChoice("Add", choices, choice);
		gd.addCheckbox("Prepend", false);
		gd.showDialog();
		if (gd.wasCanceled())
			return;
		choice = gd.getNextChoice();
		boolean prepend = gd.getNextBoolean();
		if (!imp.lock())
			return;
		ImageStack stack = imp.getStack();
		LUT[] luts = null;
		if (choice.equals("frame")) { // add time point
			int index = imp.getStackIndex(channels, slices, t1);
			if (prepend)
				index = 0;
			for (int i=0; i<channels*slices; i++) {
				ImageProcessor ip = stack.getProcessor(1).duplicate();
				ip.setColor(0); ip.fill();
				stack.addSlice(null, ip, index);
			}
			frames++;
		} else if (choice.equals("slice")) { // add slice to all volumes
			for (int t=frames; t>=1; t--) {
				int index = imp.getStackIndex(channels, z1, t);
				if (prepend)
					index = (t-1)*channels*slices;
				for (int i=0; i<channels; i++) {
					ImageProcessor ip = stack.getProcessor(1).duplicate();
					ip.setColor(0); ip.fill();
					stack.addSlice(null, ip, index);
				}
			}
			slices++;
		} else if (choice.equals("channel")) { // add channel
			if (imp.isComposite())
				luts = ((CompositeImage)imp).getLuts();
			int index = imp.getStackIndex(c1, slices, frames);
			int minIndex = 1;
			if (prepend) {
				index = channels*slices*frames - channels;
				minIndex = 0;
				c1 = 0;
			}
			while (index>=minIndex) {
				ImageProcessor ip = stack.getProcessor(1).duplicate();
				ip.setColor(0); ip.fill();
				stack.addSlice(null, ip, index);
				index -= channels;
			}
			channels++;
		}
		imp.setStack(stack, channels, slices, frames);
		if (luts!=null) {
			LUT[] luts2 = new LUT[luts.length+1];
			int index = 0;
			for (int i=0; i<luts2.length; i++) {
				if (i==c1)
					luts2[i] = LUT.createLutFromColor(Color.white);
				else 
					luts2[i] = luts[index++];
			}
			CompositeImage cimp = (CompositeImage)imp;
			for (int c=1; c<=channels; c++)
				cimp.setChannelLut(luts2[c-1], c);
			imp.updateAndDraw();
		}
		imp.unlock();
		imp.repaintWindow();
		if (prepend) {
			imp.setPosition(channels, slices, frames);
			imp.setPosition(1, 1, 1);
		}
		imp.changes = true;
	}
	
	void deleteHyperstackChannelSliceOrFrame() {
		int channels = imp.getNChannels();
		int slices = imp.getNSlices();
		int frames = imp.getNFrames();
		int c1 = imp.getChannel();
		int z1 = imp.getSlice();
		int t1 = imp.getFrame();
		ArrayList list = new ArrayList();
		if (channels>1) list.add("channel");
		if (slices>1) list.add("slice");
		if (frames>1) list.add("frame");
		String[] choices = new String[list.size()];
		list.toArray(choices);
		String choice = choices[0];
		if (frames>1 && slices==1)
			choice = "frame";
		else if (slices>1)
			choice = "slice";
    	String options = Macro.getOptions();
		if (IJ.isMacro() && options!=null && !options.contains("delete=")) {
			if (options.contains("delete"))
    			Macro.setOptions("delete=frame");
    		else
    			Macro.setOptions("delete=slice");
    	}
		if (IJ.isMacro() && options==null && (imp.isComposite() && imp.getStackSize()==imp.getNChannels()))
			Macro.setOptions("delete=channel");
		GenericDialog gd = new GenericDialog("Delete");
		gd.addChoice("Delete current", choices, choice);
		gd.showDialog();
		if (gd.wasCanceled()) return;
		choice = gd.getNextChoice();
		if (!imp.lock()) return;
		ImageStack stack = imp.getStack();
		LUT[] luts = null;
		if (choice.equals("frame")) { // delete time point
			for (int z=slices; z>=1; z--) {
				int index = imp.getStackIndex(channels, z, t1);
				for (int i=0; i<channels; i++)
					stack.deleteSlice(index-i);
			}
			frames--;
		} else if (choice.equals("slice")) { // delete slice z1 from all volumes
			for (int t=frames; t>=1; t--) {
				int index = imp.getStackIndex(channels, z1, t);
				for (int i=0; i<channels; i++)
					stack.deleteSlice(index-i);
			}
			slices--;
		} else if (choice.equals("channel")) { // delete channe c1
			if (imp.isComposite())
				luts = ((CompositeImage)imp).getLuts();
			int index = imp.getStackIndex(c1, slices, frames);
			while (index>0) {
				stack.deleteSlice(index);
				index -= channels;
			}
			channels--;
		}
		//imp.setDimensions(channels, slices, frames);
		imp.setStack(stack, channels, slices, frames);
		if (luts!=null) {
			for (int i=c1-1; i<luts.length-1; i++)
				luts[i] = luts[i+1];
			CompositeImage cimp = (CompositeImage)imp;
			for (int c=1; c<=channels; c++)
				cimp.setChannelLut(luts[c-1], c);
			imp.updateAndDraw();
		}
		imp.unlock();
		imp.repaintWindow();
		imp.changes = true;
	}

	public void convertImagesToStack() {
		(new ImagesToStack()).run("");
	}

	public void convertStackToImages(ImagePlus imp) {
		if (nSlices<2) {
			IJ.wait(500);
			imp = IJ.getImage();
			nSlices = imp.getStackSize();
		}
		if (nSlices<2) {
			IJ.error("\"Convert Stack to Images\" requires a stack\n"+imp);
			return;
		}
		if (!imp.lock())
			return;
		ImageStack stack = imp.getStack();
		int size = stack.size();
		if (size>30 && !IJ.isMacro()) {
			boolean ok = IJ.showMessageWithCancel("Convert to Images?",
			"Are you sure you want to convert this\nstack to "
			+size+" separate windows?");
			if (!ok) {
				imp.unlock();
				return;
			}
		}
		Calibration cal = imp.getCalibration();
		CompositeImage cimg = imp.isComposite()?(CompositeImage)imp:null;
		if (imp.getNChannels()!=imp.getStackSize()) cimg = null;
		Overlay overlay = imp.getOverlay();
		int lastImageID = 0;
		for (int i=1; i<=size; i++) {
			String label = stack.getShortSliceLabel(i);
			if (label!=null && (label.contains("/") || label.contains("\\") || label.contains(":")))
				label = null;
			String title = label!=null&&!label.equals("")?label:getTitle(imp, i);
			ImageProcessor ip = stack.getProcessor(i);
			if (cimg!=null) {
				LUT lut = cimg.getChannelLut(i);
				if (lut!=null) {
					ip.setColorModel(lut);
					ip.setMinAndMax(lut.min, lut.max);
				}
			}
			ImagePlus imp2 = new ImagePlus(title, ip);
			imp2.setCalibration(cal);
			String info = stack.getSliceLabel(i);
			if (info!=null && !info.equals(label))
				imp2.setProperty("Info", info);
			imp2.setIJMenuBar(i==size);
			if (overlay!=null) {
				Overlay overlay2 = new Overlay();
				for (int j=0; j<overlay.size(); j++) {
					Roi roi = overlay.get(j);
					if (roi.getPosition()==i) {
						roi.setPosition(0);
						overlay2.add((Roi)roi.clone());
					}
				}
				if (overlay2.size()>0)
					imp2.setOverlay(overlay2);
			}
			if (i==size)
				lastImageID = imp2.getID();
			imp2.show();
		}
		imp.changes = false;
		ImageWindow win = imp.getWindow();
		if (win!=null)
			win.close();
		else if (Interpreter.isBatchMode())
			Interpreter.removeBatchModeImage(imp);
		imp.unlock();
	}

	String getTitle(ImagePlus imp, int n) {
		String digits = "00000000"+n;
		return getShortTitle(imp)+"-"+digits.substring(digits.length()-4,digits.length());
	}
	
	/** Returns a shortened version of image name that does not 
		include spaces or a file name extension. */
	private String getShortTitle(ImagePlus imp) {
		String title = imp.getTitle();
		int index = title.indexOf(' ');
		if (index>-1)
			title = title.substring(0, index);
		index = title.lastIndexOf('.');
		if (index>0)
			title = title.substring(0, index);
		return title;
    }
	
}

