package imseProc.arduinoComm;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import oneLiners.OneLiners;

import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import algorithmrepository.Interpolation1D;
import algorithmrepository.LinearInterpolation1D;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import imseProc.arduinoComm.swt.ArduinoCommSWTControl;
import imseProc.core.ImagePipeController;
import imseProc.core.ImgPipe;
import imseProc.core.ImgSink;
import imseProc.core.Triggerable;

/** Communications with the Arduino in-hall controller code IMSEArduinoCtrl */

public class ArduinoCommHanger extends ImgPipe implements ImgSink, Triggerable {


	private ArduinoCommSWTControl swtController;
	private LoggedSerialComm serialComm = new LoggedSerialComm(this);
	private SequenceCtrl seqCtrl = new SequenceCtrl(this);
	
	//Last reported state of hardware
	private double ovenTemp = Double.NaN;
	private int motorPos;
	private boolean motorEnabled = false;
	private boolean flcGateState = true;
	private boolean flcDriveState = false;
	private boolean ovenPowerState = false;
	
	private int watchdogTimeoutMS = 3600000;	
	
	private LinearInterpolation1D tempCalibInterp = new LinearInterpolation1D( 
				new double[]{ 500, 564, 629, 657, 720 }, //Analogue signal (0=0v, 1023~5v)
				new double[]{ 24.0, 30, 36, 39, 45.5 }, // temp according to display
				Interpolation1D.EXTRAPOLATE_LINEAR,
				0
		);
	
	@Override
	/** Expose as public for SequenceCtrl */
	public void updateAllControllers() {
		super.updateAllControllers();
	}
	
	@Override
	public ImagePipeController createPipeController(Class interfacingClass, Object args[], boolean asSink) {
		if(interfacingClass == Composite.class){
			if(swtController == null){
				swtController = new ArduinoCommSWTControl(this, (Composite)args[0], (Integer)args[1]);
				controllers.add(swtController);
			}
			return swtController;
		}else
			return null;
	}
	
	public void receivedLine(String line){
		//incoming
		if(line.startsWith("MOT:HOME")){
			motorPos = 0;
		}else if(line.startsWith("OVN:RAWTEMP")){
			int rawTemp = OneLiners.mustParseInt(line.substring(11).trim());
			ovenTemp = tempCalibInterp.eval(rawTemp);
			
		}else if(line.startsWith("MOT:POS")){
			motorPos = OneLiners.mustParseInt(line.substring(7).trim());			
			
		}else if(line.startsWith("MOT:ENBL")){
			motorEnabled = (OneLiners.mustParseInt(line.substring(8).trim()) != 0);			
			
		}else if(line.startsWith("FLC:STAT")){
			//FLC:STAT G=#, D=#
			
			flcGateState = (line.charAt(11) == '1');
			flcDriveState = (line.charAt(16) == '1');
			
		}else if(line.startsWith("OVN:POWER")){
			//OVN:POWER #
			ovenPowerState = (OneLiners.mustParseInt(line.substring(9).trim()) != 0);			
			
		}else if(line.startsWith("SEQ:START")){
			seqCtrl.setArduinoInSeq(true);
			
		}else if(line.startsWith("SEQ:STOP")){
			seqCtrl.setArduinoInSeq(false);
		}else if(line.startsWith("SEQ:RECDUMP")){
			seqCtrl.recievedRecordDump(serialComm.getLastBinaryDump());
		}
			
		updateAllControllers();
	}
	
	void serialConnected(){
		seqCtrl.fastAbortSequence(); //stop any active sequence first
		seqCtrl.setArduinoInSeq(false);
		
		//get everything in sync
		setFLCState(flcGateState, flcDriveState);
		ovenSetPower(ovenPowerState);
		motorSetEnable(motorEnabled);
		motorRequestPos();
		getOvenTemp();
		setWatchdogTimeout(watchdogTimeoutMS);
	}
	
