package imseProc.gui.swt;


import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import jafama.FastMath;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import otherSupport.ColorMaps;
import imseProc.core.IMSEProc;
import imseProc.core.ImagePipeController;
import imseProc.core.ImagePipeControllerROISettable;
import imseProc.core.Img;
import imseProc.core.ImgPipe;
import imseProc.core.ImgSink;
import imseProc.core.ImgSource;

//async
public class ImageWindow extends ImgPipe implements ImgSink {
	private static final int nCols = 1000;
	
	private byte cMap[];
	private double vMap[];
	private double vMapColourScale = Double.NaN;

	private static final double scaleConst = 1.1;
	private static final double logScaleConst = FastMath.log(scaleConst);
	
	private ConcurrentHashMap<ImgSink, ImagePipeController> sinkControllers = new ConcurrentHashMap<ImgSink, ImagePipeController>();
	
	private Shell swtShell;
	
	private Image swtCurImage;
	private Canvas swtImageCanvas;
	
	private ScrolledComposite swtImgScrollComp;
	private SashForm swtSashForm;
	private Composite swtRHSComposite;
	private Composite swtInfoTabComposite;
	private CTabItem swtSourceTab;
	private CTabItem swtInfoTab;
	
	private Spinner swtImageIndexSpinner;
	private Label swtTimeText;
	private Button triggerStartButton;	
	private Button triggerAbortButton;
	
	private CTabFolder swtTabFoler;	
	private InfoPanel infoPanel;	
	private ImagePipeController sourceControl;
	private Composite sourceControlComposite;
	
	private int lastScaleSel;
	private int posX = 0, posY = 0;
	private boolean posLocked;
	
	private int selX0, selY0, selW, selH;
	private long selectStartTime = -1;
	
	
	/** Syncronising Objects */ 
	private String imageChangeSyncObj = new String("imageChangeSyncObj");
	private String imageSetChangeSyncObj = new String("imageSetChangeSyncObj");
	private String imageConvertSyncObj = new String("imageConvertSyncObj");
	private String imageDisplaySyncObj = new String("imageDisplaySyncObj");
	
	
	public ImageWindow(Display swtDisplay, ImgSource source, int imageIndex) {
		createWindow(swtDisplay);
		
		setSource(source);
		setSelectedSourceIndex(imageIndex);
		if(source != null){
			//this.image = getSelectedImageFromConnectedSource();
			//images = connectedSource.getImageSet();
		}
		
		//try to load controllers for all sinks already connected to the source
		if(source != null){
			for(ImgSink sink : source.getConnectedSinks())
				addSinkController(sink);
		}
	}
	
	public ImageWindow(Display swtDisplay, ImgSource source, int imageIndex, double scale, int x0, int y0) {
		this(swtDisplay, source, imageIndex);		
		setScale(scale);
		swtImgScrollComp.setOrigin(x0, y0);
	}
	
