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

/** Implements the Image/Stacks/Make Montage command. */
public class MontageMaker implements PlugIn {
			
	private static int columns, rows, first, last, inc, borderWidth;
	private static double scale;
	private static boolean label;
	private static boolean useForegroundColor;
	private static int saveID;
	private static int saveStackSize;
	private static final int defaultFontSize = 12;
	private static int fontSize = defaultFontSize;
	private boolean hyperstack;

	public void run(String arg) {
		ImagePlus imp = WindowManager.getCurrentImage();
		if (imp==null || imp.getStackSize()==1) {
			error("Stack required");
			return;
		}
		hyperstack = imp.isHyperStack();
		if (hyperstack && imp.getNSlices()>1 && imp.getNFrames()>1) {
			error("5D hyperstacks are not supported");
			return;
		}
		int channels = imp.getNChannels();
		if (!hyperstack && imp.isComposite() && channels>1) {
			int channel = imp.getChannel();
			CompositeImage ci = (CompositeImage)imp;
			int mode = ci.getMode();
			if (mode==IJ.COMPOSITE)
				ci.setMode(IJ.COLOR);
			ImageStack stack = new ImageStack(imp.getWidth(), imp.getHeight());
			for (int c=1; c<=channels; c++) {
				imp.setPosition(c, imp.getSlice(), imp.getFrame());
				Image img = imp.getImage();
				stack.addSlice(null, new ColorProcessor(img));
			}
			if (ci.getMode()!=mode)
				ci.setMode(mode);
			imp.setPosition(channel, imp.getSlice(), imp.getFrame());
			Calibration cal = imp.getCalibration();
			imp = new ImagePlus(imp.getTitle(), stack);
			imp.setCalibration(cal);
		}
		makeMontage(imp);
		imp.updateImage();
		saveID = imp.getID();
		IJ.register(MontageMaker.class);
	}
	
	public void makeMontage(ImagePlus imp) {
			int nSlices = imp.getStackSize();
			if (hyperstack) {
				nSlices = imp.getNSlices();
				if (nSlices==1)
					nSlices = imp.getNFrames();
			}
			boolean macro = Macro.getOptions()!=null;
			if (macro || columns==0 || !(imp.getID()==saveID || nSlices==saveStackSize)) {
				columns = (int)Math.sqrt(nSlices);
				rows = columns;
				int n = nSlices - columns*rows;
				if (n>0) columns += (int)Math.ceil((double)n/rows);
				scale = 1.0;
				if (imp.getWidth()*columns>800)
					scale = 0.5;
				if (imp.getWidth()*columns>1600)
					scale = 0.25;
				inc = 1;
				first = 1;
				last = nSlices;
			}
			if (macro) {
				fontSize = defaultFontSize;
				borderWidth = 0;
				label = false;
				useForegroundColor = false;
			}
			saveStackSize = nSlices;
			
			GenericDialog gd = new GenericDialog("Make Montage");
			gd.addNumericField("Columns:", columns, 0);
			gd.addNumericField("Rows:", rows, 0);
			gd.addNumericField("Scale factor:", scale, 2);
			if (!hyperstack) {
				gd.addNumericField("First slice:", first, 0);
				gd.addNumericField("Last slice:", last, 0);
			}
			gd.addNumericField("Increment:", inc, 0);
			gd.addNumericField("Border width:", borderWidth, 0);
			gd.addNumericField("Font size:", fontSize, 0);
			gd.addCheckbox("Label slices", label);
			gd.addCheckbox("Use foreground color", useForegroundColor);
			gd.showDialog();
			if (gd.wasCanceled())
				return;
			columns = (int)gd.getNextNumber();
			rows = (int)gd.getNextNumber();
			scale = gd.getNextNumber();
			gd.setSmartRecording(true);
			if (!hyperstack) {
				first = (int)gd.getNextNumber();
				last = (int)gd.getNextNumber();
			}
			inc = (int)gd.getNextNumber();
			borderWidth = (int)gd.getNextNumber();
			fontSize = (int)gd.getNextNumber();
			if (borderWidth<0) borderWidth = 0;
			if (first<1) first = 1;
			if (last>nSlices) last = nSlices;
			if (first>last)
				{first=1; last=nSlices;}
			if (inc<1) inc = 1;
			if (gd.invalidNumber()) {
				error("Invalid number");
				return;
			}
			label = gd.getNextBoolean();
			useForegroundColor = gd.getNextBoolean();
			ImagePlus imp2 = null;
			if (hyperstack)
				imp2 = makeHyperstackMontage(imp, columns, rows, scale, inc, borderWidth, label);
			else
				imp2 = makeMontage2(imp, columns, rows, scale, first, last, inc, borderWidth, label);
			if (imp2!=null)
				imp2.show();
			if (macro) {
				fontSize = defaultFontSize;
				borderWidth = 0;
				label = false;
				useForegroundColor = false;
				columns = 0;
			}
	}
	
	/** Creates a montage and displays it. */
	public void makeMontage(ImagePlus imp, int columns, int rows, double scale, int first, int last, int inc, int borderWidth, boolean labels) {
		ImagePlus imp2 = makeMontage2(imp, columns, rows, scale, first, last, inc, borderWidth, labels);
		imp2.show();
	}

