package ij.plugin;
import java.awt.*;
import java.io.*;
import java.text.DecimalFormat;	
import java.util.*;
import ij.*;
import ij.io.*;
import ij.gui.*;
import ij.measure.Calibration;
import ij.process.*;
import ij.plugin.frame.Recorder;
import ij.macro.Interpreter;
import ij.util.Tools;

/** This plugin, which saves the images in a stack as separate files, 
	implements the File/Save As/Image Sequence command. */
public class StackWriter implements PlugIn {
	private static final String DIR_KEY = "save.sequence.dir";
	private static String[] choices = {"BMP",  "FITS", "GIF", "JPEG", "PGM", "PNG", "Raw", "Text", "TIFF",  "ZIP"};
	private static String staticFileType = "TIFF";
	private String fileType = "TIFF";
	private int ndigits = 4;
	private boolean useLabels;
	private boolean firstTime = true;
	private int startAt;
	private boolean hyperstack;
	private int[] dim;
	private ImagePlus imp;
	private String directory;
	private String format = "tiff";
	private String name;
	
	/** Saves the specified image as a sequence of images. */
	public static void save(ImagePlus imp, String directoryPath, String options) {
		StackWriter sw = new StackWriter();
		sw.imp = imp;
		sw.format = Tools.getStringFromList(options, "format=", sw.format);
		sw.name = Tools.getStringFromList(options, "name=");
		sw.ndigits = (int)Tools.getNumberFromList(options, "digits=", sw.ndigits);
		sw.useLabels = options.contains(" use");
		sw.run(directoryPath);
	}


	public void run(String arg) {
		if (imp==null)
			imp = WindowManager.getCurrentImage();
		if (imp==null || (imp!=null && imp.getStackSize()<2&&!IJ.isMacro())) {
			IJ.error("Stack Writer", "This command requires a stack.");
			return;
		}
		int stackSize = imp.getStackSize();
		if (name==null) {
			name = imp.getTitle();
			int dotIndex = name.lastIndexOf(".");
			if (dotIndex>=0)
				name = name.substring(0, dotIndex);
		}
		hyperstack = imp.isHyperStack();
		LUT[] luts = null;
		int lutIndex = 0;
		int nChannels = imp.getNChannels();
		if (hyperstack) {
			dim = imp.getDimensions();
			if (imp.isComposite())
				luts = ((CompositeImage)imp).getLuts();
			if (firstTime && ndigits==4) {
				ndigits = 3;
				firstTime = false;
			}
		}
		if (arg!=null && arg.length()>0)
			directory = arg;
		else {		
			if (!showDialog(imp))
				return;
		}		
		File d = new File(directory);
		if (d==null || !d.isDirectory()) {
			IJ.error("File>Save As>Image Sequence", "Directory not found: "+directory);
			return;
		}
		int number = 0;
		if (ndigits<1) ndigits = 1;
		if (ndigits>8) ndigits = 8;
		int maxImages = (int)Math.pow(10,ndigits);
		if (stackSize>maxImages && !useLabels && !hyperstack) {
			IJ.error("Stack Writer", "More than " + ndigits
				+" digits are required to generate \nunique file names for "+stackSize+" images.");
			return;			
		}
		if (format.equals("fits") && !FileSaver.okForFits(imp))
			return;			
		if (format.equals("text"))
			format = "text image";
		String extension = "." + format;
		if (format.equals("tiff"))
			extension = ".tif";
		else if (format.equals("text image"))
			extension = ".txt";					
		Overlay overlay = imp.getOverlay();
		boolean isOverlay = overlay!=null && !imp.getHideOverlay();
		if (!(format.equals("jpeg")||format.equals("png")))
			isOverlay = false;
		ImageStack stack = imp.getStack();
		ImagePlus imp2 = new ImagePlus();
		imp2.setTitle(imp.getTitle());
		Calibration cal = imp.getCalibration();
		int nSlices = stack.size();
		String path,label=null;
		imp.lock();
		for (int i=1; i<=nSlices; i++) {
			IJ.showStatus("writing: "+i+"/"+nSlices);
			IJ.showProgress(i, nSlices);
			ImageProcessor ip = stack.getProcessor(i);
			if (isOverlay) {
				imp.setSliceWithoutUpdate(i);
				ip = imp.flatten().getProcessor();
			} else if (luts!=null && nChannels>1 && hyperstack) {
				ip.setColorModel(luts[lutIndex++]);
				if (lutIndex>=luts.length) lutIndex = 0;
			}
			imp2.setProcessor(null, ip);
			String label2 = stack.getSliceLabel(i);
			imp2.setProp("Slice_Label", null);
			if (label2!=null) {
				if (label2.contains("\n"))
					imp2.setProperty("Info", label2);
				else
					imp2.setProp("Slice_Label", label2);;
			} else {
				Properties props = imp2.getProperties();
				if (props!=null) props.remove("Info");
			}
			imp2.setCalibration(cal);
			String digits = getDigits(number++);
			if (useLabels) {
				label = stack.getShortSliceLabel(i, 111);
				if (label!=null && label.equals("")) label = null;
				if (label!=null) label = label.replaceAll("/","-");
			}
			if (label==null)
				path = directory+name+digits+extension;
			else {
				if (label.toLowerCase().endsWith(".tiff") && ".tif".equals(extension))
					path = directory+label;
				else
					path = directory+label+extension;
			}
			if (i==1) {
				File f = new File(path);
				if (f.exists()) {
					if (!IJ.isMacro() && !IJ.showMessageWithCancel("Overwrite files?",
						"One or more files will be overwritten if you click \"OK\".\n \n"+path)) {
						imp.unlock();
						IJ.showStatus("");
						IJ.showProgress(1.0);
						return;
					}
				}
			}
			if (IJ.recording())
				Recorder.disablePathRecording();
			imp2.setOverlay(null);
			if (overlay!=null && format.equals("tiff")) {
				Overlay overlay2 = overlay.duplicate();
				overlay2.crop(i, i);
				if (overlay2.size()>0) {
					for (int j=0; j<overlay2.size(); j++) {
						Roi roi = overlay2.get(j);
						int pos = roi.getPosition();
						if (pos==1)
							roi.setPosition(i);
					}
					imp2.setOverlay(overlay2);
				}
			}
			IJ.saveAs(imp2, format, path);
		}
		imp.unlock();
		if (isOverlay) imp.setSlice(1);
		IJ.showStatus("");
	}
	
