package imseProc.sensicam;

import java.util.HashMap;

import jafama.FastMath;


import oneLiners.OneLiners;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;

import sensicamJNI.CamTypes;
import sensicamJNI.SencamDef;

import imseProc.core.IMSEProc;
import imseProc.core.ImagePipeController;
import imseProc.core.ImgPipe;
import imseProc.core.ImgSource;

public class SensicamSWTControl implements ImagePipeController {
	public static final double log2 = FastMath.log(2);
	
	private static final int exposureSubModes[] = new int[]{
		CamTypes.NORMALLONG,
		CamTypes.VIDEO, 
		CamTypes.MECHSHUT, 
		CamTypes.MECHSHUTV, 
		CamTypes.QE_FAST, 
		CamTypes.QE_DOUBLE
	};
	
	private static final int triggerModes[][] = new int[][]{
		{ SencamDef.TRIG_SEQ_AUTO, SencamDef.TRIG_SEQ_RISING, SencamDef.TRIG_SEQ_FALLING },
		{ SencamDef.TRIG_FRAME_AUTO, SencamDef.TRIG_FRAME_RISING, SencamDef.TRIG_FRAME_FALLING }			
	};
	private static final String triggerModeNames[] = new String[]{ "Auto", "Rising", "Falling" };

	private SensicamSource source;
	private Group swtGroup;
	
	private Label sourceStatusLabel;
	private Label captureStatusLabel;
	private Label ccdInfoLabel;
	private Label timingInfoLabel;
	
	private Button continuousButton;
	private Button blackLevelButton;
	private Spinner numImagesSpinner;
	private Spinner numKBuffersSpinner;
	private Combo exposureMode;
	private Combo gainMode;
	private Combo seqTriggerMode;
	private Combo frameTriggerMode;	
	private Button autoExposure;
		
	private Spinner delayLengthSpinner;
	private Spinner exposureLengthSpinner;
	private Spinner binningXSpinner, binningYSpinner;
	private Spinner roiX0Text, roiY0Text, roiWText, roiHText;
	
	private Button triggerEnableCheckbox;
	private Button startCaptureButton;
	private Button abortCaptureButton;
	private Button releaseMemoryButton;

	private int oldBinX, oldBinY;
		
