package imseProc.dacControl;

import java.util.concurrent.locks.LockSupport;

import oneLiners.OneLiners;
import otherSupport.SettingsManager;
import comediJNI.Comedi;
import comediJNI.ComediDef;
import comediJNI.ComediJNIException;

/** Direct driving of the controllers via DIO through Comedi
 * using the CPU intensive synchronous writing. 
 * 
 * Stepper motor
 * FLC
 * Camera
 *  
 * @author oliford *
 */
 
public class OfflineDACDrive implements Runnable {
	static { new SettingsManager("minerva", true); 	}

	
	public static void main(String[] args) {
		drive(3, -1, true, 1500000, true);
	}
	
	
	private Thread thread;
	private boolean death = false;
	
	private OfflineDACDrive(){ }
	
	/** Use the DIO via comedi to drive the motor until the switch is hit (i.e. send it home)  
	 * Performed in a high priority thread (to set the timing close-ish) which can be killed by calling abort() */
	public OfflineDACDrive(int stepperSelect, int nSteps, boolean homeStop, int periodNS, boolean reverse) {
		this.stepperSelect = stepperSelect;
		this.nSteps = nSteps;
		this.periodNS = periodNS;
		this.reverse = reverse;
		this.homeStop = homeStop;
		
		thread = new Thread(this);
		thread.setPriority(Thread.MAX_PRIORITY);
		thread.start();
	}
	
	public void abort() {
		this.death = true;
		if(thread != null)
			LockSupport.unpark(thread);
	}
	
	/** Use the DIO via comedi to drive the motor until the switch is hit (i.e. send it home)
	 * Performed in the current thread, so blocks until complete.
	
	 * @param stepperSelect 0 or 1, or 3 for both
	 * @param periodNS
	 * @param reverse
	 */
	public static void drive(int stepperSelect, int nSteps, boolean homeStop, int periodNS, boolean reverse){
		OfflineDACDrive drive = new OfflineDACDrive();
		drive.stepperSelect = stepperSelect;
		drive.nSteps = nSteps;
		drive.periodNS = periodNS;
		drive.reverse = reverse;
		drive.homeStop = homeStop;
		drive.run(); //call directly
	}
	
	private int stepperSelect;
	private int nSteps;
	private int periodNS;
	private boolean reverse;
	private boolean homeStop;

	public void run() {
		
		String comediDev = SettingsManager.defaultGlobal().getProperty("comedi.device", "/dev/comedi0");
		int subdev = OneLiners.mustParseInt(SettingsManager.defaultGlobal().getProperty("comedi.subdevice.dio", "2"));
		
		long dev = Comedi.open(comediDev + "_subd" + subdev);
		if(dev == 0)
			throw new ComediJNIException("comedi_open");
		
		for(int i=0; i < 2; i++){
			int ret = Comedi.dio_config(dev, subdev, DACTimingConfig.CH_STEP[i], ComediDef.COMEDI_OUTPUT);
			if(ret < 0)
				throw new ComediJNIException("dio_config(step)");
			ret = Comedi.dio_config(dev, subdev, DACTimingConfig.CH_DIRECTION[i], ComediDef.COMEDI_OUTPUT);
			if(ret < 0)
				throw new ComediJNIException("dio_config(dir)");
			ret = Comedi.dio_config(dev, subdev, DACTimingConfig.CH_SWITCH[i], ComediDef.COMEDI_INPUT);
			if(ret < 0)
				throw new ComediJNIException("dio_config(switch)");
			
			ret = Comedi.data_write(dev, subdev, DACTimingConfig.CH_DIRECTION[i], 0, 0, reverse ? 0 : 1);
			if(ret < 0)
				throw new ComediJNIException("data_write");
		}
		
		boolean allActiveHome;
		int stepsDone = 0;
		do{
			allActiveHome = true;
			
			int retArr[] = Comedi.data_read(dev, subdev, DACTimingConfig.CH_SWITCH[0], 0, 0);
			boolean isHome0 = (retArr[1] == 0);
			
			retArr = Comedi.data_read(dev, subdev, DACTimingConfig.CH_SWITCH[1], 0, 0);
			boolean isHome1 = (retArr[1] == 0);
			
			for(int i=0; i < 2; i++){
				
				if((!homeStop || !isHome0) && (stepperSelect == 0 || stepperSelect == 3)){
					Comedi.data_write(dev, subdev, DACTimingConfig.CH_STEP[0], 0, 0, i);				
					
					allActiveHome = false;				
				}
				
				if((!homeStop || !isHome1) && (stepperSelect == 1 || stepperSelect == 3)){
					Comedi.data_write(dev, subdev, DACTimingConfig.CH_STEP[1], 0, 0, i);				
					
					allActiveHome = false;
				}
				
				nanoSleepAtLeast(periodNS/2);
				
			}
			
			stepsDone++;			
			System.out.println(stepsDone + " / " + nSteps + " :\t" + isHome0 + "\t" + isHome1);
		}while(!death && (!homeStop || !allActiveHome) && (nSteps < 0 || stepsDone < nSteps));
		
		Comedi.close(dev);
	}
	

