package ij.gui;
import ij.*;
import ij.measure.Calibration;
import ij.plugin.frame.SyncWindows;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;

/** This class is an extended ImageWindow that displays stacks and hyperstacks. */
public class StackWindow extends ImageWindow implements Runnable, AdjustmentListener, ActionListener, MouseWheelListener {

	protected Scrollbar sliceSelector; // for backward compatibity with Image5D
	protected ScrollbarWithLabel cSelector, zSelector, tSelector;
	protected Thread thread;
	protected volatile boolean done;
	protected volatile int slice;
	protected ScrollbarWithLabel animationSelector;
	boolean hyperStack;
	int nChannels=1, nSlices=1, nFrames=1;
	int c=1, z=1, t=1;


	public StackWindow(ImagePlus imp) {
		this(imp, null);
	}

	public StackWindow(ImagePlus imp, ImageCanvas ic) {
		super(imp, ic);
		addScrollbars(imp);
		addMouseWheelListener(this);
		if (sliceSelector==null && this.getClass().getName().indexOf("Image5D")!=-1)
			sliceSelector = new Scrollbar(); // prevents Image5D from crashing
		pack();
		ic = imp.getCanvas();
		if (ic!=null)
			ic.setMaxBounds();
		if (IJ.isMacro() && !isVisible()) //'super' may have called show()
			imp.setDeactivated(); //prepare for waitTillActivated (imp may have been activated before)
		show();
		if (IJ.isMacro())
			imp.waitTillActivated();
		int previousSlice = imp.getCurrentSlice();
		if (previousSlice>1 && previousSlice<=imp.getStackSize())
			imp.setSlice(previousSlice);
		else
			imp.setSlice(1);
		thread = new Thread(this, "zSelector");
		thread.start();
	}

	void addScrollbars(ImagePlus imp) {
		ImageStack s = imp.getStack();
		int stackSize = s.getSize();
		int sliderHeight = 0;
		nSlices = stackSize;
		hyperStack = imp.getOpenAsHyperStack();
		//imp.setOpenAsHyperStack(false);
		int[] dim = imp.getDimensions();
		int nDimensions = 2+(dim[2]>1?1:0)+(dim[3]>1?1:0)+(dim[4]>1?1:0);
		if (nDimensions<=3 && dim[2]!=nSlices)
			hyperStack = false;
		if (hyperStack) {
			nChannels = dim[2];
			nSlices = dim[3];
			nFrames = dim[4];
		}
		if (nSlices==stackSize) hyperStack = false;
		if (nChannels*nSlices*nFrames!=stackSize) hyperStack = false;
		if (cSelector!=null||zSelector!=null||tSelector!=null)
			removeScrollbars();
		ImageJ ij = IJ.getInstance();
		//IJ.log("StackWindow: "+hyperStack+" "+nChannels+" "+nSlices+" "+nFrames+" "+imp);
		if (nChannels>1) {
			cSelector = new ScrollbarWithLabel(this, 1, 1, 1, nChannels+1, 'c');
			add(cSelector);
			sliderHeight += cSelector.getPreferredSize().height + ImageWindow.VGAP;
			if (ij!=null) cSelector.addKeyListener(ij);
			cSelector.addAdjustmentListener(this);
			cSelector.setFocusable(false); // prevents scroll bar from blinking on Windows
			cSelector.setUnitIncrement(1);
			cSelector.setBlockIncrement(1);
		}
		if (nSlices>1) {
			char label = nChannels>1||nFrames>1?'z':'t';
			if (stackSize==dim[2] && imp.isComposite()) label = 'c';
			zSelector = new ScrollbarWithLabel(this, 1, 1, 1, nSlices+1, label);
			if (label=='t') animationSelector = zSelector;
			add(zSelector);
			sliderHeight += zSelector.getPreferredSize().height + ImageWindow.VGAP;
			if (ij!=null) zSelector.addKeyListener(ij);
			zSelector.addAdjustmentListener(this);
			zSelector.setFocusable(false);
			int blockIncrement = nSlices/10;
			if (blockIncrement<1) blockIncrement = 1;
			zSelector.setUnitIncrement(1);
			zSelector.setBlockIncrement(blockIncrement);
			sliceSelector = zSelector.bar;
		}
		if (nFrames>1) {
			animationSelector = tSelector = new ScrollbarWithLabel(this, 1, 1, 1, nFrames+1, 't');
			add(tSelector);
			sliderHeight += tSelector.getPreferredSize().height + ImageWindow.VGAP;
			if (ij!=null) tSelector.addKeyListener(ij);
			tSelector.addAdjustmentListener(this);
			tSelector.setFocusable(false);
			int blockIncrement = nFrames/10;
			if (blockIncrement<1) blockIncrement = 1;
			tSelector.setUnitIncrement(1);
			tSelector.setBlockIncrement(blockIncrement);
		}
		ImageWindow win = imp.getWindow();
		if (win!=null)
			win.setSliderHeight(sliderHeight);
	}

