package ij.plugin;
import java.awt.*;
import java.io.*;
import java.awt.event.*;
import java.awt.image.ColorModel;
import ij.*;
import ij.io.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.Calibration;

/** Implements the File/Import/Image Sequence command, which
opens a folder of images as a stack. */
public class FolderOpener implements PlugIn {

	private static boolean convertToGrayscale, convertToRGB;
	private static boolean sortFileNames = true;
	private static double scale = 100.0;
	private static boolean virtualStack;
	private int n, start, increment;
	private String filter;
	private boolean isRegex;
	private FileInfo fi;
	private String info1;

	public void run(String arg) {
		OpenDialog od = new OpenDialog("Open Image Sequence...", arg);
		String directory = od.getDirectory();
		String name = od.getFileName();
		if (name==null)
			return;
		String[] list = (new File(directory)).list();
		if (list==null)
			return;
		String title = directory;
		if (title.endsWith(File.separator))
			title = title.substring(0, title.length()-1);
		int index = title.lastIndexOf(File.separatorChar);
		if (index!=-1) title = title.substring(index + 1);
		if (title.endsWith(":"))
			title = title.substring(0, title.length()-1);
		
		IJ.register(FolderOpener.class);
		list = trimFileList(list);
		if (list==null) return;
		if (IJ.debugMode) IJ.log("FolderOpener: "+directory+" ("+list.length+" files)");
		int width=0,height=0,depth=0,bitDepth=0;
		ImageStack stack = null;
		double min = Double.MAX_VALUE;
		double max = -Double.MAX_VALUE;
		Calibration cal = null;
		boolean allSameCalibration = true;
		IJ.resetEscape();		
		try {
			for (int i=0; i<list.length; i++) {
				IJ.redirectErrorMessages();
				ImagePlus imp = (new Opener()).openImage(directory, list[i]);
				if (imp!=null) {
					width = imp.getWidth();
					height = imp.getHeight();
					bitDepth = imp.getBitDepth();
					fi = imp.getOriginalFileInfo();
					if (!showDialog(imp, list))
						return;
					break;
				}
			}
			if (width==0) {
				IJ.error("Import Sequence", "This folder does not appear to contain any TIFF,\n"
				+ "JPEG, BMP, DICOM, GIF, FITS or PGM files.");
				return;
			}

			if (filter!=null && (filter.equals("") || filter.equals("*")))
				filter = null;
			if (filter!=null) {
				int filteredImages = 0;
  				for (int i=0; i<list.length; i++) {
					if (isRegex&&list[i].matches(filter))
						filteredImages++;
					else if (list[i].indexOf(filter)>=0)
						filteredImages++;
 					else
 						list[i] = null;
 				}
  				if (filteredImages==0) {
  					if (isRegex)
  						IJ.error("Import Sequence", "None of the file names match the regular expression.");
  					else
   						IJ.error("Import Sequence", "None of the "+list.length+" files contain\n the string '"+filter+"' in their name.");
 					return;
  				}
  				String[] list2 = new String[filteredImages];
  				int j = 0;
  				for (int i=0; i<list.length; i++) {
 					if (list[i]!=null)
 						list2[j++] = list[i];
 				}
  				list = list2;
  			}
			if (sortFileNames)
				list = sortFileList(list);

			if (n<1)
				n = list.length;
			if (start<1 || start>list.length)
				start = 1;
			if (start+n-1>list.length)
				n = list.length-start+1;
			int count = 0;
			int counter = 0;
			ImagePlus imp = null;
			for (int i=start-1; i<list.length; i++) {
				if ((counter++%increment)!=0)
					continue;
				Opener opener = new Opener();
				opener.setSilentMode(true);
				IJ.redirectErrorMessages();
				if (!virtualStack||stack==null)
					imp = opener.openImage(directory, list[i]);
				if (imp!=null && stack==null) {
					width = imp.getWidth();
					height = imp.getHeight();
					depth = imp.getStackSize();
					bitDepth = imp.getBitDepth();
					cal = imp.getCalibration();
					if (convertToRGB) bitDepth = 24;
					if (convertToGrayscale) bitDepth = 8;
					ColorModel cm = imp.getProcessor().getColorModel();
					if (virtualStack)
						stack = new VirtualStack(width, height, cm, directory);
					else if (scale<100.0)						
						stack = new ImageStack((int)(width*scale/100.0), (int)(height*scale/100.0), cm);
					else
						stack = new ImageStack(width, height, cm);
					info1 = (String)imp.getProperty("Info");
				}
				if (imp==null)
					continue;
				if (imp.getWidth()!=width || imp.getHeight()!=height) {
					IJ.log(list[i] + ": wrong size; "+width+"x"+height+" expected, "+imp.getWidth()+"x"+imp.getHeight()+" found");
					continue;
				}
				String label = imp.getTitle();
				if (depth==1) {
					String info = (String)imp.getProperty("Info");
					if (info!=null)
						label += "\n" + info;
				}
				if (imp.getCalibration().pixelWidth!=cal.pixelWidth)
					allSameCalibration = false;
				ImageStack inputStack = imp.getStack();
				for (int slice=1; slice<=inputStack.getSize(); slice++) {
					ImageProcessor ip = inputStack.getProcessor(slice);
					int bitDepth2 = imp.getBitDepth();
					if (convertToRGB) {
						ip = ip.convertToRGB();
						bitDepth2 = 24;
					} else if(convertToGrayscale) {
						ip = ip.convertToByte(true);
						bitDepth2 = 8;
					}
					if (bitDepth2!=bitDepth) {
						if (bitDepth==8) {
							ip = ip.convertToByte(true);
							bitDepth2 = 8;
						} else if (bitDepth==24) {
							ip = ip.convertToRGB();
							bitDepth2 = 24;
						}
					}
					if (bitDepth2!=bitDepth) {
						IJ.log(list[i] + ": wrong bit depth; "+bitDepth+" expected, "+bitDepth2+" found");
						break;
					}
					if (slice==1) count++;
					IJ.showStatus(count+"/"+n);
					IJ.showProgress(count, n);
					if (scale<100.0)
						ip = ip.resize((int)(width*scale/100.0), (int)(height*scale/100.0));
					if (ip.getMin()<min) min = ip.getMin();
					if (ip.getMax()>max) max = ip.getMax();
					String label2 = label;
					if (depth>1) label2 = ""+slice;
					if (virtualStack) {
						if (slice==1) ((VirtualStack)stack).addSlice(list[i]);
					} else
						stack.addSlice(label2, ip);
				}
				if (count>=n)
					break;
				if (IJ.escapePressed())
					{IJ.beep(); break;}
				//System.gc();
			}
		} catch(OutOfMemoryError e) {
			IJ.outOfMemory("FolderOpener");
			if (stack!=null) stack.trim();
		}
		if (stack!=null && stack.getSize()>0) {
			if (info1!=null && info1.lastIndexOf("7FE0,0010")>0)
				stack = (new DICOM_Sorter()).sort(stack);
			ImagePlus imp2 = new ImagePlus(title, stack);
			if (imp2.getType()==ImagePlus.GRAY16 || imp2.getType()==ImagePlus.GRAY32)
				imp2.getProcessor().setMinAndMax(min, max);
			if (fi==null)
				fi = new FileInfo();
			fi.fileFormat = FileInfo.UNKNOWN;
			fi.fileName = "";
			fi.directory = directory;
			imp2.setFileInfo(fi); // saves FileInfo of the first image
			if (allSameCalibration)
				imp2.setCalibration(cal); // use calibration from first image
			if (imp2.getStackSize()==1 && info1!=null)
				imp2.setProperty("Info", info1);
			imp2.show();
		}
		IJ.showProgress(1.0);
	}
	
