package imseProc.dacControl;

import java.awt.Color;

import org.eclipse.swt.SWT;
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.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.experimental.chart.swt.ChartComposite;

import comedi.ComediDataView;
import comedi.ComediRawDataStore;
import comedi.ComediRawWriteBuffer;
import comedi.DataViewChangedNotification;

import simpleScope.DoubleBufferDataset;

import imseProc.core.IMSEProc;
import imseProc.core.ImagePipeController;
import imseProc.core.ImgPipe;
import imseProc.graph.JFreeChartGraph;


/** 
 * @author oliford 
 */
public class DACTimingSWTController  implements ImagePipeController {

	private Group swtGroup;
	
	private Label statusLabel;
	private Spinner nFramesSpinner;
	private Spinner endDelaySpinner;
	private Spinner exposePeriodSpinner;
	private Spinner clockPeriodSpinner;	
	private Button startTriggerCheckbox;
	
	private Group stepperSettings;
	private Spinner frameStepperSelectSpinner;
	private Spinner nStepsPerFrameSpinner;
	private Spinner stepperPeriodSpinner;
	private Button frameStepperDirButton;

	private Combo flcModeCombo;
	
	private Spinner nCyclesSpinner;
	private Spinner cycleStepperSelectSpinner;
	private Spinner nStepsPerCycleSpinner;
	private Button cycleStepperDirButton;
	private Button alternateFrameStepDirButton;

	private Button startButton;
	private Button setCameraTimeButton;
	private Button armTriggerButton;
	private Button stopButton;
	
	private DACOfflineSWTControl offlineControl;
	
	private DACTimingHanger proc;
	
	private ComediDataView dataView;
	private DoubleBufferDataset dataset;
	private JFreeChartGraph graph;
	
