package ij;
import ij.process.*;
import ij.io.*;
import ij.gui.ImageCanvas;
import ij.util.Tools;
import ij.plugin.FolderOpener;
import java.io.*;
import java.awt.*;
import java.awt.image.ColorModel;
import java.util.Properties;

/** This class represents an array of disk-resident images. */
public class VirtualStack extends ImageStack {
	private static final int INITIAL_SIZE = 100;
	private String path;
	private int nSlices;
	private String[] names;
	private String[] labels;
	private int bitDepth;
	private int delay;
	private Properties properties;
	private boolean generateData;
	private int[] indexes;  // used to translate non-CZT hyperstack slice numbers

	
	/** Default constructor. */
	public VirtualStack() { }
	
	public VirtualStack(int width, int height) {
		super(width, height);
	}

	/** Creates an empty virtual stack.
	 * @param width		image width
	 * @param height	image height
	 * @param cm	ColorModel or null
	 * @param path	file path of directory containing the images
	 * @see #addSlice(String)
	 * @see <a href="http://wsr.imagej.net/macros/js/OpenAsVirtualStack.js">OpenAsVirtualStack.js</a>
	*/
	public VirtualStack(int width, int height, ColorModel cm, String path) {
		super(width, height, cm);
		path = IJ.addSeparator(path);
		this.path = path;
		names = new String[INITIAL_SIZE];
		labels = new String[INITIAL_SIZE];
	}

	/** Creates a virtual stack with no backing storage.<br>
	 * See: Help&gt;Examples&gt;JavaScript&gt;Terabyte VirtualStack
	*/
	public VirtualStack(int width, int height, int slices) {
		this(width, height, slices, "8-bit");
	}

	/** Creates a virtual stack with no backing storage.<br>
	 * See: Help&gt;Examples&gt;JavaScript&gt;Terabyte VirtualStack
	 */
	public VirtualStack(int width, int height, int slices, String options) {
		super(width, height, null);
		nSlices = slices;
		int depth = 8;
  		if (options.contains("16-bit")) depth=16;
 	    if (options.contains("RGB")) depth=24;
        if (options.contains("32-bit")) depth=32;
        if (options.contains("delay")) delay=250;
        this.generateData = options.contains("fill");
		this.bitDepth = depth;
	}

	/** Adds an image to the end of a virtual stack created using the
	 * VirtualStack(w,h,cm,path) constructor. The argument 
	 * can be a full file path (e.g., "C:/Users/wayne/dir1/image.tif")
	 * if the 'path' argument in the constructor is "". File names
	 * that start with '.' are ignored.
	*/
	public void addSlice(String fileName) {
		if (fileName==null) 
			throw new IllegalArgumentException("'fileName' is null!");
		if (fileName.startsWith("."))
			return;
		if (names==null)
				throw new IllegalArgumentException("VirtualStack(w,h,cm,path) constructor not used");
		nSlices++;
	   if (nSlices==names.length) {
			String[] tmp = new String[nSlices*2];
			System.arraycopy(names, 0, tmp, 0, nSlices);
			names = tmp;
			tmp = new String[nSlices*2];
			System.arraycopy(labels, 0, tmp, 0, nSlices);
			labels = tmp;
		}
		names[nSlices-1] = fileName;
	}

   /** Does nothing. */
	public void addSlice(String sliceLabel, Object pixels) {
	}

	/** Does nothing.. */
	public void addSlice(String sliceLabel, ImageProcessor ip) {
	}
	
	/** Does noting. */
	public void addSlice(String sliceLabel, ImageProcessor ip, int n) {
	}

	/** Deletes the specified slice, where {@literal 1<=n<=nslices}. */
	public void deleteSlice(int n) {
		if (nSlices==0)
			return;
		if (n<1 || n>nSlices)
			throw new IllegalArgumentException("Argument out of range: "+n);
		for (int i=n; i<nSlices; i++)
			names[i-1] = names[i];
		names[nSlices-1] = null;
		nSlices--;
	}
	