	/** Enables or disables the sliders. Used when locking/unlocking an image. */
	public synchronized void setSlidersEnabled(final boolean b) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				if (sliceSelector != null)	   sliceSelector.setEnabled(b);
				if (cSelector != null)		   cSelector.setEnabled(b);
				if (zSelector != null)		   zSelector.setEnabled(b);
				if (tSelector != null)		   tSelector.setEnabled(b);
				if (animationSelector != null) animationSelector.setEnabled(b);
			}
		});
	}

	public synchronized void adjustmentValueChanged(AdjustmentEvent e) {
		if (!running2 || imp.isHyperStack()) {
			if (e.getSource()==cSelector) {
				c = cSelector.getValue();
				if (c==imp.getChannel()&&e.getAdjustmentType()==AdjustmentEvent.TRACK) return;
			} else if (e.getSource()==zSelector) {
				z = zSelector.getValue();
				int slice = hyperStack?imp.getSlice():imp.getCurrentSlice();
				if (z==slice&&e.getAdjustmentType()==AdjustmentEvent.TRACK) return;
			} else if (e.getSource()==tSelector) {
				t = tSelector.getValue();
				if (t==imp.getFrame()&&e.getAdjustmentType()==AdjustmentEvent.TRACK) return;
			}
			slice = (t-1)*nChannels*nSlices + (z-1)*nChannels + c;
			notify();
		}
		if (!running)
			syncWindows(e.getSource());
	}

	private void syncWindows(Object source) {
		if (SyncWindows.getInstance()==null)
			return;
		if (source==cSelector)
			SyncWindows.setC(this, cSelector.getValue());
		else if (source==zSelector) {
			int stackSize = imp.getStackSize();
			if (imp.getNChannels()==stackSize)
				SyncWindows.setC(this, zSelector.getValue());
			else if (imp.getNFrames()==stackSize)
				SyncWindows.setT(this, zSelector.getValue());
			else
				SyncWindows.setZ(this, zSelector.getValue());
		} else if (source==tSelector)
			SyncWindows.setT(this, tSelector.getValue());
		else
			throw new RuntimeException("Unknownsource:"+source);
	}

	public void actionPerformed(ActionEvent e) {
	}

	public void mouseWheelMoved(MouseWheelEvent e) {
		synchronized(this) {
			int rotation = e.getWheelRotation();
			boolean ctrl = (e.getModifiers()&Event.CTRL_MASK)!=0;
			if (hyperStack) // ctrl+scroll wheel adjusts hyperstack slice positions
				ctrl = false;
			if ((ctrl||IJ.shiftKeyDown()) && ic!=null) {
				Point loc = ic.getCursorLoc();
				int x = ic.screenX(loc.x);
				int y = ic.screenY(loc.y);
				if (rotation<0)
					ic.zoomIn(x,y);
				else if (rotation>0)
					ic.zoomOut(x,y);
				return;
			}
			if (!Prefs.mouseWheelStackScrolling)
				return;
			if (hyperStack) {
				if (rotation>0)
					IJ.run(imp, "Next Slice [>]", "");
				else if (rotation<0)
					IJ.run(imp, "Previous Slice [<]", "");
			} else {
				int slice = imp.getCurrentSlice() + rotation;
				if (slice<1)
					slice = 1;
				else if (slice>imp.getStack().getSize())
					slice = imp.getStack().getSize();
				setSlice(imp,slice);
				imp.updateStatusbarValue();
				SyncWindows.setZ(this, slice);
			}
		}
	}

	public boolean close() {
		if (!super.close())
			return false;
		synchronized(this) {
			done = true;
			notify();
		}
		return true;
	}

	/** Displays the specified slice and updates the stack scrollbar. */
	public void showSlice(int index) {
		if (imp!=null && index>=1 && index<=imp.getStackSize()) {
			setSlice(imp,index);
			SyncWindows.setZ(this, index);
		}
	}

	/** Updates the stack scrollbar. */
	public void updateSliceSelector() {
		if (hyperStack || zSelector==null || imp==null)
			return;
		int stackSize = imp.getStackSize();
		int max = zSelector.getMaximum();
		if (max!=(stackSize+1))
			zSelector.setMaximum(stackSize+1);
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				if (imp!=null && zSelector!=null)
					zSelector.setValue(imp.getCurrentSlice());
			}
		});
	}

	public void run() {
		while (!done) {
			synchronized(this) {
				try {wait();}
				catch(InterruptedException e) {}
			}
			if (done) return;
			if (slice>0) {
				int s = slice;
				slice = 0;
				if (s!=imp.getCurrentSlice()) {
					imp.updatePosition(c, z, t);
					setSlice(imp,s);
				}
			}
		}
	}

	public String createSubtitle() {
		String subtitle = super.createSubtitle();
		if (!hyperStack || imp.getStackSize()==1)
			return subtitle;
		String s="";
		int[] dim = imp.getDimensions(false);
		int channels=dim[2], slices=dim[3], frames=dim[4];
		if (channels>1) {
			s += "c:"+imp.getChannel()+"/"+channels;
			if (slices>1||frames>1) s += " ";
		}
		if (slices>1) {
			s += "z:"+imp.getSlice()+"/"+slices;
			if (frames>1) s += " ";
		}
		if (frames>1)
			s += "t:"+imp.getFrame()+"/"+frames;
		if (running2) return s;
		int index = subtitle.indexOf(";");
		if (index!=-1) {
			int index2 = subtitle.indexOf("(");
			if (index2>=0 && index2<index && subtitle.length()>index2+4 && !subtitle.substring(index2+1, index2+4).equals("ch:")) {
				index = index2;
				s = s + " ";
			}
			subtitle = subtitle.substring(index, subtitle.length());
		} else
			subtitle = "";
		return s + subtitle;
	}

	public boolean isHyperStack() {
		return hyperStack && getNScrollbars()>0;
	}

	public void setPosition(int channel, int slice, int frame) {
		if (cSelector!=null && channel!=c) {
			c = channel;
			cSelector.setValue(channel);
			SyncWindows.setC(this, channel);
		}
		if (zSelector!=null && slice!=z) {
			z = slice;
			zSelector.setValue(slice);
			SyncWindows.setZ(this, slice);
		}
		if (tSelector!=null && frame!=t) {
			t = frame;
			tSelector.setValue(frame);
			SyncWindows.setT(this, frame);
		}
		this.slice = (t-1)*nChannels*nSlices + (z-1)*nChannels + c;
		imp.updatePosition(c, z, t);
		if (this.slice>0) {
			int s = this.slice;
			this.slice = 0;
			if (s!=imp.getCurrentSlice())
				imp.setSlice(s);
		}
	}

	private void setSlice(ImagePlus imp, int n) {
		if (imp.isLocked()) {
			IJ.beep();
			IJ.showStatus("Image is locked");
		} else
			imp.setSlice(n);
	}

	public boolean validDimensions() {
		int c = imp.getNChannels();
		int z = imp.getNSlices();
		int t = imp.getNFrames();
		//IJ.log(c+" "+z+" "+t+" "+nChannels+" "+nSlices+" "+nFrames+" "+imp.getStackSize());
		int size = imp.getStackSize();
		if (c==size && c*z*t==size && nSlices==size && nChannels*nSlices*nFrames==size)
			return true;
		if (c!=nChannels||z!=nSlices||t!=nFrames||c*z*t!=size)
			return false;
		else
			return true;
	}

	public void setAnimate(boolean b) {
		if (running2!=b && animationSelector!=null)
			animationSelector.updatePlayPauseIcon();
		running2 = b;
	}

	public boolean getAnimate() {
		return running2;
	}

	public int getNScrollbars() {
		int n = 0;
		if (cSelector!=null) n++;
		if (zSelector!=null) n++;
		if (tSelector!=null) n++;
		return n;
	}

	void removeScrollbars() {
		if (cSelector!=null) {
			remove(cSelector);
			cSelector.removeAdjustmentListener(this);
			cSelector = null;
		}
		if (zSelector!=null) {
			remove(zSelector);
			zSelector.removeAdjustmentListener(this);
			zSelector = null;
		}
		if (tSelector!=null) {
			remove(tSelector);
			tSelector.removeAdjustmentListener(this);
			tSelector = null;
		}
	}

}