	public DACTimingSWTController(DACTimingHanger proc, Composite parent, int style) {
		this.proc = proc;
		
		swtGroup = new Group(parent, style);
		swtGroup.setText("DAQ Output");
        swtGroup.setLayout(new GridLayout(4, false));
        
        Label lS = new Label(swtGroup, SWT.NONE); lS.setText("Status:");
        statusLabel = new Label(swtGroup, SWT.NONE);
        statusLabel.setText("Init, Init, 0/0\nFrame = ?ms, Run = ?ms");
        statusLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 3, 1));
        
        Label lNF = new Label(swtGroup, SWT.NONE); lNF.setText("Num Frames:");
        nFramesSpinner = new Spinner(swtGroup, SWT.NONE);
        nFramesSpinner.setValues(500, 0, Integer.MAX_VALUE, 0, 10000, 100000);
        nFramesSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        nFramesSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });     
    	
        Label lEP = new Label(swtGroup, SWT.NONE); lEP.setText("Expose / ms:");
     	exposePeriodSpinner = new Spinner(swtGroup, SWT.NONE);
    	exposePeriodSpinner.setValues(100000, 0, Integer.MAX_VALUE, 3, 10000, 100000);
    	exposePeriodSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
    	exposePeriodSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   

        Label lED = new Label(swtGroup, SWT.NONE); lED.setText("End Delay / ms:");
    	endDelaySpinner = new Spinner(swtGroup, SWT.NONE);
    	endDelaySpinner.setValues(0, 0, Integer.MAX_VALUE, 3, 10000, 100000);
    	endDelaySpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
    	endDelaySpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   

    	Label lCP = new Label(swtGroup, SWT.NONE); lCP.setText("Clock Period / µs:");
     	clockPeriodSpinner = new Spinner(swtGroup, SWT.NONE);
     	clockPeriodSpinner.setValues(50, 0, Integer.MAX_VALUE, 0, 10, 100);
     	clockPeriodSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
     	clockPeriodSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   

        Label lF = new Label(swtGroup, SWT.NONE); lF.setText("FLC:");
        flcModeCombo = new Combo(swtGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
        flcModeCombo.setItems(new String[]{ "Low", "High", "Interlace" });
        flcModeCombo.select(0);
        flcModeCombo.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        flcModeCombo.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });

        startTriggerCheckbox = new Button(swtGroup, SWT.CHECK);
        startTriggerCheckbox.setText("H/W Start Trigger");
        startTriggerCheckbox.setSelection(false);
        startTriggerCheckbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
        
     	stepperSettings = new Group(swtGroup, SWT.BORDER_SOLID);
    	stepperSettings.setText("Steppers");
        stepperSettings.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
        stepperSettings.setLayout(new GridLayout(4, false));        
    	
        Label lFS = new Label(stepperSettings, SWT.NONE); lFS.setText("Frame Stepper:");
        lFS.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
        
        
    	Label lSS = new Label(stepperSettings, SWT.NONE); lSS.setText("Stepper:");
    	frameStepperSelectSpinner = new Spinner(stepperSettings, SWT.NONE);
    	frameStepperSelectSpinner.setValues(0, 0, 1, 0, 1, 1);
    	frameStepperSelectSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
    	frameStepperSelectSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   
    	
    	Label lNS = new Label(stepperSettings, SWT.NONE); lNS.setText("Num Steps:");
    	nStepsPerFrameSpinner = new Spinner(stepperSettings, SWT.NONE);
        nStepsPerFrameSpinner.setValues(200, 0, Integer.MAX_VALUE, 0, 1, 10);
        nStepsPerFrameSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        nStepsPerFrameSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   
    	
        Label lSP = new Label(stepperSettings, SWT.NONE); lSP.setText("Step / μs:");
    	stepperPeriodSpinner = new Spinner(stepperSettings, SWT.NONE);
    	stepperPeriodSpinner.setValues(1500, 0, Integer.MAX_VALUE, 0, 1, 1000);
    	stepperPeriodSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
    	stepperPeriodSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   
    	    	    	
        frameStepperDirButton = new Button(stepperSettings, SWT.CHECK);
        frameStepperDirButton.setText("Reverse");
        frameStepperDirButton.setSelection(false);
        frameStepperDirButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
        
        Label lCSl = new Label(stepperSettings, SWT.NONE); lCSl.setText("Cycle Steper:");
        lCSl.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
        
        Label lCS = new Label(stepperSettings, SWT.NONE); lCS.setText("Stepper:");
        cycleStepperSelectSpinner = new Spinner(stepperSettings, SWT.NONE);
    	cycleStepperSelectSpinner.setValues(1, 0, 1, 0, 1, 1);
    	cycleStepperSelectSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
    	cycleStepperSelectSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   

    	Label lCNS = new Label(stepperSettings, SWT.NONE); lCNS.setText("Num Steps:");
    	nStepsPerCycleSpinner = new Spinner(stepperSettings, SWT.NONE);
    	nStepsPerCycleSpinner.setValues(200, 0, Integer.MAX_VALUE, 0, 1, 10);
        nStepsPerCycleSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        nStepsPerCycleSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });   

    	Label lNC = new Label(stepperSettings, SWT.NONE); lNC.setText("nCycles:");
        nCyclesSpinner = new Spinner(stepperSettings, SWT.NONE);
        nCyclesSpinner.setValues(1, 0, Integer.MAX_VALUE, 0, 10000, 100000);
        nCyclesSpinner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        nCyclesSpinner.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { dataChangingEvent(event); } });     
    	    	
        cycleStepperDirButton = new Button(stepperSettings, SWT.CHECK);
        cycleStepperDirButton.setText("Reverse");
        cycleStepperDirButton.setSelection(false);
        cycleStepperDirButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        
        alternateFrameStepDirButton = new Button(stepperSettings, SWT.CHECK);
        alternateFrameStepDirButton.setText("Flip Frame dir");
        alternateFrameStepDirButton.setSelection(false);
        alternateFrameStepDirButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        
        stepperSettings.pack();
        
        startButton = new Button(swtGroup, SWT.PUSH);
        startButton.setText("Start");
        startButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { startButtonEvent(event); } });        
        startButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        
        setCameraTimeButton = new Button(swtGroup, SWT.PUSH);
        setCameraTimeButton.setText("Get Cam Time");
        setCameraTimeButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { getCamTimeButtonEvent(event); } });        
        setCameraTimeButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        
        armTriggerButton = new Button(swtGroup, SWT.PUSH);
        armTriggerButton.setText("Arm Trigger");
        armTriggerButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { armButtonEvent(event); } });        
        armTriggerButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
                
        stopButton = new Button(swtGroup, SWT.PUSH);
        stopButton.setText("Abort");
        stopButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { stopButtonEvent(event); } });        
        stopButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
        
        dataset = new DoubleBufferDataset("ADCDataSet");
        
        graph = new JFreeChartGraph(swtGroup, SWT.NONE, dataset);
        //graph.setData(new double[0], new double[0]);
        graph.getComposite().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 8, 1));
        
        offlineControl = new DACOfflineSWTControl(swtGroup);
        offlineControl.swtGroup.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 8, 1));
        
        swtGroup.pack();
	}
	
	private void dataChangingEvent(Event event){
		updateGUI();
	}
	
	private void startButtonEvent(Event event) {
		proc.setConfig(guiToConfig());
		proc.start();
	}
	
	private void armButtonEvent(Event event) {
		proc.setConfig(guiToConfig());
		proc.armTrigger();
	}
	
	private void stopButtonEvent(Event event) {
		proc.abort();
	}
	
	private void getCamTimeButtonEvent(Event event) {
		double camTimeMS = proc.getCameraTotalTimeMS();
		
		exposePeriodSpinner.setValues((int)(camTimeMS * 1000), 0, Integer.MAX_VALUE, 3, 10000, 100000);
		
	}
		
	private DACTimingConfig guiToConfig(){
		DACTimingConfig config = new DACTimingConfig();
		
		long endDelayNS = (long)endDelaySpinner.getSelection() * 1000; //us --> ns
				
		config.nFramesPerCycle = nFramesSpinner.getSelection();  
		config.exposeHighPeriodNS = ((long)exposePeriodSpinner.getSelection() * 1000) / 10; //us --> ns and 50% duty...
		config.exposeLowPeriodNS = config.exposeHighPeriodNS * 9; //since the camera does it's own exposure timing
		config.frameStepperSelect = frameStepperSelectSpinner.getSelection();
		config.nStepsPerFrame = nStepsPerFrameSpinner.getSelection();
		config.stepperPeriodNS = stepperPeriodSpinner.getSelection() * 1000; //us --> ns
		config.flcMode = flcModeCombo.getSelectionIndex();
		//config.clockPeriodNS = Math.min(config.exposeHighPeriodNS, config.stepperPeriodNS) / 5;
		config.clockPeriodNS = clockPeriodSpinner.getSelection() * 1000;
		
		config.framePeriodNS = (config.exposeLowPeriodNS + config.exposeHighPeriodNS)
									* ((config.flcMode == config.FLC_INTERLACE) ? 2 : 1) +
								((long)config.stepperPeriodNS * (long)config.nStepsPerFrame) +
								endDelayNS;
		
		config.frameStepReverse = frameStepperDirButton.getSelection();
		
		
		config.nCycles = nCyclesSpinner.getSelection();  
		config.cycleStepperSelect = cycleStepperSelectSpinner.getSelection();
		config.nStepsPerCycle = nStepsPerCycleSpinner.getSelection();
		config.cycleStepReverse = cycleStepperDirButton.getSelection();
		config.alternateFrameStepDir = alternateFrameStepDirButton.getSelection();
		
		config.trigSourcePFI = startTriggerCheckbox.getSelection() ? 1 : -1;

		return config;
	}
	
		

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

	@Override
	public void generalControllerUpdate() {
		ComediRawDataStore rawStore = proc.getStore();
		if(rawStore != null && (dataView == null || dataView.getRawStore() != rawStore)){
			dataView = new ComediDataView(rawStore, null);		
			dataView.setRestrictions(0, 5000, -1, 1);
			dataView.forceUpdate();
			
			int nC = dataView.getNChans();
			int x0 = dataView.getX0();
			int x1 = dataView.getX1();
			int dx = dataView.getDX();
			
			dataset.setData(dataView.getBuffer(), nC, x0, x1, dx);
			
			updateGUI();
		}
	}

	public void updateGUI() {
		IMSEProc.ensureFinalSWTUpdate(swtGroup.getDisplay(), this, new Runnable() { @Override public void run() { doGraphGUI(); } });
	}
	
	private void doGraphGUI() {
		ComediRawDataStore rawStore = proc.getStore();
		DACTimingConfig config = guiToConfig();
		
		
		long cyclePeriodNS = config.framePeriodNS * config.nFramesPerCycle
								+ config.stepperPeriodNS * config.nStepsPerCycle;
		
		String statusText = 
				((rawStore != null) ? "Data ready, " : "No data, ") +
				(proc.isActive() ? "Active, " : "Inactive, ") + 
				((rawStore != null) ? (rawStore.nextScanNum + " / " + 
					(((ComediRawWriteBuffer)rawStore).maxCycles * rawStore.buffLength)) : "- / -") +
				"\nFrame = " + ((double)config.framePeriodNS / 1e6) + " ms, " +
				"Cycle = " + ((double)cyclePeriodNS / 1e6) + " ms, " +
				"Run = " + (config.nFramesPerCycle * config.nCycles) + " frames = " 
					+ ((double)cyclePeriodNS * config.nCycles / 1e9) + " s";
		
		statusLabel.setText(statusText);

		//for some reason the colousr don't stay set from the init
		graph.setColours(new Color[]{
				Color.BLACK, // CH_CAM_TRIG
				Color.RED, // CH_FLC_DRIVE
				
				Color.BLUE, // CH_STEP[0]
				Color.MAGENTA, // CH_DIR[0]
				Color.GRAY, // CH_SWITCH[0]
				
				Color.GREEN, // CH_STEP[1]
				Color.CYAN, // CH_DIR[1]
				Color.GRAY, // CH_SWITCH[1]				
			});
				
		dataset.updateGraph();
	}
	
	
	@Override
	public void destroy() {
		proc.destroy();
	}

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

	
}