	public SensicamSWTControl(Composite parent, int style, SensicamSource source) {
		this.source = source;
		
		swtGroup = new Group(parent, style);
		swtGroup.setText("Sensicam Control");
		swtGroup.setLayout(new GridLayout(5, false));
		
		Label lSS = new Label(swtGroup, SWT.NONE); lSS.setText("Src:");
		sourceStatusLabel = new Label(swtGroup, SWT.NONE);
		sourceStatusLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
		
		Label lCS = new Label(swtGroup, SWT.NONE); lCS.setText("Capt:");
		captureStatusLabel = new Label(swtGroup, SWT.NONE);
		captureStatusLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
		
		Label lCI = new Label(swtGroup, SWT.NONE); lCI.setText("Cam:");
		ccdInfoLabel = new Label(swtGroup, SWT.NONE);
		ccdInfoLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
		
		Label lTI = new Label(swtGroup, SWT.NONE); lTI.setText("Times:");
		timingInfoLabel = new Label(swtGroup, SWT.NONE);
		timingInfoLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
		
		Label lNI = new Label(swtGroup, SWT.NONE); lNI.setText("Imgs:");
		numImagesSpinner = new Spinner(swtGroup, SWT.NONE);
		numImagesSpinner.setValues(1, 1, 9999, 0, 1, 10);
		numImagesSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		
		continuousButton = new Button(swtGroup, SWT.CHECK);
		continuousButton.setText("live");
		continuousButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 1, 1));
		continuousButton.setToolTipText("Live view: Keep reading images, wrapping");
		
		blackLevelButton = new Button(swtGroup, SWT.CHECK);
		blackLevelButton.setText("Black level");
		blackLevelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 1, 1));
		
		Label lEM = new Label(swtGroup, SWT.NONE); lEM.setText("Exp.:");
		exposureMode = new Combo(swtGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
		for(Integer subMode : exposureSubModes)
			exposureMode.add(CamTypes.exposureModeToString(CamTypes.M_LONG, subMode));
		exposureMode.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 3, 1));
				
		autoExposure = new Button(swtGroup, SWT.CHECK);
		autoExposure.setText("Auto adjust");
		autoExposure.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		autoExposure.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { autoExposureModifyEvent(event); } });
		
		
		Label lGM = new Label(swtGroup, SWT.NONE); lGM.setText("Gain:");
		gainMode = new Combo(swtGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
		gainMode.setItems(new String[]{ "Normal", "Extended", "Low Light"});
		gainMode.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false, 2, 1));
		gainMode.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { liveModifyEvent(event); } });
		
		Label lNB = new Label(swtGroup, SWT.NONE); lNB.setText("Bufs:");
		numKBuffersSpinner = new Spinner(swtGroup, SWT.NONE);
		numKBuffersSpinner.setValues(1, 1, 999, 0, 1, 10);
		numKBuffersSpinner.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 1, 1));		
		
		Label lHWT = new Label(swtGroup, SWT.NONE); lHWT.setText("H/W Trigger:");
		
		Label lST = new Label(swtGroup, SWT.NONE); lST.setText("Seq:");
		seqTriggerMode = new Combo(swtGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
		seqTriggerMode.setItems(triggerModeNames);
		seqTriggerMode.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		
		Label lFT = new Label(swtGroup, SWT.NONE); lFT.setText("Frame:");
		frameTriggerMode = new Combo(swtGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
		frameTriggerMode.setItems(triggerModeNames);
		frameTriggerMode.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
				
		Label lT = new Label(swtGroup, SWT.NONE); lT.setText("Times:");
		Label lDT = new Label(swtGroup, SWT.NONE); lDT.setText("Dly:");
		delayLengthSpinner = new Spinner(swtGroup, SWT.NONE);
		delayLengthSpinner.setValues(1, 0, 100000, 0, 1, 1);
		delayLengthSpinner.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 1, 1));
		delayLengthSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { liveModifyEvent(event); } });
		
		Label lET = new Label(swtGroup, SWT.NONE); lET.setText("Exp:");
		exposureLengthSpinner = new Spinner(swtGroup, SWT.NONE);
		exposureLengthSpinner.setValues(1, 1, 100000, 0, 1, 10);
		exposureLengthSpinner.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 1, 1));
		exposureLengthSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { liveModifyEvent(event); } });
		
		Label lB = new Label(swtGroup, SWT.NONE); lB.setText("Bining:");
		Label lBX = new Label(swtGroup, SWT.NONE); lBX.setText("X:");
		binningXSpinner = new Spinner(swtGroup, SWT.NONE);
		binningXSpinner.setValues(1, 1, 256, 0, 1, 10);
		binningXSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		binningXSpinner.addListener(SWT.Modify, new Listener() { @Override public void handleEvent(Event event) { binningModifyEvent(event); } });
		Label lBY = new Label(swtGroup, SWT.NONE); lBY.setText("Y:");
		binningYSpinner = new Spinner(swtGroup, SWT.NONE);
		binningYSpinner.setValues(1, 1, 256, 0, 1, 10);
		binningYSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		binningYSpinner.addListener(SWT.Modify, new Listener() { @Override public void handleEvent(Event event) { binningModifyEvent(event); } });
		oldBinX = 1; oldBinY = 1;
		
		
		Label lR = new Label(swtGroup, SWT.NONE); lR.setText("ROI:");
		Label lRX = new Label(swtGroup, SWT.NONE); lRX.setText("X:");
		roiX0Text = new Spinner(swtGroup, SWT.NONE);
		roiX0Text.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		roiX0Text.setValues(0, 0, 9999, 0, 1, 1);
		roiX0Text.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { liveModifyEvent(event); } });
		
		Label lRY = new Label(swtGroup, SWT.NONE); lRY.setText("Y:");
		roiY0Text = new Spinner(swtGroup, SWT.NONE);
		roiY0Text.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		roiY0Text.setValues(0, 0, 9999, 0, 1, 1);
		roiY0Text.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { liveModifyEvent(event); } });
		
		
		Label lR2 = new Label(swtGroup, SWT.NONE); lR2.setText("");
		Label lRW = new Label(swtGroup, SWT.NONE); lRW.setText("W:");
		roiWText = new Spinner(swtGroup, SWT.NONE);
		roiWText.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		roiWText.setTextLimit(4);
		roiWText.setValues(0, 0, 9999, 0, 1, 1);
		
		
		Label lRH = new Label(swtGroup, SWT.NONE); lRH.setText("H:");
		roiHText = new Spinner(swtGroup, SWT.NONE);
		roiHText.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		roiHText.setTextLimit(4);
		roiHText.setValues(0, 0, 9999, 0, 1, 1);
		
