package imseProc.arduinoComm;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;

import imseProc.core.IMSEProc;

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

import oneLiners.OneLiners;

public class LoggedSerialComm implements Runnable {
	private ArduinoCommHanger proc;
	
	private SerialPort port = null;
	private InputStream inStream;
	private OutputStream outStream;
	private StringBuffer serialLog = new StringBuffer(1048576);
	
	private boolean death = false;
	private Thread thread;
	private byte[] lastBinaryDump;
	private int binaryDumpPos = -1;

	public LoggedSerialComm(ArduinoCommHanger proc) {
		this.proc = proc;
		
	}
	
	@Override
	public void run() {
		StringBuffer lineBuild = new StringBuffer(128);
		
		try {
			while(!death){
				int byteRead;
				while(inStream.available() == 0){
					try{
						Thread.sleep(10);
					} catch (InterruptedException e) {
						if(death)break;
					}
				}
				if(death)break;
				byteRead = inStream.read();
					
				if(byteRead >= 0 && binaryDumpPos >= 0){
					lastBinaryDump[binaryDumpPos] = (byte)byteRead;
					binaryDumpPos++;
					if(binaryDumpPos >= lastBinaryDump.length){
						binaryDumpPos = -1;
						System.out.println("Serial: Binary dump received");
					}
					continue;
				}
					
				
				if(byteRead == 0x0A){
					serialLog.append("< ");
					serialLog.append(lineBuild);
					serialLog.append('\n');
					String lineStr = lineBuild.toString();
					if(lineStr.startsWith("BINDUMP")){
						int size = OneLiners.mustParseInt(lineStr.substring(7).trim());			
						lastBinaryDump = new byte[size];
						binaryDumpPos=0;
						System.out.println("Serial: Starting binary dump receive of len " + size);
					}else{
						proc.receivedLine(lineStr);
					}
					
					lineBuild.setLength(0);
				}else if(byteRead == 0x0D){
					// what?
				}else{				
					lineBuild.append((char)byteRead);
				}				
			}
			proc.serialLogChange();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void connect(final String portName, final int baudRate){
		
		IMSEProc.ensureFinalUpdate(this, new Runnable() { @Override public void run() { doConnect(portName, baudRate); } });
	}
	
	public void disconnect(){
		IMSEProc.ensureFinalUpdate(this, new Runnable() { @Override public void run() { doDisconnect(); } });
	}
	

	private void doConnect(String portName, int baudRate){
		doDisconnect();
		if(thread != null && thread.isAlive()){
			waitForThreadDeath();
		}
		
		try{
			serialLog.append("----- Connecting to port "+portName+" BAUD " + baudRate + " ----- \n");
			
			CommPortIdentifier portIdentifier;
			portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
		
			if ( portIdentifier.isCurrentlyOwned() )
				throw new RuntimeException("Error: Port is currently in use");
	
			CommPort commPort = portIdentifier.open(this.getClass().getName(), 2000);
	
			if(!(commPort instanceof SerialPort)) 
				throw new RuntimeException("Error: '"+portName+"' is not a serial port.");
	
			port = (SerialPort) commPort;
			port.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
			//serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT);
			//serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_XONXOFF_IN | SerialPort.FLOWCONTROL_XONXOFF_OUT);
			port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
			
			inStream = port.getInputStream();
			outStream = port.getOutputStream();
			binaryDumpPos = -1;
			
			death = false;
			thread = new Thread(this);
			thread.start();
			

		}catch(Exception e) {
			e.printStackTrace();
			serialLog.append("| ERROR: " + e.getMessage() + '\n'); 
			port = null;
		}

		try{
			proc.serialConnected();
		}catch(Exception e) {
			e.printStackTrace();
			serialLog.append("| ERROR: " + e.getMessage() + '\n'); 
		}
		
		proc.serialLogChange();
	}
	
	public StringBuffer getSerialLog(){ return serialLog; }
	
	
	private void doDisconnect(){
		death = true;
		if(thread != null)
			thread.interrupt();
		if(port != null){
			port.close();
			port = null;
			serialLog.append("----- Port closed -----");
		}
		proc.serialLogChange();
	}
	
	private void waitForThreadDeath(){
		serialLog.append("Waiting for thread to die");
		death = true;
		thread.interrupt();
		while(thread != null && thread.isAlive()){
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) { }
		}
		serialLog.append("Thread died");		
		thread = null;
		proc.serialLogChange();
	}
	
	public void send(String sendData){
		
		try {
			if(port == null){
				throw new RuntimeException("Not connected");
			}
			
			outStream.write('\n'); //clear any previous rubbish
			outStream.write(sendData.getBytes());
			outStream.write('\n');
			
			serialLog.append("> ");
			serialLog.append(sendData);
			serialLog.append('\n');
			
		} catch (Exception e) {
			serialLog.append(">X ERROR: Could not send data '"+sendData+"', because: " + e.getMessage() + '\n');
			e.printStackTrace();
		}
		proc.serialLogChange();
	}
	

	public void sendByte(byte singleByte) {
		String hexStr = "0x" + Integer.toHexString(((int)singleByte) & 0x000000FF).toUpperCase();
		try {
			if(port == null){
				throw new RuntimeException("Not connected");
			}
			
			outStream.write(singleByte);
			
			serialLog.append("> [0x");
			serialLog.append(hexStr);
			serialLog.append("]\n");
			
		} catch (Exception e) {
			serialLog.append(">X ERROR: Could not send ["+hexStr+"], because: " + e.getMessage() + '\n');
			e.printStackTrace();
		}
		proc.serialLogChange();
	}

	public void destroy() {
		disconnect();
	}

	public String getStatus() {
		return "Port = " + port;
	}
	
	public boolean isConnected(){ return port != null; }

	public byte[] getLastBinaryDump() { return lastBinaryDump; }

}