	void serialLogChange() { //call through from LoggedSerialComm
		updateAllControllers();
	}
	
	public LoggedSerialComm serialComm(){ return serialComm; }
	
	@Override
	public void destroy() {
		serialComm.destroy();
		super.destroy();
	}

	public void motorSetCurrent(int milliAmps) {
		serialComm.send("MOT-CURR" + milliAmps);
	}
	
	public void motorSetStepPeriod(int microSecs) {
		serialComm.send("MOT-SDLY" + microSecs);
	}
	
	public void motorHome() {
		serialComm.send("MOT-HOME");
	}
	
	public void motorSetEnable(boolean enable) {
		serialComm.send("MOT-ENBL" + (enable ? "1" : "0"));
	}

	public void motorRequestPos() {
		serialComm.send("MOT-GPOS");
	}
	

	public void motorGotoPos(int pos) {
		serialComm.send("MOT-GOTO" + pos);
	}

	public void setFLCState(boolean gate, boolean drive) {
		serialComm.send("FLC-SET" + (gate ? "1" : "0") + (drive ? "1" : "0"));
		
	}

	public void ovenSetPower(boolean enable) { 
		serialComm.send("OVN-POWR" + (enable ? "1" : "0"));
	}

	public void requestOvenTemp() {
		serialComm.send("OVN-TEMP");
	}
	
	public int getMotorPos(){ return motorPos; }
	public double getOvenTemp(){ return ovenTemp; }


	public boolean motorIsEnabled() {
		return motorEnabled;
	}
	
	public ArduinoCommSWTControl getSWTController(){ return swtController; }

	public boolean getOvenPowerState() { return ovenPowerState; }
	public boolean getFLCGateState() { return flcGateState; }
	public boolean getFLCDriveState() { return flcDriveState;	}
		
	public long getKeepAliveTimeMS(){ return keepAliveRunner.getPeriodMS(); }
	public void setKeepAliveTimeMS(long keepAliveTimeMS){ keepAliveRunner.setPeriodMS(keepAliveTimeMS);	};

	public SequenceCtrl seqCtrl(){ return seqCtrl; }
	
	private class KeepAliveRunner implements Runnable {
		private Thread thread;
		private boolean death = false;
		private long periodMS;
		
		public KeepAliveRunner() {
			setPeriodMS(60000); //60s default period
		}
		
		@Override
		public void run() {
			while(!death){
				if(serialComm.isConnected()){
					if(seqCtrl.isRemoteActive()){
						System.out.println("Keep-alive inhibited because sequence is active.");
					}else{
						serialComm.send("PING");
					}
				}
				try {
					Thread.sleep(periodMS);
				} catch (InterruptedException e) { }
			}
		}
		public void setPeriodMS(long periodMS) {
			this.periodMS = periodMS;
			if(periodMS <= 0){
				death = true;
				if(thread != null && thread.isAlive()){ 
					thread.interrupt();
				}
			}else{
				death = false;
				if(thread == null || !thread.isAlive()){
					thread = new Thread(this);
					thread.start();
				}else{
					thread.interrupt();
				}
			}		
			
		}
		public long getPeriodMS() { return periodMS; }
	}
	
	private KeepAliveRunner keepAliveRunner = new KeepAliveRunner();
	
	@Override
	public void triggerStart() {
		seqCtrl.softTriggerStart();
	}
	
	@Override
	public void triggerAbort() {
		seqCtrl.softTriggerAbort();
	}

 	@Override
	public void imageChanged(int idx) { }		// we don't care		

 	public int getWatchdogTimeout() { return watchdogTimeoutMS; }
 	public void setWatchdogTimeout(int watchdogTimeoutMS) { 
 		this.watchdogTimeoutMS = Math.max(5000, watchdogTimeoutMS);
 		serialComm.send("CTL-WDOG" + this.watchdogTimeoutMS);
	}

 	public boolean isIdle(){ return false; }
}
