package jGPIO.SerialPort;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.objects.collections.List;

import com.fazecast.jSerialComm.*;


@BA.ShortName("SerialComm")
@BA.Events(values= {
	"log(msg as string)",
	"newdata(bb() as byte)"
	
})
//@BA.DependsOn(values = { "jSerialComm-2.6.2" })

public class SerialComm {
	
	private BA ba;
	private Object myobject;
	private String event;
	private boolean need_log_event = false;
	private boolean need_newdata_event = false;
	
	
	private jSerialPort_Event javaevent;
	private boolean isReady = false;
	private String version="";
	private long writtencount = 0;
	private long readcount = 0;
	
	private SerialPort myport;
	
	private String myportname;
	
	
	/**
	 * Initialize jSerialPort (B4J / B4A)
	 * @param caller : caller object
	 * @param eventname : event name
	 */
	public void Initialize(BA bax, Object caller, String eventname) {
		isReady = false;
		ba = bax;
		event = eventname;
		myobject = caller;
		need_log_event = ba.subExists(event+"_log");
		need_newdata_event = ba.subExists(event+"_newdata");
		
		version = SerialPort.getVersion();
		if (version!="") isReady = true;
		
		 Runtime.getRuntime().addShutdownHook(new Thread() {
	            @Override
	            public void run() {
	                ClosePort();
	            }
	        });
	}
	
	/**
	 * Check Version String of SerialPort Library
	 * @return version string
	 */
	public String GetVersion() {
		return version;
	}
	
	/**
	 * Get all available port names in this system
	 * @return List of Serial Port Names
	 */
	public List GetPortNames(){
		List result = new List();
		result.Initialize();
		if (isReady) {
			for(SerialPort xx:SerialPort.getCommPorts()) {
				if (xx != null) {
					String portname = xx.getSystemPortName();
					if (portname!="") {
						result.Add(portname);
					}
				}
			}
		} else {
			raise_log("GetPortNames failed, SerialPort Library not ready");
		}
		return result;
	}
	
	/**
	 * Check if SerialComm is Ready to work
	 * @return true if ready 
	 */
	public boolean PortIsReady() {
		if (myport!=null) {
			if (myport instanceof SerialPort) {
				return myport.isOpen();
			}
		}
		return false;
		
	}
	