	/** Creates a montage and returns it as an ImagePlus. */
	public ImagePlus makeMontage2(ImagePlus imp, int columns, int rows, double scale, int first, int last, int inc, int borderWidth, boolean labels) {
		int stackWidth = imp.getWidth();
		int stackHeight = imp.getHeight();
		int nSlices = imp.getStackSize();
		int width = (int)(stackWidth*scale);
		int height = (int)(stackHeight*scale);
		int montageWidth = width*columns + borderWidth*(columns-1);
		int montageHeight = height*rows + borderWidth*(rows-1);
		ImageProcessor ip = imp.getProcessor();
		ImageProcessor montage = ip.createProcessor(montageWidth, montageHeight);
		ImagePlus imp2 = new ImagePlus("Montage", montage);
		imp2.setCalibration(imp.getCalibration());
		montage = imp2.getProcessor();
		Color fgColor=Color.white;
		Color bgColor = Color.black;
		if (useForegroundColor) {
			fgColor = Toolbar.getForegroundColor();
			bgColor = Toolbar.getBackgroundColor();
		} else {
			boolean whiteBackground = false;
			if ((ip instanceof ByteProcessor) || (ip instanceof ColorProcessor)) {
				ip.setRoi(0, stackHeight-12, stackWidth, 12);
				ImageStatistics stats = ImageStatistics.getStatistics(ip, Measurements.MODE, null);
				ip.resetRoi();
				whiteBackground = stats.mode>=200;
				if (imp.isInvertedLut())
					whiteBackground = !whiteBackground;
			}
			if (whiteBackground) {
				fgColor=Color.black;
				bgColor = Color.white;
			}
		}
		if (Double.isNaN(Toolbar.getBackgroundValue()))
			montage.setColor(bgColor);
		else
			montage.setGlobalBackgroundColor();
		montage.fill();
		if (Double.isNaN(Toolbar.getForegroundValue()))
			montage.setColor(fgColor);
		else
			montage.setGlobalForegroundColor();
		montage.setFont(new Font("SansSerif", Font.PLAIN, fontSize));
		montage.setAntialiasedText(true);
		ImageStack stack = imp.getStack();
		int x = 0;
		int y = 0;
		ImageProcessor aSlice;
	    int slice = first;
		while (slice<=last) {
			aSlice = stack.getProcessor(slice);
			if (scale!=1.0) {
				aSlice.setInterpolationMethod(ImageProcessor.BILINEAR);
				boolean averageWhenDownSizing = width<200;
				aSlice = aSlice.resize(width, height, averageWhenDownSizing);
			}
			montage.insert(aSlice, x, y);
			String label = stack.getShortSliceLabel(slice);
			if (labels)
				drawLabel(montage, slice, label, x, y, width, height, borderWidth);
			x += width + borderWidth;
			if (x>=montageWidth) {
				x = 0;
				y += height + borderWidth;;
				if (y>=montageHeight)
					break;
			}
			IJ.showProgress((double)(slice-first)/(last-first));
			slice += inc;
		}
		if (borderWidth>0) {
			for (x=width; x<montageWidth; x+=width+borderWidth) {
				montage.setRoi(x, 0, borderWidth, montageHeight);
				montage.fill();
			}
			for (y=height; y<montageHeight; y+=height+borderWidth) {
				montage.setRoi(0, y, montageWidth, borderWidth);
				montage.fill();
			}
		}
		IJ.showProgress(1.0);
		Calibration cal = imp2.getCalibration();
		if (cal.scaled()) {
			cal.pixelWidth /= scale;
			cal.pixelHeight /= scale;
		}
        imp2.setProperty("Info", "xMontage="+columns+"\nyMontage="+rows+"\n");
		return imp2;
	}
		
	/** Creates a hyperstack montage and returns it as an ImagePlus. */
	private ImagePlus makeHyperstackMontage(ImagePlus imp, int columns, int rows, double scale, int inc, int borderWidth, boolean labels) {
		ImagePlus[] channels = ChannelSplitter.split(imp);
		int n = channels.length;
		ImagePlus[] montages = new ImagePlus[n];
		for (int i=0; i<n; i++) {
			int last = channels[i].getStackSize();
			montages[i] = makeMontage2(channels[i], columns, rows, scale, 1, last, inc, borderWidth, labels);
		}
		ImagePlus montage = (new RGBStackMerge()).mergeHyperstacks(montages, false);
		montage.setCalibration(montages[0].getCalibration());
		montage.setTitle("Montage");
		return montage;
	}
	
	private void error(String msg) {
		IJ.error("Make Montage", msg);
	}
	
	void drawLabel(ImageProcessor montage, int slice, String label, int x, int y, int width, int height, int borderWidth) {
		if (label!=null && !label.equals("") && montage.getStringWidth(label)>=width) {
			do {
				label = label.substring(0, label.length()-1);
			} while (label.length()>1 && montage.getStringWidth(label)>=width);
		}
		if (label==null || label.equals(""))
			label = ""+slice;
		int swidth = montage.getStringWidth(label);
		x += width/2 - swidth/2;
		y -= borderWidth/2;
		y += height;
		montage.drawString(label, x, y);
	}
	
	public void setFontSize(int fontSize) {
		this.fontSize = fontSize;
	}
	
}