	public static final void setFLC(boolean high){
		String comediDev = SettingsManager.defaultGlobal().getProperty("comedi.device", "/dev/comedi0");
		int subdev = OneLiners.mustParseInt(SettingsManager.defaultGlobal().getProperty("comedi.subdevice.dio", "2"));
		
		long dev = Comedi.open(comediDev + "_subd" + subdev);
		if(dev == 0)
			throw new ComediJNIException("comedi_open");
		
		int ret = Comedi.dio_config(dev, subdev, DACTimingConfig.CH_FLC_DRIVE, ComediDef.COMEDI_OUTPUT);
		if(ret < 0)
			throw new ComediJNIException("dio_config(step)");
		
		Comedi.data_write(dev, subdev, DACTimingConfig.CH_FLC_DRIVE, 0, 0, high ? 1 : 0);				
		
		Comedi.close(dev);
	}
	
	public static final void cameraTrig(int periodMS){
		String comediDev = SettingsManager.defaultGlobal().getProperty("comedi.device", "/dev/comedi0");
		int subdev = OneLiners.mustParseInt(SettingsManager.defaultGlobal().getProperty("comedi.subdevice.dio", "2"));
		
		long dev = Comedi.open(comediDev + "_subd" + subdev);
		if(dev == 0)
			throw new ComediJNIException("comedi_open");
		
		int ret = Comedi.dio_config(dev, subdev, DACTimingConfig.CH_CAM_TRIG, ComediDef.COMEDI_OUTPUT);
		if(ret < 0)
			throw new ComediJNIException("dio_config(step)");
		
		Comedi.data_write(dev, subdev, DACTimingConfig.CH_CAM_TRIG, 0, 0, 1);
		try {
			Thread.sleep(periodMS);
		} catch (InterruptedException e) { }
		
		Comedi.data_write(dev, subdev, DACTimingConfig.CH_CAM_TRIG, 0, 0, 0);
		try {
			Thread.sleep(periodMS);
		} catch (InterruptedException e) { }
		
		Comedi.close(dev);
	}
	
	/** We don't have a Thread.sleep of less than 1ms, so we just have to burn CPU cycles
	 * 
	 * Hopefully, most of the time, parknanos(), together with the overhead will
	 * throw the while loop out after 1 iteration, giving the correct time.
	 * 
	 * It is however possible that this can sleep for up to almost 2*nanosecs, occasionally.
	 * 
	 */
	private static final void nanoSleepAtLeast(long nanosecs){
		long t0 = System.nanoTime();
		while((System.nanoTime() - t0) < nanosecs){
			LockSupport.parkNanos(nanosecs);
		}
	}
	

}