//*/
		startCaptureButton = new Button(swtGroup, SWT.PUSH);
		startCaptureButton.setText("Capture");
		startCaptureButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		startCaptureButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { startCaptureButtonEvent(event); } });

		triggerEnableCheckbox = new Button(swtGroup, SWT.CHECK);
		triggerEnableCheckbox.setText("S/W Trigger");
		triggerEnableCheckbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		triggerEnableCheckbox.setSelection(false);
		triggerEnableCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { armTriggerEvent(event); } });
		
		releaseMemoryButton = new Button(swtGroup, SWT.PUSH);
		releaseMemoryButton.setText("Release Mem");
		releaseMemoryButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		releaseMemoryButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { releaseMemoryButtonEvent(event); } });

		abortCaptureButton = new Button(swtGroup, SWT.PUSH);
		abortCaptureButton.setText("Abort");
		abortCaptureButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		abortCaptureButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { abortCaptureButtonEvent(event); } });
		

		generalControllerUpdate();
		configToGUI();
	}
	
	private void binningModifyEvent(Event event) {
		int newX = binningXSpinner.getSelection();
		if(newX != oldBinX){
			if(newX > oldBinX){
				newX = (oldBinX == 1) ? 2 : oldBinX * 2; 
			}else{				
				newX = oldBinX / 2;
			}
			binningXSpinner.setSelection(newX);
			oldBinX = newX;
		}
		
		int newY = binningYSpinner.getSelection();
		if(newY != oldBinY){			
			if(newY > oldBinY){
				newY = (oldBinY == 1) ? 2 : oldBinY * 2; 
			}else{				
				newY = oldBinY / 2;
			}
			binningYSpinner.setSelection(newY);
			oldBinY = newY;
		}
	}
	
	private void autoExposureModifyEvent(Event event){
		source.setAutoExposure(autoExposure.getSelection());		
	}
		
	private void liveModifyEvent(Event event){
		if(source.isCapturing())
			source.modifyConfig(guiToConfig());
	}

	private void armTriggerEvent(Event event){
		if(triggerEnableCheckbox.getSelection()){
			SensicamConfig cfg = guiToConfig();
			source.setAutoExposure(autoExposure.getSelection());		
			source.armTrigger(cfg);
		}else{
			source.armTrigger(null);
		}
	}
		
	private void startCaptureButtonEvent(Event event){
		SensicamConfig cfg = guiToConfig();
		source.setAutoExposure(autoExposure.getSelection());	
		source.startCapture(cfg);
	}
	
	private void abortCaptureButtonEvent(Event event){
		source.abortCapture();
		doUpdate(); //force an update, so the user can keep clicking until the driver stops being active
	}

	private void releaseMemoryButtonEvent(Event event){
		source.releaseMemory();
	}
		
	/*private void resetSettingsButtonEvent(Event event){
		configToGUI();
		autoExposure.setSelection(source.getAutoExposure());		
	}*/
	
	private void configToGUI(){
		SensicamConfig config = source.getConfig();
		//exposureSubModes[i]	

		numImagesSpinner.setSelection(config.nImagesToCapture);
		numKBuffersSpinner.setSelection(config.nKernelBuffers);
		int expModeSel = -1;
		for(int i=0; i < exposureSubModes.length; i++){
			if(exposureSubModes[i] == config.exposureSubMode){
				expModeSel = i; break;
			}
		}
		exposureMode.select(expModeSel);
		seqTriggerMode.select((config.triggerMode & 0x100) / 0x100);
		frameTriggerMode.select(config.triggerMode & 0x001);
		 
		gainMode.select(config.gainMode);
		continuousButton.setSelection(config.wrap != SensicamConfig.WRAP_NONE); 
		if(config.wrap != SensicamConfig.WRAP_NONE)
			blackLevelButton.setSelection(config.wrap == SensicamConfig.WRAP_NOTFIRST);
		roiX0Text.setSelection(config.roiX0); 
		roiY0Text.setSelection(config.roiY0); 
		roiWText.setSelection(config.roiW);
		roiHText.setSelection(config.roiH); 
		binningXSpinner.setSelection(config.binX); 
		binningYSpinner.setSelection(config.binY);
		setGUIExposureBox(config);
		
	}
	
	private void setGUIExposureBox(SensicamConfig config){

		if(config.exposureSubMode == CamTypes.QE_FAST){
			delayLengthSpinner.setSelection(config.delayNS);		
			exposureLengthSpinner.setSelection(config.exposureNS);			
		}else{
			delayLengthSpinner.setSelection(config.delayMS);
			exposureLengthSpinner.setSelection(config.exposureMS);			
		}
	}
	
	private SensicamConfig guiToConfig(){
		SensicamConfig config = new SensicamConfig();
		
		//leave these to the default
		//config.boardNo = 0; //meh
		//config.logFile = logFile;
		//config.enableSyslog = enableSyslog;
		//config.captureTimeout = captureTimeout;
		
		config.nImagesToCapture = numImagesSpinner.getSelection();
		config.nKernelBuffers = numKBuffersSpinner.getSelection();
		config.exposureType = CamTypes.M_LONG;
		int expModeSel = exposureMode.getSelectionIndex();
		config.exposureSubMode = (expModeSel >= 0 && expModeSel < exposureSubModes.length) 
										? exposureSubModes[expModeSel] : -1;
		config.gainMode = gainMode.getSelectionIndex();
		config.wrap = !continuousButton.getSelection() ? SensicamConfig.WRAP_NONE :
						(blackLevelButton.getSelection() ? SensicamConfig.WRAP_NOTFIRST : SensicamConfig.WRAP_ALL);
		
		config.triggerMode = triggerModes[0][seqTriggerMode.getSelectionIndex()] |
								triggerModes[1][frameTriggerMode.getSelectionIndex()];
		
		config.roiX0 = roiX0Text.getSelection();			
		config.roiY0 = roiY0Text.getSelection();
		config.roiW = roiWText.getSelection();
		config.roiH = roiHText.getSelection();
		config.binX = binningXSpinner.getSelection();
		config.binY = binningYSpinner.getSelection();
		
		if(config.exposureSubMode == CamTypes.QE_FAST){
			config.delayNS = delayLengthSpinner.getSelection();		
			config.exposureNS = exposureLengthSpinner.getSelection();
			config.delayMS = -1;
			config.exposureMS = -1;
		}else{
			config.delayMS = delayLengthSpinner.getSelection();
			config.exposureMS = exposureLengthSpinner.getSelection();
			config.delayNS = -1;		
			config.exposureNS = -1;
		}
		
		
		return config;
	}
	
	public ImgSource getSource() { return source;	}

	@Override
	public Object getInterfacingObject() { return swtGroup;	}

	@Override
	public void generalControllerUpdate() {
		if(swtGroup.isDisposed())
			return;
		IMSEProc.ensureFinalSWTUpdate(swtGroup.getDisplay(), this, new Runnable() { @Override public void run() { doUpdate(); } });
	}
	
	private void doUpdate() {
		if(swtGroup.isDisposed())
				return;
	
		sourceStatusLabel.setText(source.getSourceStatus());
		captureStatusLabel.setText(source.getCaptureStatus());
		ccdInfoLabel.setText(source.getCCDInfo());
		
		boolean active = source.isCapturing();
		
		startCaptureButton.setEnabled(!active);
		//abortCaptureButton.setEnabled(active);

		continuousButton.setEnabled(!active);
		numImagesSpinner.setEnabled(!active);
		numKBuffersSpinner.setEnabled(!active);
		//exposureMode.setEnabled(!active);
		//gainMode.setEnabled(!active);
			
		//delayLengthSpinner.setEnabled(!active);
		//exposureLengthSpinner.setEnabled(!active);
		binningXSpinner.setEnabled(!active);
		binningYSpinner.setEnabled(!active);
		//roiX0Text.setEnabled(!active);
		//roiY0Text.setEnabled(!active);
		roiWText.setEnabled(!active);
		roiHText.setEnabled(!active);
		
		if(source.isCapturing() && source.getAutoExposure()){
			setGUIExposureBox(source.getConfig());
		}
		
		Number num;
		num = (Number)source.getSeriesMetaData("totalFrameTimeMS"); double totalTimeMS = (num == null) ? 0 : num.doubleValue(); 
		num = (Number)source.getSeriesMetaData("Sensicam/values/readtime"); double readTimeMS = (num == null) ? 0 : num.doubleValue();
		num = (Number)source.getSeriesMetaData("Sensicam/values/coctime"); double coctime = (num == null) ? 0 : num.doubleValue()/1000;
		num = (Number)source.getSeriesMetaData("Sensicam/values/beltime"); double beltime = (num == null) ? 0 : num.doubleValue()/1000;
		num = (Number)source.getSeriesMetaData("Sensicam/values/exptime"); double exptime = (num == null) ? 0 : num.doubleValue()/1000;
		num = (Number)source.getSeriesMetaData("Sensicam/values/deltime"); double deltime = (num == null) ? 0 : num.doubleValue()/1000;
		
		
		timingInfoLabel.setText(totalTimeMS + " [read=" + readTimeMS + ", coc=" + coctime + ", bel=" + beltime + ", exp=" + exptime + ", del=" + deltime + "]");

	}
	
	@Override
	public void destroy() {
		source.controllerDestroyed(this);
		swtGroup.dispose();
	}

	@Override
	public ImgPipe getPipe() { return source; }
}