	boolean showDialog(ImagePlus imp, String[] list) {
		int fileCount = list.length;
		FolderOpenerDialog gd = new FolderOpenerDialog("Sequence Options", imp, list);
		gd.addNumericField("Number of Images:", fileCount, 0);
		gd.addNumericField("Starting Image:", 1, 0);
		gd.addNumericField("Increment:", 1, 0);
		gd.addNumericField("Scale Images:", scale, 0, 4, "%");
		gd.addStringField("File Name Contains:", "", 10);
		gd.addStringField("or Enter Pattern:", "", 10);
		gd.addCheckbox("Convert to 8-bit Grayscale", convertToGrayscale);
		gd.addCheckbox("Convert_to_RGB", convertToRGB);
		gd.addCheckbox("Sort Names Numerically", sortFileNames);
		gd.addCheckbox("Use Virtual Stack", virtualStack);
		gd.addMessage("10000 x 10000 x 1000 (100.3MB)");
		gd.showDialog();
		if (gd.wasCanceled())
			return false;
		n = (int)gd.getNextNumber();
		start = (int)gd.getNextNumber();
		increment = (int)gd.getNextNumber();
		if (increment<1)
			increment = 1;
		scale = gd.getNextNumber();
		if (scale<5.0) scale = 5.0;
		if (scale>100.0) scale = 100.0;
		filter = gd.getNextString();
		String regex = gd.getNextString();
		if (!regex.equals("")) {
			filter = regex;
			isRegex = true;
		}
		convertToGrayscale = gd.getNextBoolean();
		convertToRGB = gd.getNextBoolean();
		sortFileNames = gd.getNextBoolean();
		virtualStack = gd.getNextBoolean();
		if (virtualStack) {
			convertToGrayscale = false;
			convertToRGB = false;
			scale = 100.0;
		}
		return true;
	}