	private void createWindow(Display display){
		swtShell = new Shell(display, SWT.RESIZE | SWT.DIALOG_TRIM);
		swtShell.setText("Image [init]");
		
        swtShell.setSize(500, 500);
        swtShell.setLayout(new FillLayout());
        swtShell.addListener(SWT.Close, new Listener() { @Override public void handleEvent(Event event) { shellClosedEvent(event); }});
        
        swtSashForm = new SashForm(swtShell, SWT.HORIZONTAL);
        swtSashForm.setLayout(new GridLayout(2, false));
        
        swtImgScrollComp = new ScrolledComposite(swtSashForm, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER_SOLID);
        
        swtImageCanvas = new Canvas(swtImgScrollComp, SWT.NO_BACKGROUND);
        swtImageCanvas.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { canvasPaintEvent(e); }});
        swtImageCanvas.addListener(SWT.MouseDown, new Listener() { @Override public void handleEvent(Event e) { canvasMouseDownEvent(e); }});
        swtImageCanvas.addListener(SWT.MouseUp, new Listener() { @Override public void handleEvent(Event e) { canvasMouseUpEvent(e); }});
        swtImageCanvas.addListener(SWT.MouseMove, new Listener() { @Override public void handleEvent(Event e) { canvasMouseMoveEvent(e); }});
        
        swtImgScrollComp.setContent(swtImageCanvas);
        swtImgScrollComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        swtImgScrollComp.addListener(SWT.MouseWheel, new Listener() { @Override public void handleEvent(Event event) { scrollWheelEvent(event); } });
       
        swtRHSComposite = new Composite(swtSashForm, SWT.NONE);
        swtRHSComposite.setLayout(new GridLayout(5, false));
        
        Label lII = new Label(swtRHSComposite, 0);
        lII.setText("Index:");
        
        swtImageIndexSpinner = new Spinner(swtRHSComposite, 0);
        swtImageIndexSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
       	swtImageIndexSpinner.setValues(-1, -1, -1, 0, 1, 1);
        swtImageIndexSpinner.setEnabled(false);
        swtImageIndexSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { imageIndexChanged(); } });
        
        swtTimeText = new Label(swtRHSComposite, SWT.NONE);
        swtTimeText.setText("t = ??.????");
        swtTimeText.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
       	
        triggerStartButton = new Button(swtRHSComposite, SWT.PUSH);
        triggerStartButton.setText("Trigger All");
        triggerStartButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1));
        triggerStartButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { triggerStartButtonEvent(event); } });        
        
        triggerAbortButton = new Button(swtRHSComposite, SWT.PUSH);
        triggerAbortButton.setText("Abort All");
        triggerAbortButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1));
        triggerAbortButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { triggerAbortButtonEvent(event); } });   
       
        swtTabFoler = new CTabFolder(swtRHSComposite, SWT.BORDER);
        swtTabFoler.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));
        swtTabFoler.setUnselectedCloseVisible(true);
        swtInfoTab = new CTabItem(swtTabFoler, SWT.NONE);
        
        swtInfoTabComposite = new Composite(swtTabFoler, SWT.NONE);
        swtInfoTabComposite.setLayout(new GridLayout(1, false));
        
        infoPanel = new InfoPanel(this, swtInfoTabComposite, SWT.BORDER_SOLID);
        
        swtInfoTab.setControl(swtInfoTabComposite);
        swtInfoTab.setText("Info");
        
    	swtSourceTab = new CTabItem(swtTabFoler, SWT.NONE);
		swtSourceTab.setText("Source");		
                        
        System.out.println("init,  scale now: " + infoPanel.swtZoomScale.getSelection());
        
       // swtSidePane.setSize(swtSidePane.computeSize(SWT.DEFAULT, SWT.DEFAULT));
        swtShell.pack();        
        swtShell.open();
        
        createSWTImage();
	}
		
	private void triggerStartButtonEvent(Event event){
		connectedSource.triggerStart();
	}

	private void triggerAbortButtonEvent(Event event){
		connectedSource.triggerAbort();
	}
	
	/** Add an SWT controller for the given sink to our window */ 
	public void addSinkController(ImgSink sink){
		
		//ImagePipeController sinkController = (ImagePipeController)sink.createSinkController(Composite.class, new Object[]{ swtSidePane, SWT.BORDER_SOLID });
		final ImagePipeController sinkController = (ImagePipeController)sink.createSinkController(Composite.class, new Object[]{ swtTabFoler, SWT.BORDER_SOLID });
    	if(sinkController != null){
    		final Composite sinkComposite = (Composite)sinkController.getInterfacingObject();
    		//sinkComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    		CTabItem sinkTab = new CTabItem(swtTabFoler, SWT.NONE | SWT.CLOSE);
    		sinkTab.setText(sinkController.getPipe().toShortString());
    		sinkTab.setControl(sinkComposite);
    		sinkTab.addListener(SWT.Dispose, new Listener() { @Override public void handleEvent(Event event) { 
    			sinkComposite.dispose();
    			sinkControllers.remove((ImgSink)sinkController.getPipe());
    			
    		}});
    		
    		sinkControllers.put(sink, sinkController);    		
    		
        }
    }
	
	void updateInfoPanel() {
		Img image = getSelectedImageFromConnectedSource();
		infoPanel.swtImageInfoLabel.setText((image == null) ? "NULL" : image.toString());
        swtShell.setText("Image " + getShortID());
        
        infoPanel.swtAcceptNewButton.setEnabled(connectedSource != null); //only works with an image source
        
        // Update list of sources
        LinkedList<ImgSource> srcList = IMSEProc.getSourceInstances();
        
        infoPanel.swtSourceCombo.setItems(new String[0]);        
        for(ImgSource src : srcList){
        	infoPanel.swtSourceCombo.add(src.toShortString());
    		if(src == connectedSource){
    			infoPanel.swtSourceCombo.select(infoPanel.swtSourceCombo.getItemCount() - 1);
    		}
    	}
        
        // Update list of sink types        
        List<Class> sinkTypes = IMSEProc.getSinkClasses();
        infoPanel.swtNewSinkCombo.setItems(new String[0]);        
        for(Class sinkType : sinkTypes){
        	infoPanel.swtNewSinkCombo.add(sinkType.getSimpleName());
        }
        
        //if we have some metaData called time
        if(connectedSource != null){
        	Object o = connectedSource.getSeriesMetaData("/time");
        	if(o != null && o instanceof double[]){
        		double t[] = (double[])o;
        		if(selectedSourceIndex >= 0 && selectedSourceIndex < t.length)
        		swtTimeText.setText("t = " + t[selectedSourceIndex]);
        	}
        }
        
        
        
	}
	
	private String getShortID(){
		Img image = getSelectedImageFromConnectedSource();
		return 
				((connectedSource == null) ? "NoSRC" : connectedSource.toShortString()) 
						+ "-" +
				((image == null) ? "NoIMG" : image.toShortString());
	}
	
	void colorScaleEvent(Event event, boolean centreOnMouse){
		createSWTImage();
		swtImageCanvas.redraw();		
	}

	void zoomScaleEvent(Event event, boolean centreOnMouse){
		setScale(selToScale(infoPanel.swtZoomScale.getSelection())); //force scale in range
		int newScaleSel = infoPanel.swtZoomScale.getSelection();
		
		Point dOrigin = swtImgScrollComp.getOrigin(); //display units
		Rectangle clientArea = swtImgScrollComp.getClientArea();
		
		// position in display coords of point to zoom in on 
		int dispZX, dispZY;
		if(centreOnMouse){
			dispZX = event.x; 
			dispZY = event.y;
		}else{
			dispZX = clientArea.width / 2;
			dispZY = clientArea.height / 2;
		}
		
		//position in image of that point (caled using the old scale) 
		double lastScale = selToScale(lastScaleSel);
		double imgZX = (dispZX + dOrigin.x) / lastScale;
		double imgZY = (dispZY + dOrigin.y) / lastScale;
		
		//origin of swtScrollComp of that point required to holding that point in the /same place/. 
		double newScale = selToScale(newScaleSel);
		double newDispOX =  imgZX * newScale - dispZX;
		double newDispOY =  imgZY * newScale - dispZY;
		
		swtImgScrollComp.setOrigin((int)newDispOX, (int)newDispOY);
			
		/*System.out.println("scale: " + lastScaleSel + " - " + newScaleSel 
				+ ", origin = " + dOrigin 
				+ ", mouse =" + event.x + ", " + event.y
				+ ", imgC = " + imgCX + ", " + imgCY
				+ ", dispO = " + newDispOX + ", " + newDispOY
				);//*/
		
		Img img = getSelectedImageFromConnectedSource();
		if(img != null){
	        swtImageCanvas.setSize(
					(int)(img.getWidth()*getScale()), 
					(int)(img.getHeight()*getScale()));
		}
		lastScaleSel = newScaleSel;
		swtImageCanvas.redraw();
		
	}
	
	private void scrollWheelEvent(Event event){
		//this stop the default of just scrolling vertically
		event.doit =  false;
		if( (event.stateMask & SWT.CTRL) != 0){
			int v = infoPanel.swtZoomScale.getSelection();
			v += event.count;
			setScale(selToScale(v));			
 
			//adjust the position so that the mouse cursor remains at the same image point
			zoomScaleEvent(event, true);	
		}else if( (event.stateMask & SWT.SHIFT) != 0){
			Point pos = swtImgScrollComp.getOrigin();
			pos.x -= swtImgScrollComp.getHorizontalBar().getPageIncrement() * event.count;
			swtImgScrollComp.setOrigin(pos);		
			swtImageCanvas.redraw();
			
		}else{
			Point pos = swtImgScrollComp.getOrigin();
			pos.y -= swtImgScrollComp.getVerticalBar().getPageIncrement() * event.count;
			swtImgScrollComp.setOrigin(pos);
			swtImageCanvas.redraw();
		}
	}
	
	private void canvasMouseDownEvent(Event event){
		
		if(selectStartTime < 0){
			// start secltion
			selectStartTime = System.currentTimeMillis();
			
			setSel(event.x, event.y, true);
			
			swtImageCanvas.redraw(); //redraw without the crosshair lines
		}
				
		
	}
	
	private void canvasMouseUpEvent(Event event){
		
		if((System.currentTimeMillis() - selectStartTime) < 500){
			selectStartTime = -1;
		}
		
		if(selectStartTime < 0){
			setPos(event.x, event.y, true);
			posLocked = (event.button != 3);
		}
		
		if(selectStartTime >= 0){
			// stop selecting
			setSel(event.x, event.y, false);
			selectStartTime = -1;
		}
		
	}
	
	private void canvasMouseMoveEvent(Event event){
		if(selectStartTime >= 0){
			setSel(event.x, event.y, false);
			
		}else if(!posLocked){
			setPos(event.x, event.y, false);		
		}
	}
	
	private void setPos(int canvasX, int canvasY, boolean click){
		double scale = getScale();
		posX = (int)(canvasX / scale + 0.5);
		posY = (int)(canvasY / scale + 0.5);

		if(click){
			if(sourceControl instanceof ImagePipeControllerROISettable)
						((ImagePipeControllerROISettable)sourceControl).fixedPos(posX, posY);
			for(ImagePipeController sinkController : sinkControllers.values())			
				if(sinkController instanceof ImagePipeControllerROISettable)
					((ImagePipeControllerROISettable)sinkController).fixedPos(posX, posY);
		}else{
			if(sourceControl instanceof ImagePipeControllerROISettable)
					((ImagePipeControllerROISettable)sourceControl).movingPos(posX, posY);
			
			for(ImagePipeController sinkController : sinkControllers.values())			
				if(sinkController instanceof ImagePipeControllerROISettable)
					((ImagePipeControllerROISettable)sinkController).movingPos(posX, posY);
		}
	
		swtImageCanvas.redraw();
	}
	
	private void setSel(int canvasX, int canvasY, boolean init){
			
		double scale = getScale();
		int x = (int)(canvasX / scale + 0.5);
		int y = (int)(canvasY / scale + 0.5);
		
		if(init){
			selX0 = x;	selY0 = y;
			selW = 0; 	selH = 0;
			return;
		}
		
		selW = x - selX0;
		selH = y - selY0;
		
		if(selW != 0 && selH != 0){
			//flip the x/w, y/h if they're drawing it backwards
			int x0 = selX0;
			int y0 = selY0;
			int w = selW;
			int h = selH;
			if(w < 0){
				x0 = x0 + w + 1; 			
				w = -w;
			}
			if(h < 0){
				y0 = y0 + h + 1;
				h = -h;
			}
			if(sourceControl instanceof ImagePipeControllerROISettable)
				((ImagePipeControllerROISettable)sourceControl).setRect(x0, y0, w, h);
			
			for(ImagePipeController sinkController : sinkControllers.values())			
				if(sinkController instanceof ImagePipeControllerROISettable)
					((ImagePipeControllerROISettable)sinkController).setRect(x0, y0, w, h);
		}
	
		swtImageCanvas.redraw();
	}
	
	private void canvasPaintEvent(PaintEvent event){
        Rectangle clientArea = swtImageCanvas.getClientArea();
        Img image = getSelectedImageFromConnectedSource();
                
        if(image != null && swtCurImage != null){
        	double scale = getScale();
        	int imgX0 = (int)((double)event.x / scale);
        	int imgY0 = (int)((double)event.y / scale);
        	int imgW = (int)((double)event.width / scale) + 2;
        	int imgH = (int)((double)event.height / scale) + 2;
        	if((imgX0 + imgW) >= image.getWidth()){
        		imgW = image.getWidth() - imgX0;
        	}
        	if((imgY0 + imgH) >= image.getHeight()){
        		imgH = image.getHeight() - imgY0;
        	}
        	try{
//	        	event.gc.drawImage(swtImage, 0, 0,  image.getWidth(),  image.getHeight(), 
//		        		0, 0, (int)(image.getWidth()*scale), (int)(image.getHeight()*scale));
        		event.gc.drawImage(swtCurImage, imgX0, imgY0, imgW, imgH, 
	        			(int)(imgX0*scale), 
	        			(int)(imgY0*scale), 
	        			(int)(imgW*scale), 
	        			(int)(imgH*scale));
        	}catch(IllegalArgumentException e){
        		System.out.println("DRAW FAILED: " + scale + "\t" + imgX0 +", "+ imgY0+", "+ imgW+", "+ imgH +"\t-->\t" +
    	        		event.x+", "+ event.y+", "+ event.width+", "+ event.height);
        	}        
	        event.gc.setForeground(swtImageCanvas.getDisplay().getSystemColor(SWT.COLOR_BLACK));
	        if(selectStartTime >= 0){
	        	event.gc.drawRectangle(
	        			(int)(selX0 * scale), (int)(selY0 * scale),
	        			(int)(selW * scale), (int)(selH * scale));
	        }else{
	        	event.gc.drawLine(0, (int)(posY * scale), clientArea.width, (int)(posY * scale));
		        event.gc.drawLine((int)(posX * scale), 0, (int)(posX * scale), clientArea.height);
	        }
	        
	        for(ImagePipeController sinkController : sinkControllers.values())
	        	if(sinkController instanceof SWTControllerInfoDraw)
	        		((SWTControllerInfoDraw)sinkController).drawOnImage(event.gc, scale, image.getWidth(), image.getHeight(), false);
	        	        
	        if(sourceControl != null && sourceControl instanceof SWTControllerInfoDraw){
	        	((SWTControllerInfoDraw)sourceControl).drawOnImage(event.gc, scale, image.getWidth(), image.getHeight(), true);
	        }
	    	        
        }else{
        	event.gc.fillRectangle(clientArea);
        	if(image == null){
        		event.gc.drawText("null image", 0, 0);
        	}else if(image.isDestroyed()){
        		event.gc.drawText("Image Destroyed", 0, 0);
        	}else{
        		event.gc.drawText("Image OK but no swtCurImage!?!", 0, 0);	
        	}
        	
        }
        
	}
	
	private void shellClosedEvent(Event event){

		//setSource(null);
		if(connectedSource != null)
			connectedSource.removeSink(this);
	}
	
	@Override
	/** Image has changed. We really need to deal with this since the image has changed in the buffer
	 * and we'll never be able to recreate the old image.
	 * 
	 * This can come in from an unknown thread (not the SWT one)
	 */
	public void imageChanged(int idx) { 
		Img img = connectedSource.getImage(idx);		
		if(img == null){
			System.out.println("wtfs?");
		}
		if(swtShell == null || swtShell.isDisposed() || img == null || img.isDestroyed())
			return;
		
		final int changedIdx = idx;
		
		//we need this to be run by the GUI thread in the end, but we 
		//need it to follow the usual 'once only and at least after last'
		// update rules
		IMSEProc.ensureFinalSWTUpdate(swtShell.getDisplay(), imageChangeSyncObj, new Runnable() {
			@Override
			public void run() {
				if(!swtShell.isDisposed()){
					if(changedIdx != getSelectedSourceIndex() && infoPanel.swtGotoChangedButton.getSelection()){
						setSelectedSourceIndex(changedIdx);						
					}
					if(changedIdx == getSelectedSourceIndex()){
						createSWTImage();
					}
				}
			}
		});
	}
	
	/** Generate the SWT image object from the Img object */
	private void createSWTImage(){
		if(swtShell.isDisposed())
			return;
		
		final Img image = getSelectedImageFromConnectedSource();
				
		if(image == null || image.isDestroyed()){
			doSetImage(null, 300, 300);			
			return;
		}
		
		// queue the image convert
		
		final double colorScale = (double)infoPanel.swtColorLogScale.getSelection() * 0.02;
		final double minCut = (double)infoPanel.swtColorMinSpinner.getSelection() / 1000;
		final double maxCut = (double)infoPanel.swtColorMaxSpinner.getSelection() / 1000;
		final boolean relMax = infoPanel.relativeMinMax.getSelection();
		IMSEProc.ensureFinalUpdate(imageConvertSyncObj, new Runnable() { @Override public void run() { 
			doImageConvert(image, colorScale, minCut, maxCut, relMax); 
		} });
	}
	
	/** Convert the given image to a SWT ImageData (non-SWT thread because it takes a while) */ 
	private void doImageConvert(Img image, double colorScale, double minCut, double maxCut, boolean relativeMinMax){
			
		if(cMap == null){
			 double cMapDbl[][] = ColorMaps.jet(nCols);
			 cMap = new byte[nCols*3];
			 for(int i=0; i < nCols; i++){
				 cMap[i*3+0] = (byte)(cMapDbl[i][0]*255);
				 cMap[i*3+1] = (byte)(cMapDbl[i][1]*255);
				 cMap[i*3+2] = (byte)(cMapDbl[i][2]*255);
			 }
			 
		}
		
		/* This turns out to be slower, meh
		if(vMap == null || colorScale != vMapColourScale){
			//value map - list of 0.0-1.0 values that match each colour entry
			vMap = new double[nCols];
			for(int i=0; i < nCols; i++){
				double x = (double)i / (nCols-1);			
				vMap[i] = FastMath.exp(FastMath.log(x) / colorScale); 
			}
			vMapColourScale = colorScale;
		}*/
		
		PaletteData palette = new PaletteData(0xFF0000, 0xFF00 , 0xFF);		
		
		ImageData swtImageData = null;
		
		try{
			image.startReading();
		}catch(InterruptedException e){
			System.out.println("ImageWindow.createSWTImage() interuptted");
			queueSetImage(null, 300, 300);			
			return;
		}
		try{
		
			if(!image.isRangeValid()){
				System.out.println("ImageWindow: Image " + image + " has invalid range, not actually switching to it.");
				queueSetImage(null, 300, 300);
				return;
			}
			
			int w = image.getWidth();
			int h = image.getHeight();		
			
			swtImageData = new ImageData(w, h, 24, palette);
			
			double min,max;
			if(relativeMinMax){
				min = image.getMin() + (minCut/1000) * (image.getMax() - image.getMin());
				max = image.getMin() + (maxCut/1000) * (image.getMax() - image.getMin());
			}else{
				min = minCut;
				max = maxCut;
			}
			
	        for(int y=0; y < h; y++){
				for(int x=0; x < w; x++){
					double v = image.getPixelValue(x, y);
					if(Double.isNaN(v)){
						swtImageData.data[(y*w + x)*3+0] = (byte)255;
						swtImageData.data[(y*w + x)*3+1] = (byte)255;
						swtImageData.data[(y*w + x)*3+2] = (byte)255;
						continue;
					}
					
					//scale color
					double f = (v - min) / (max - min);
					
					if(colorScale != 1.0)
						f = FastMath.pow(f, colorScale);
					
					int c = (int)((nCols-1) * f);				
					if(c < 0)c=0;
					if(c >= nCols)c=nCols-1;
					//*/
					
					/* This turns out to be slower... meh
					int c2 = OneLiners.getNearestIndex(vMap, f);
					/*if(c2 != c){
						System.out.println(f + "\t" + c + "\t" + c2);
					}//*/
					
					swtImageData.data[(y*w + x)*3+0] = cMap[c*3+0];
					swtImageData.data[(y*w + x)*3+1] = cMap[c*3+1];
					swtImageData.data[(y*w + x)*3+2] = cMap[c*3+2];
				}	
			}
		}finally{
			image.endReading();
		}
        
		if(swtShell.isDisposed())
			return;
		
        Image newSWTImage = new Image(swtShell.getDisplay(), swtImageData);
        
        //the the actual setting need to be done in the SWT thread
        queueSetImage(newSWTImage, image.getWidth(), image.getHeight());
        
	}
	
	private void queueSetImage(final Image newSWTImage, final int w, final int h){
		IMSEProc.ensureFinalSWTUpdate(swtShell.getDisplay(), imageDisplaySyncObj, new Runnable() { @Override public void run() { 
        	doSetImage(newSWTImage, w, h); 
        } });
	}
	
	private void doSetImage(Image swtImage, int imgWidth, int imgHeight){
		if(swtCurImage != null)
			swtCurImage.dispose();
		
		if(swtShell.isDisposed())
			return;
		
		this.swtCurImage = swtImage;
        swtImageCanvas.setSize(
				(int)(imgWidth*getScale()), 
				(int)(imgHeight*getScale()));
		swtImageCanvas.redraw();
		updateInfoPanel();
	}
	
	@Override
	public void notifySourceChanged(){
		
		if(swtShell.isDisposed()) return;
		//we need this to be run by the GUI thread in the end, but we 
		//need it to follow the usual 'once only and at least after last'
		// update rules
		IMSEProc.ensureFinalSWTUpdate(swtShell.getDisplay(), imageSetChangeSyncObj,
					new Runnable() { public void run() { imageIndexChanged(); } });
		
		
	}
	

	private void imageIndexChanged() {
		if(swtShell.isDisposed()) return;
			
		int imageIndex = swtImageIndexSpinner.getSelection();		
		int nImages = (connectedSource != null) ? connectedSource.getNumImages() : 0;
		
		if(nImages <= 0){
			nImages = 0;
		    imageIndex = -1;
			swtImageIndexSpinner.setValues(-1, -1, -1, 0, 1, 1);
		    swtImageIndexSpinner.setEnabled(false);
		    
		}else{
			//if we now have more images that we allowed for before, we have a new one
			if((nImages-1) >  swtImageIndexSpinner.getMaximum() 
					&& (infoPanel.swtAcceptNewButton.getSelection())){
				//don't switch to to it if hasn't finished yet
				Img newImg = connectedSource.getImage(nImages-1);
				if(newImg != null && newImg.isRangeValid())				
					imageIndex = nImages-1;
			}else if(imageIndex < 0){
				imageIndex = 0;
			}
		}
		
		swtImageIndexSpinner.setValues(imageIndex, 0, nImages-1, 0, 1, 10);
		swtImageIndexSpinner.setEnabled(true);

		setSelectedSourceIndex(imageIndex);
		
		
	}
	
	@Override
	public void setSelectedSourceIndex(int index) {
		if(index == selectedSourceIndex)
			return;
		
		super.setSelectedSourceIndex(index);
		
        //if(swtImageIndexSpinner.getSelection() != getSelectedSourceIndex()){
        	swtImageIndexSpinner.setSelection(selectedSourceIndex);
        //}

		//also sync the image index in all the other sinks connected to this source
		if(connectedSource != null)
			for(ImgSink sink : connectedSource.getConnectedSinks())
				sink.setSelectedSourceIndex(selectedSourceIndex);
	}

	@Override
	protected void selectedImageChanged() {
		
		createSWTImage();					
			
		//System.out.println("ImageWindow " + hashCode() + " moved to image " + ((image == null) ? null : image.hashCode()));
		
		updateInfoPanel();
	}
	

	private static final int scaleToSel(double scale){
		return (int)(FastMath.log(scale) / logScaleConst) + 50;
	}
	
	private static final double selToScale(int sel){
		return FastMath.pow(scaleConst, sel - 50);
	};
	
	public double getScale(){ return infoPanel.swtZoomScale.isDisposed() ? 0 : selToScale(infoPanel.swtZoomScale.getSelection()); }	
	
	public void setScale(double scale){
		Img image = getSelectedImageFromConnectedSource();
		
		//SWT won't let us create a canvas with > 64k pixels in either dir
		//  fair enough 
		if(image == null){
			infoPanel.swtZoomScale.setSelection(50);
			return;
		}
			
		if(scale * image.getWidth() > 65535){
			scale = 65535 / image.getWidth();
		}
		if(scale * image.getHeight() > 65535){
			scale = 65535 / image.getHeight();
		}
		infoPanel.swtZoomScale.setSelection(scaleToSel(scale));
	}
	
	public void setSource(ImgSource source){
		
		if(source != connectedSource){			
			
			if(connectedSource != null && sourceControl != null){
				ImagePipeController ctrl = sourceControl;
				sourceControl = null; //don't go round in circles
				ctrl.destroy(); //does the SWT dispose		
			}
			super.setSource(source);
		}
			
		if(connectedSource != null){
			sourceControl = source.createSourceController(Composite.class, new Object[]{ swtTabFoler, SWT.BORDER_SOLID });
        	if(sourceControl != null){
        		sourceControlComposite = (Composite) sourceControl.getInterfacingObject();
        		sourceControlComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false));
        		swtSourceTab.setControl(sourceControlComposite);
    			
	        }
        	//swtSidePaneScrollComp.pack();
        }
		 
		notifySourceChanged();
		
		//also set the source of all sinks for which we have a controller in our window
		for(ImagePipeController sinkController : sinkControllers.values()){
			ImgSink sink = (ImgSink)sinkController.getPipe();			
			sink.setSource(connectedSource);
		}
		
		//set the window icon to the first letter of the source class (for Alt-Tab help)
		
		//go back and find the ID of the root source
		ImgSource rootSource = source;		
		while(rootSource instanceof ImgSink && ((ImgSink)rootSource).getConnectedSource() != null)
			rootSource = ((ImgSink)rootSource).getConnectedSource();
		
		int cols[] = new int[]{
				SWT.COLOR_BLUE,
				SWT.COLOR_RED,
				SWT.COLOR_GREEN,
				SWT.COLOR_BLACK,
		};
		int h = rootSource == null ? 0 : (rootSource.hashCode() % cols.length);
		int col = cols[h];
		
		Image icon = new Image(swtShell.getDisplay(), 16, 16);
       
		GC gc = new GC(icon); 
	    
	    Font font = new Font(swtShell.getDisplay(), "Arial", 8, SWT.BOLD | SWT.ITALIC); 
		gc.setForeground(swtShell.getDisplay().getSystemColor(col)); 
		gc.setFont(font); 
		gc.drawText((source == null) ? "--" : source.getClass().getSimpleName().substring(0, 2), 0, 0); 
		font.dispose();
		
		swtShell.setImage(icon);
		
		infoPanel.repopulatePipeTree();
	}
	
	public Point getScrollOrigin() { return swtImgScrollComp.getOrigin();	}

	public void bringToFront() { if(!swtShell.isDisposed()) swtShell.forceActive(); }

	@Override
	public String toString() {
		return "ImageWindow[src=" + connectedSource.toShortString() + "]";
	}

	public Image getSWTImage() { return swtCurImage; }

	public boolean isIdle() { return false; };
}