	/** Deletes the last slice in the stack. */
	public void deleteLastSlice() {
		int n = size();
		if (n>0)
			deleteSlice(n);
	}
	   
   /** Returns the pixel array for the specified slice, where {@literal 1<=n<=nslices}. */
	public Object getPixels(int n) {
		ImageProcessor ip = getProcessor(n);
		if (ip!=null)
			return ip.getPixels();
		else
			return null;
	}		
	
	/** Assigns a pixel array to the specified slice, where {@literal 1<=n<=nslices}. */
	public void setPixels(Object pixels, int n) {
	}

	/** Returns an ImageProcessor for the specified slice,
	 * where {@literal 1<=n<=nslices}. Returns null if
	 * the stack is empty.
	*/
     public ImageProcessor getProcessor(int n) {
		if (path==null) {  //Help>Examples?JavaScript>Terabyte VirtualStack
			ImageProcessor ip = null;
			int w=getWidth(), h=getHeight();
			switch (bitDepth) {
				case 8: ip = new ByteProcessor(w,h); break;
				case 16: ip = new ShortProcessor(w,h); break;
				case 24: ip = new ColorProcessor(w,h); break;
				case 32: ip = new FloatProcessor(w,h); break;
			}
			String hlabel = null;
			if (generateData) {
				int value = 0;
				ImagePlus img = WindowManager.getCurrentImage();
				if (img!=null && img.getStackSize()==nSlices)
					value = img.getCurrentSlice()-1;
				if (bitDepth==16)
					value *= 256;
				if (bitDepth!=32) {
					for (int i=0; i<ip.getPixelCount(); i++)
						ip.set(i,value++);
				}
				if (img!=null && img.isHyperStack()) {
					int[] pos = img.convertIndexToPosition(n);
					hlabel = pos[0]+" "+pos[1]+" "+pos[2]+" "+n;
				}
			}
			label(ip, hlabel!=null?hlabel:""+n, Color.white);
			if (delay>0)
				IJ.wait(delay);
			return ip;
		}
		n = translate(n);  // update n for hyperstacks not in the default CZT order
		Opener opener = new Opener();
		opener.setSilentMode(true);
		IJ.redirectErrorMessages(true);
		ImagePlus imp = opener.openTempImage(path, names[n-1]);
		IJ.redirectErrorMessages(false);
		ImageProcessor ip = null;
		int depthThisImage = 0;
		if (imp!=null) {
			int w = imp.getWidth();
			int h = imp.getHeight();
			int type = imp.getType();
			ColorModel cm = imp.getProcessor().getColorModel();
			String info = (String)imp.getProperty("Info");
			if (info!=null) {
				if (FolderOpener.useInfo(info))
					labels[n-1] = info;
			} else {
				String sliceLabel = imp.getStack().getSliceLabel(1);
				if (FolderOpener.useInfo(sliceLabel))
					labels[n-1] = "Label: "+sliceLabel;
			}
			depthThisImage = imp.getBitDepth();
			ip = imp.getProcessor();
			ip.setOverlay(imp.getOverlay());
			properties = imp.getProperty("FHT")!=null?imp.getProperties():null;
		} else {
			File f = new File(path, names[n-1]);
			String msg = f.exists()?"Error opening ":"File not found: ";
			ip = new ByteProcessor(getWidth(), getHeight());
			ip.invert();
			label(ip, msg+names[n-1], Color.black);
			depthThisImage = 8;
		}
		if (depthThisImage!=bitDepth) {
			switch (bitDepth) {
				case 8: ip=ip.convertToByte(true); break;
				case 16: ip=ip.convertToShort(true); break;
				case 24:  ip=ip.convertToRGB(); break;
				case 32: ip=ip.convertToFloat(); break;
			}
		}
		if (ip.getWidth()!=getWidth() || ip.getHeight()!=getHeight()) {
			ImageProcessor ip2 = ip.createProcessor(getWidth(), getHeight());
			ip2.insert(ip, 0, 0);
			ip = ip2;
		}
		if (cTable!=null)
			ip.setCalibrationTable(cTable);
		ip.setSliceNumber(n);
		return ip;
	 }
	 	 