	private boolean showDialog(ImagePlus imp) {
		String options = Macro.getOptions();
		if (options!=null && options.contains("save="))  //macro
			Macro.setOptions(options.replaceAll("save=", "dir="));
		directory = Prefs.get(DIR_KEY, IJ.getDir("downloads")+"stack2/");
		GenericDialog gd = new GenericDialog("Save Image Sequence");
		if (!IJ.isMacro())
			fileType = staticFileType;
		gd.setInsets(5, 0, 0);
		gd.addDirectoryField("Dir:", directory);		
		gd.setInsets(2, 110, 5);
		gd.addMessage("drag and drop target", IJ.font10, Color.darkGray);
		gd.addChoice("Format:", choices, fileType);
		gd.addStringField("Name:", name, 12);
		if (!hyperstack)
			gd.addNumericField("Start At:", startAt, 0);
		gd.addNumericField("Digits (1-8):", ndigits, 0);
		if (!hyperstack)
			gd.addCheckbox("Use slice labels as file names", useLabels);
		gd.showDialog();
		if (gd.wasCanceled())
			return false;
		directory = gd.getNextString();
		directory = IJ.addSeparator(directory);
		Prefs.set(DIR_KEY, directory);
		gd.setSmartRecording(true);
		fileType = gd.getNextChoice();
		format = fileType.toLowerCase(Locale.US);
		if (!IJ.isMacro())
			staticFileType = fileType;
		String name2 = gd.getNextString();
		boolean nameChanged =  !name2.equals(name);
		name = name2;
		if (!hyperstack)
			startAt = (int)gd.getNextNumber();
		if (startAt<0) startAt = 0;
		int ndigits2 = (int)gd.getNextNumber();
		boolean ndigitsChanged = ndigits2!=ndigits;
		ndigits = ndigits2;
		if (!hyperstack)
			useLabels = gd.getNextBoolean();
		else
			useLabels = false;
		if (IJ.recording()) {
			String options2 = "format="+format;
			if (nameChanged)
				options2 += " name="+name;
			if (ndigitsChanged)
				options2 += " digits="+ndigits;
			if (useLabels)
				options2 += " use";			
			String dir = Recorder.fixPath(directory);
   			Recorder.recordCall("StackWriter.save(imp, \""+dir+"\", \""+options2+"\");");
		}
		return true;
	}
	
	String getDigits(int n) {
		if (hyperstack) {
			int c = (n%dim[2])+1;
			int z = ((n/dim[2])%dim[3])+1;
			int t = ((n/(dim[2]*dim[3]))%dim[4])+1;
			String cs="", zs="", ts="";
			if (dim[2]>1) {
				cs = "00000000"+c;
				cs = "_c"+cs.substring(cs.length()-ndigits);
			}
			if (dim[3]>1) {
				zs = "00000000"+z;
				zs = "_z"+zs.substring(zs.length()-ndigits);
			}
			if (dim[4]>1) {
				ts = "00000000"+t;
				ts = "_t"+ts.substring(ts.length()-ndigits);
			}
			return ts+zs+cs;
		} else {
			String digits = "00000000"+(startAt+n);
			return digits.substring(digits.length()-ndigits);
		}
	}
	
}
