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.image.ColorModel;



/** Implements the "Stack to HyperStack", "RGB to HyperStack" 
	and "HyperStack to Stack" commands. */
public class HyperStackConverter implements PlugIn {
	static final int C=0, Z=1, T=2;
	static final int CZT=0, CTZ=1, ZCT=2, ZTC=3, TCZ=4, TZC=5;
    static final String[] orders = {"xyczt(default)", "xyctz", "xyzct", "xyztc", "xytcz", "xytzc"};
    static int order = CZT;
    static boolean splitRGB = true;

	public void run(String arg) {
		if (arg.equals("new"))
		{newHyperStack(); return;}
		ImagePlus imp = IJ.getImage();
    	if (arg.equals("stacktohs"))
    		convertStackToHS(imp);
    	else if (arg.equals("hstostack"))
    		convertHSToStack(imp);
	}
	
	/** Displays the current stack in a HyperStack window. Based on the 
		Stack_to_Image5D class in Joachim Walter's Image5D plugin. */
	void convertStackToHS(ImagePlus imp) {
        int nChannels = imp.getNChannels();
        int nSlices = imp.getNSlices();
        int nFrames = imp.getNFrames();
		int stackSize = imp.getImageStackSize();
		if (stackSize==1) {
			IJ.error("Stack to HyperStack", "Stack required");
			return;
		}
		boolean rgb = imp.getBitDepth()==24;
		String[] modes = {"Composite", "Color", "Grayscale"};
		GenericDialog gd = new GenericDialog("Convert to HyperStack");
		gd.addChoice("Order:", orders, orders[order]);
		gd.addNumericField("Channels (c):", nChannels, 0);
		gd.addNumericField("Slices (z):", nSlices, 0);
		gd.addNumericField("Frames (t):", nFrames, 0);
		gd.addChoice("Display Mode:", modes, modes[1]);
		if (rgb) {
			gd.setInsets(15, 0, 0);
			gd.addCheckbox("Convert RGB to 3 Channel Hyperstack", splitRGB);
		}
		gd.showDialog();
		if (gd.wasCanceled()) return;
		order = gd.getNextChoiceIndex();
		nChannels = (int) gd.getNextNumber();
		nSlices = (int) gd.getNextNumber();
		nFrames = (int) gd.getNextNumber();
		int mode = gd.getNextChoiceIndex();
		if (rgb)
			splitRGB = gd.getNextBoolean();
		if (rgb && splitRGB==true) {
			new CompositeConverter().run(mode==0?"composite":"color");
			return;
		}
		if (rgb && nChannels>1) {
			IJ.error("HyperStack Converter", "RGB stacks are limited to one channel");
			return;
		}
		if (nChannels*nSlices*nFrames!=stackSize) {
			IJ.error("HyperStack Converter", "channels x slices x frames <> stack size");
			return;
		}
		imp.setDimensions(nChannels, nSlices, nFrames);
		if (order!=CZT && imp.getStack().isVirtual())
			IJ.error("HyperStack Converter", "Virtual stacks must by in XYCZT order.");
		else {
			shuffle(imp, order);
			ImagePlus imp2 = imp;
			if (nChannels>1 && imp.getBitDepth()!=24) {
				LUT[] luts = imp.getLuts();
				if (luts!=null && luts.length<nChannels) luts = null;
				imp2 = new CompositeImage(imp, mode+1);
				if (luts!=null)
					((CompositeImage)imp2).setLuts(luts);
			} else if (imp.getClass().getName().indexOf("Image5D")!=-1) {
				imp2 = imp.createImagePlus();
				imp2.setStack(imp.getTitle(), imp.getImageStack());
				imp2.setDimensions(imp.getNChannels(), imp.getNSlices(), imp.getNFrames());
				imp2.getProcessor().resetMinAndMax();
			}
			imp2.setOpenAsHyperStack(true);
			new StackWindow(imp2);
			if (imp!=imp2) {
				imp2.setOverlay(imp.getOverlay());
				imp.hide();
				WindowManager.setCurrentWindow(imp2.getWindow());
			}
		}
	}

	/** Changes the dimension order of a 4D or 5D stack from 
		the specified order (CTZ, ZCT, ZTC, TCZ or TZC) to 
		the XYCZT order used by ImageJ. */
	public void shuffle(ImagePlus imp, int order) {
        int nChannels = imp.getNChannels();
        int nSlices = imp.getNSlices();
        int nFrames = imp.getNFrames();
		int first=C, middle=Z, last=T;
		int nFirst=nChannels, nMiddle=nSlices, nLast=nFrames;
		switch (order) {
			case CTZ: first=C; middle=T; last=Z;
				nFirst=nChannels; nMiddle=nFrames; nLast=nSlices;
				break;
			case ZCT: first=Z; middle=C; last=T;
				nFirst=nSlices; nMiddle=nChannels; nLast=nFrames;
				break;
			case ZTC: first=Z; middle=T; last=C;
				nFirst=nSlices; nMiddle=nFrames; nLast=nChannels;
				break;
			case TCZ: first=T; middle=C; last=Z;
				nFirst=nFrames; nMiddle=nChannels; nLast=nSlices;
				break;
			case TZC: first=T; middle=Z; last=C;
				nFirst=nFrames; nMiddle=nSlices; nLast=nChannels;
				break;
		}
		if (order!=CZT) {
			ImageStack stack = imp.getImageStack();
			Object[] images1 = stack.getImageArray();
			Object[] images2 = new Object[images1.length];
			System.arraycopy(images1, 0, images2, 0, images1.length);
			String[] labels1 = stack.getSliceLabels();
			String[] labels2 = new String[labels1.length];
			System.arraycopy(labels1, 0, labels2, 0, labels1.length);
			int[] index = new int[3];
			for (index[2]=0; index[2]<nFrames; ++index[2]) {
				for (index[1]=0; index[1]<nSlices; ++index[1]) {
					for (index[0]=0; index[0]<nChannels; ++index[0]) {
						int dstIndex = index[0] + index[1]*nChannels + index[2]*nChannels*nSlices;
						int srcIndex = index[first] + index[middle]*nFirst + index[last]*nFirst*nMiddle;
						images1[dstIndex] = images2[srcIndex];
						labels1[dstIndex] = labels2[srcIndex];
					}
				}
			}
		}
	}

	void convertHSToStack(ImagePlus imp) {
		if (!imp.isHyperStack()) return;
		ImagePlus imp2 = imp;
		if (imp.isComposite()) {
			ImageStack stack = imp.getStack();
			imp2 = imp.createImagePlus();
			imp2.setStack(imp.getTitle(), stack);
			int[] dim = imp.getDimensions();
			imp2.setDimensions(dim[2], dim[3], dim[4]);
			ImageProcessor ip2 = imp2.getProcessor();
			ip2.setColorModel(ip2.getDefaultColorModel());
		}
		imp2.setOpenAsHyperStack(false);
		new StackWindow(imp2);
		if (imp!=imp2) {
			imp2.setOverlay(imp.getOverlay());
			imp.hide();
		}
	}
	
	void newHyperStack() {
		IJ.runMacroFile("ij.jar:HyperStackMaker", "");
	}
	
}