	/** Removes names that start with "." or end with ".db". ".txt", ".lut", "roi" or ".pty". */
	public String[] trimFileList(String[] rawlist) {
		int count = 0;
		for (int i=0; i< rawlist.length; i++) {
			String name = rawlist[i];
			if (name.startsWith(".")||name.equals("Thumbs.db")||name.endsWith(".txt")||name.endsWith(".lut")||name.endsWith(".roi")||name.endsWith(".pty"))
				rawlist[i] = null;
			else
				count++;
		}
		if (count==0) return null;
		String[] list = rawlist;
		if (count<rawlist.length) {
			list = new String[count];
			int index = 0;
			for (int i=0; i< rawlist.length; i++) {
				if (rawlist[i]!=null)
					list[index++] = rawlist[i];
			}
		}
		return list;
	}

	/** Sorts the file names into numeric order. */
	public String[] sortFileList(String[] list) {
		int listLength = list.length;
		boolean allSameLength = true;
		int len0 = list[0].length();
		for (int i=0; i<listLength; i++) {
			if (list[i].length()!=len0) {
				allSameLength = false;
				break;
			}
		}
		if (allSameLength)
			{ij.util.StringSorter.sort(list); return list;}
		int maxDigits = 15;		
		String[] list2 = null;	
		char ch;	
		for (int i=0; i<listLength; i++) {
			int len = list[i].length();
			String num = "";
			for (int j=0; j<len; j++) {
				ch = list[i].charAt(j);
				if (ch>=48&&ch<=57) num += ch;
			}
			if (list2==null) list2 = new String[listLength];
			if (num.length()==0) num = "aaaaaa";
			num = "000000000000000" + num; // prepend maxDigits leading zeroes
			num = num.substring(num.length()-maxDigits);
			list2[i] = num + list[i];
		}
		if (list2!=null) {
			ij.util.StringSorter.sort(list2);
			for (int i=0; i<listLength; i++)
				list2[i] = list2[i].substring(maxDigits);
			return list2;	
		} else {
			ij.util.StringSorter.sort(list);
			return list;   
		}	
	}

} // FolderOpener