	 private void label(ImageProcessor ip, String msg, Color color) {
		int size = getHeight()/20;
		if (size<9) size=9;
		Font font = new Font("Helvetica", Font.PLAIN, size);
		ip.setFont(font);
		ip.setAntialiasedText(true);
		ip.setColor(color);
		ip.drawString(msg, size, size*2);
	}
 
	/** Currently not implemented */
	public int saveChanges(int n) {
		return -1;
	}
	
	/** Returns the number of slices in this stack. */
	public int size() {
		return getSize();
	}

	public int getSize() {
		return nSlices;
	}

	/** Returns the label of the Nth image. */
	public String getSliceLabel(int n) {
		if (labels==null)
			return null;
		String label = labels[n-1];
		if (label==null)
			return names[n-1];
		else {
			if (label.startsWith("Label: "))  // slice label
				return label.substring(7,label.length());
			else
				return names[n-1]+"\n"+label;
		}
	}
	
	/** Returns null. */
	public Object[] getImageArray() {
		return null;
	}

   /** Does nothing. */
	public void setSliceLabel(String label, int n) {
	}

	/** Always return true. */
	public boolean isVirtual() {
		return true;
	}

   /** Does nothing. */
	public void trim() {
	}
	
	/** Returns the path to the directory containing the images. */
	public String getDirectory() {
		return IJ.addSeparator(path);
	}
		
	/** Returns the file name of the specified slice, where {@literal 1<=n<=nslices}. */
	public String getFileName(int n) {
		return names[n-1];
	}
	
	/** Sets the bit depth (8, 16, 24 or 32). */
	public void setBitDepth(int bitDepth) {
		this.bitDepth = bitDepth;
	}

	/** Returns the bit depth (8, 16, 24 or 32), or 0 if the bit depth is not known. */
	public int getBitDepth() {
		return bitDepth;
	}
	
	public ImageStack sortDicom(String[] strings, String[] info, int maxDigits) {
		int n = size();
		String[] names2 = new String[n];
		for (int i=0; i<n; i++)
			names2[i] = names[i];
		for (int i=0; i<n; i++) {
			int slice = (int)Tools.parseDouble(strings[i].substring(strings[i].length()-maxDigits), 0.0);
			if (slice==0) return null;
			names[i] = names2[slice-1];
			labels[i] = info[slice-1];
		}
		return this;
	}
	
	/** Returns the ImagePlus Properties assoctated with the current slice, or null. */
	public Properties getProperties() {
		return properties;
	}
	
	/** Sets the table that translates slice numbers of hyperstacks not in default CZT order. */
	public void setIndexes(int[] indexes) {
		this.indexes = indexes;
	}
	
	/** Translates slice numbers of hyperstacks not in default CZT order. */
	public int translate(int n) {
		int n2 = (indexes!=null&&indexes.length==getSize()) ? indexes[n-1]+1 : n;
		//IJ.log("translate: "+n+" "+n2+" "+getSize()+" "+(indexes!=null?indexes.length:null));
		return n2;
	}
	
	/** Reduces the number of slices in this stack by a factor. */
	public void reduce(int factor) {
		if (factor<2 || nSlices/factor<1 || names==null)
			return;
		nSlices = nSlices/factor;
		for (int i=0; i<nSlices; i++) {
			names[i] = names[i*factor];
			labels[i] = labels[i*factor];
		}
		ImagePlus imp = WindowManager.getCurrentImage();
		if (imp!=null) {
			imp.setSlice(1);
			imp.updateAndRepaintWindow();
		}
	}

} 