	/**
	 * Open Serial Port, with default value 1000 bytes RX buffer, and 1000 bytes TX Buffer
	 * @param portname : portname (a member of GetPortNames)
	 * @return true if port opened
	 */
	public boolean OpenPort(String portname) {
		myportname="";
		writtencount = 0;
		readcount = 0;
		if (isReady) {
			myport = SerialPort.getCommPort(portname);
			if (myport instanceof SerialPort) {
				if (myport.openPort()) {
					
					if (myport.addDataListener(new SerialPortDataListener() {

						@Override
						public int getListeningEvents() {
							return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
						}

						@Override
						public void serialEvent(SerialPortEvent event) {
							if (event instanceof SerialPortEvent) {
								if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
									int available = myport.bytesAvailable();
									if (available>0) {
										byte[] bb = new byte[available];
										int readbytes = myport.readBytes(bb, available);

										raise_newdata(bb);
										readcount+= readbytes;
									}
								}
							}
						}
						
					})) {
						
					} else {
						raise_log("Failed to add DataListener to SerialComm");
					}
					
					
					myportname = portname;
					raise_log("SerialPort name="+myportname+" is opened");
					return true;
				}	
			}
			
		}
		raise_log("Failed to open SerialPort name="+portname);
		return false;
	}
	
	@BA.Hide
	/**
	 * Read serial port 
	 * @param count : maximum bytes wanted to read
	 * @return array of bytes, or null if failed
	 */
	public byte[] Read(int count) {
		if (isReady) {
			if (myport instanceof SerialPort) {
				if (myport.isOpen()) {
					int available = myport.bytesAvailable();
					if (available>0) {
						byte[] bb = new byte[count];
						myport.readBytes(bb, bb.length);
						
						readcount+= bb.length;
						return bb;
					}
				}
			}
		}
		return null;
	}
	
	/**
	 * Close Serial Port
	 */
	public void ClosePort() {
		if (isReady) {
			if (myport!=null) {
				if (myport instanceof SerialPort) {
					try {
						myport.removeDataListener();
						if (myport.isOpen()) myport.closePort();	
					} catch(Exception e) {
						raise_log("ClosePort exception, Msg : "+e.getMessage());
					}
				}
				
				myport = null;
				raise_log("SerialPort name="+myportname+" is closed");		
			}
		}
		isReady = false;
		myportname = "";
		
	}
	
	/**
	 * Get current used Port Name
	 * @return port name, or empty string if closed
	 */
	public String CurrentPortName() {
		return myportname;
	}
	
	/**
	 * Get Baud Rate
	 * @return -1 if failed
	 */
	public int GetBaudRate() {
		if (isReady) {
			if (myport instanceof SerialPort) {
				return myport.getBaudRate();
			}
		}
		return -1;
	}
	
	/**
	 * Set Baud Rate
	 * @param value : baud rate value
	 * @return true if success
	 */
	public boolean SetBaudRate(int value) {
		if (isReady) {
			if (myport instanceof SerialPort) {
				myport.setBaudRate(value);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Get Data bits
	 * @return -1 if failed
	 */
	public int GetDataBits() {
		if (isReady) {
			if (myport instanceof SerialPort) {
				return myport.getNumDataBits();
			}
		}
		
		return -1;
	}
	
	/**
	 * Set Data bits
	 * @param value : data bits value
	 * @return true if success
	 */
	public boolean SetDataBits(int value) {
		if (isReady) {
			if (myport instanceof SerialPort) {
				myport.setNumDataBits(value);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Get / Set Stop bit
	 * @return -1 = failed, 1 = 1 stop bit, 2 = 2 stop bit, 3 = 1.5 stop bit
	 */
	public int GetStopBits() {
		if (isReady) {
			if (myport instanceof SerialPort) {
				int value = myport.getNumStopBits();
				switch(value) {
					case SerialPort.ONE_STOP_BIT : 
						return 1;
					case SerialPort.ONE_POINT_FIVE_STOP_BITS :
						return 3;
					case SerialPort.TWO_STOP_BITS : 
						return 2;
				}
			}
		}
		return -1;
	}
	
	/**
	 * Set Stop bit
	 * @param stopbitcode : 1 = 1 stop bit, 2 = 2 stop bit, 3 = 1.5 stop bit, other = 1 stop bit
	 * @return true if success
	 */
	public boolean SetStopBits(int stopbitcode) {
		if (isReady) {
			if (myport instanceof SerialPort) {
				switch(stopbitcode) {
					case 3 :
						myport.setNumStopBits(SerialPort.ONE_POINT_FIVE_STOP_BITS);
						break;
					case 2 :
						myport.setNumStopBits(SerialPort.TWO_STOP_BITS);
						break;
					default :
						myport.setNumStopBits(SerialPort.ONE_STOP_BIT);
						break;
					
				}
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Get Parity Code
	 * @return -1 if failed, 0 = None, 1 = Odd, 2 = Even, 3 = Mark, 4 = Space
	 */
	public int GetParityBits() {
		if (isReady) {
			if (myport instanceof SerialPort) {
				int value = myport.getParity();
				switch(value) {
					case SerialPort.ODD_PARITY :
						return 1;
					case SerialPort.EVEN_PARITY :
						return 2;
					case SerialPort.MARK_PARITY :
						return 3;
					case SerialPort.SPACE_PARITY :
						return 4;
					default :
						return 0;
				}
			}
		}
		return -1;
	}
	
	/**
	 * Set Parity
	 * @param paritycode : 1 = odd, 2 = even, 3 = mark, 4 = space, default (0) = None
	 * @return true if success
	 */
	public boolean SetParityBits(int paritycode) {
		if (isReady) {
			if (myport instanceof SerialPort) {
				switch(paritycode) {
				case 1 :
					myport.setParity(SerialPort.ODD_PARITY);
					break;
				case 2 :
					myport.setParity(SerialPort.EVEN_PARITY);
					break;
				case 3 :
					myport.setParity(SerialPort.MARK_PARITY);
					break;
				case 4 :
					myport.setParity(SerialPort.SPACE_PARITY);
					break;
				default : 
					myport.setParity(SerialPort.NO_PARITY);
					break;
				}
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Get available bytes to read in internal buffer
	 * @return -1 if failed
	 */
	public int getAvailableBytesToRead() {
		if (isReady) {
			if (myport instanceof SerialPort) {
				return myport.bytesAvailable();
			}
		}
		return -1;
	}
	
	/**
	 * Get number of bytes stil waiting to send in internal buffer
	 * @return -1 if failed
	 */
	public int getWaitingBytesToSend() {
		if (isReady) {
			if (myport instanceof SerialPort) {
				return myport.bytesAwaitingWrite();
			}
		}
		return -1;
	}
	
	/**
	 * Get Clear-To-Send bit value
	 * @return true if CTS set
	 */
	public boolean getCTS() {
		if (isReady) {
			if (myport instanceof SerialPort) {
				return myport.getCTS();
			}
		}
		return false;
	}
	
	/**
	 * Set Request-To-Send bit
	 * @param value : if true, RTS is ON
	 */
	public void setRTS(boolean value) {
		if (isReady) {
			if (myport instanceof SerialPort) {
				
				if (value)
					myport.setRTS();
				else
					myport.clearRTS();
			}
		}
	}
	
	/**
	 * Write bytes to Serial
	 * @param bb : bytes to write
	 * @return number of bytes written to internal buffer, or -1 if failed
	 */
	public int Write(byte[] bb) {
		if (isReady) {
			if (myport  instanceof SerialPort) {
				int writeresult = myport.writeBytes(bb, bb.length);
				writtencount+=writeresult;
				return writeresult;
			}
		}
		return -1;
	}
	
	/**
	 * Set Read and Write blocking / timeout. Positive value means block until specific timeout
	 * @param readblocking_ms : 0 = no read block, positive = milliseconds to block waiting for read
	 * @param writeblocking_ms : 0 = no write block, positive = milliseconds to block until writing complete
	 */
	public void SetBlockingMode(int readblocking_ms, int writeblocking_ms) {
		if (isReady) {
			if (myport instanceof SerialPort) {
				int blockvalue = 0;		// default is NON_BLOCKING
				if (readblocking_ms>0) blockvalue |= SerialPort.TIMEOUT_READ_BLOCKING;
				if (writeblocking_ms>0) blockvalue |= SerialPort.TIMEOUT_WRITE_BLOCKING;
				
				myport.setComPortTimeouts(blockvalue, readblocking_ms, writeblocking_ms);
			}
		}
	}
	
	/**
	 * Get how many bytes written since opening
	 * @return bytes written
	 */
	public long getBytesWritten() {
		return writtencount;
	}
	
	/**
	 * Get how many bytes read since opening
	 * @return bytes read
	 */
	public long getBytesRead() {
		return readcount;
	}
	
	@BA.Hide
	public void SetJavaEvent(jSerialPort_Event event) {
		javaevent = event;
	}
	
	private void raise_log(String msg) {
		if (need_log_event) {
			ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg});
		}
		if (javaevent!=null) {
			javaevent.Log(msg);
		}
	}
	
	private void raise_newdata(byte[] bb) {
		if (need_newdata_event) {
			ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_newdata", false, new Object[] {bb});
		}
		if (javaevent!=null) {
			javaevent.newdata(bb);
		}
	}

	
}