class FolderOpenerDialog extends GenericDialog {
	ImagePlus imp;
	int fileCount;
 	boolean eightBits, rgb;
 	String[] list;
 	boolean isRegex;

	public FolderOpenerDialog(String title, ImagePlus imp, String[] list) {
		super(title);
		this.imp = imp;
		this.list = list;
		this.fileCount = list.length;
	}

	protected void setup() {
 		eightBits = ((Checkbox)checkbox.elementAt(0)).getState();
 		rgb = ((Checkbox)checkbox.elementAt(1)).getState();
		setStackInfo();
	}
 	
	public void itemStateChanged(ItemEvent e) {
		Checkbox item = (Checkbox)e.getSource();
		Checkbox grayscaleCB = (Checkbox)checkbox.elementAt(0);
		Checkbox rgbCB = (Checkbox)checkbox.elementAt(1);
		if (item==grayscaleCB) {
			eightBits = item.getState();
			if (eightBits) {
			 rgbCB.setState(false);
			 rgb = false;
			}
		}
		if (item==rgbCB) {
			rgb = item.getState();
			if (rgb) {
				grayscaleCB.setState(false);
				eightBits = false;
			}
		}
 		setStackInfo();
	}
	
	public void textValueChanged(TextEvent e) {
 		setStackInfo();
	}

	void setStackInfo() {
		int width = imp.getWidth();
		int height = imp.getHeight();
		int depth = imp.getStackSize();
		int bytesPerPixel = 1;
 		int n = getNumber(numberField.elementAt(0));
		int start = getNumber(numberField.elementAt(1));
		int inc = getNumber(numberField.elementAt(2));
		double scale = getNumber(numberField.elementAt(3));
		if (scale<5.0) scale = 5.0;
		if (scale>100.0) scale = 100.0;
		
		if (n<1) n = fileCount;
		if (start<1 || start>fileCount) start = 1;
		if (start+n-1>fileCount)
			n = fileCount-start+1;
		if (inc<1) inc = 1;
 		TextField tf = (TextField)stringField.elementAt(0);
 		String filter = tf.getText();
		tf = (TextField)stringField.elementAt(1);
  		String regex = tf.getText();
		if (!regex.equals("")) {
			filter = regex;
			isRegex = true;
		}
 		if (!filter.equals("") && !filter.equals("*")) {
 			int n2 = 0;
			for (int i=0; i<list.length; i++) {
				if (isRegex&&list[i].matches(filter))
					n2++;
				else if (list[i].indexOf(filter)>=0)
					n2++;
			}
			if (n2<n) n = n2;
 		}
		switch (imp.getType()) {
			case ImagePlus.GRAY16:
				bytesPerPixel=2;break;
			case ImagePlus.COLOR_RGB:
			case ImagePlus.GRAY32:
				bytesPerPixel=4; break;
		}
		if (eightBits)
			bytesPerPixel = 1;
		if (rgb)
			bytesPerPixel = 4;
		width = (int)(width*scale/100.0);
		height = (int)(height*scale/100.0);
		int n2 = ((fileCount-start+1)*depth)/inc;
		if (n2<0) n2 = 0;
		if (n2>n) n2 = n;
		double size = ((double)width*height*n2*bytesPerPixel)/(1024*1024);
 		((Label)theLabel).setText(width+" x "+height+" x "+n2+" ("+IJ.d2s(size,1)+"MB)");
	}

	public int getNumber(Object field) {
		TextField tf = (TextField)field;
		String theText = tf.getText();
		double value;
		Double d;
		try {d = new Double(theText);}
		catch (NumberFormatException e){
			d = null;
		}
		if (d!=null)
			return (int)d.doubleValue();
		else
			return 0;
      }

} // FolderOpenerDialog

