<commit_before>package org.myrobotlab.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.myrobotlab.codec.serial.Codec;
import org.myrobotlab.codec.serial.CodecOutputStream;
import org.myrobotlab.framework.Platform;
import org.myrobotlab.framework.Service;
import org.myrobotlab.framework.ServiceType;
import org.myrobotlab.io.FileIO;
import org.myrobotlab.logging.Level;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.logging.LoggingFactory;
import org.myrobotlab.serial.Port;
import org.myrobotlab.serial.PortQueue;
import org.myrobotlab.serial.SerialControl;
import org.myrobotlab.serial.PortStream;
import org.myrobotlab.service.interfaces.QueueSource;
import org.myrobotlab.service.interfaces.SerialDataListener;
import org.myrobotlab.service.interfaces.ServiceInterface;
import org.slf4j.Logger;

/**
 * 
 * Serial - a service that allows reading and writing to a serial port device.
 *
 */
public class Serial extends Service implements SerialControl, QueueSource, SerialDataListener {

	/**
	 * general read timeout - 0 is infinite > 0 is number of milliseconds to
	 * wait up to, until data is returned timeout = null: wait forever timeout =
	 * 0: non-blocking mode (return immediately on read) timeout = x: set
	 * timeout to x milliseconds
	 */
	private Integer timeoutMS = null;

	private static final long serialVersionUID = 1L;

	// rates
	public final static Integer BAUD_2400 = 2400;
	public final static Integer BAUD_4800 = 4800;
	public final static Integer BAUD_9600 = 9600;
	public final static Integer BAUD_19200 = 19200;
	public final static Integer BAUD_38400 = 38400;
	public final static Integer BAUD_57600 = 57600;
	public final static Integer BAUD_115200 = 115200;

	/**
	 * deprecated hardware library
	 */
	final public static String HARDWARE_LIBRARY_RXTX = "org.myrobotlab.serial.PortRXTX";

	/**
	 * different hardware library - hotspot only
	 */
	final public static String HARDWARE_LIBRARY_JSSC = "org.myrobotlab.serial.PortJSSC";

	/**
	 * Android only bluetooth library
	 */
	final public static String HARDWARE_LIBRARY_ANDROID_BLUETOOTH = "android.somethin";

	transient public final static Logger log = LoggerFactory.getLogger(Serial.class);

	/**
	 * cached list of portnames on the system
	 */
	static HashSet<String> portNames = new HashSet<String>();

	/**
	 * blocking and non-blocking publish/subscribe reading is possible at the
	 * same time. If blocking is not used then the internal buffer will fill to
	 * the BUFFER_SIZE and just be left - overrun data will be lost
	 */
	int BUFFER_SIZE = 1024;

	/**
	 * blocking queue for blocking rx read requests
	 */
	transient BlockingQueue<Integer> blockingRX = new LinkedBlockingQueue<Integer>();

	/**
	 * our set of ports we have access to. This is a shared resource between ALL
	 * serial services. It should be possible simply to iterate through this
	 * list to get all (cached) names .. operating system ports may have changed
	 * and need re-querying
	 * 
	 * it also might be worthwhile to keep a "static" list for remote and
	 * virtual ports so that remote and other services can have access to that
	 * list
	 * 
	 * has to be transient because many Ports are not serializable
	 * 
	 * remote manipulations and identification should always be done through
	 * portNames
	 */
	transient static HashMap<String, Port> ports = new HashMap<String, Port>();

	/**
	 * all the ports we are currently connected to typically there is 0 to 1
	 * connected ports - however the serial service has the ability to "fork"
	 * ports where it is connected to 2 or more ports simultaneously
	 */
	transient HashMap<String, Port> connectedPorts = new HashMap<String, Port>();

	/**
	 * used as the "default" port - now that Serial can multiplex with multiple
	 * ports - the default is used for methods which are not explicit ... e.g.
	 * connect(), disconnect() etc.. are now equivalent to connect(portName),
	 * disconnect(portName)
	 */
	String portName = null;

	/**
	 * last port name which was connected to - still has name when portName is
	 * null and disconnected
	 */
	public String lastPortName;

	/**
	 * "the" port - there is only one although we can fork and multiplex others.
	 * This is the port which we determine if this Serial service is connected
	 * or not
	 */
	transient Port port = null;

	/**
	 * we need to dynamically load our preferred hardware type, because we may
	 * want to change it or possibly the platform does not support it. When it
	 * is null - we will let MRL figure out what is best.
	 */
	String hardwareLibrary = null;

	/**
	 * rx files saved to an output stream
	 */
	transient CodecOutputStream outRX = new CodecOutputStream("rx", this);

	/**
	 * tx bytes saved to an output stream
	 */
	transient CodecOutputStream outTX = new CodecOutputStream("tx", this);

	/**
	 * number of tx bytes
	 */
	int txCount = 0;

	/**
	 * number of received bytes
	 */
	int rxCount = 0;

	/**
	 * default bps
	 */
	int baudrate = 115200;

	/**
	 * default databits
	 */
	int databits = 8;

	/**
	 * default stopbits
	 */
	int stopbits = 1;

	/**
	 * default parity
	 */
	int parity = 0;

	/**
	 * list of RX listeners - if "local" they will be immediately called back by
	 * the serial device's thread when data arrives, if they are "remote" they
	 * should be published to. They can subscribe to the publishRX method when a
	 * lister is added. Serial is the first listener added to this map
	 */
	transient HashMap<String, SerialDataListener> listeners = new HashMap<String, SerialDataListener>();

	/**
	 * conversion utility TODO - support endianess
	 * 
	 * @param bytes
	 * @param offset
	 * @param length
	 * @return
	 */
	public static int bytesToInt(int[] bytes, int offset, int length) {
		return (int) bytesToLong(bytes, offset, length);
	}

	/**
	 * conversion utility TODO - support endianess
	 * 
	 * @param bytes
	 * @param offset
	 * @param length
	 * @return
	 */
	public static long bytesToLong(int[] bytes, int offset, int length) {

		long retVal = 0;

		for (int i = 0; i < length; ++i) {
			retVal |= (bytes[offset + i] & 0xff);
			if (i != length - 1) {
				retVal <<= 8;
			}
		}

		return retVal;
	}

	/*
	 * DEPRECATED BECAUSE SIGNED BYTE ARRAYS ARE SILLY public static byte[]
	 * intArrayToByteArray(int[] src) {
	 * 
	 * if (src == null) { return null; }
	 * 
	 * byte[] ret = new byte[src.length]; for (int i = 0; i < src.length; ++i) {
	 * ret[i] = (byte) src[i]; } return ret; }
	 */

	/**
	 * Static list of third party dependencies for this service. The list will
	 * be consumed by Ivy to download and manage the appropriate resources
	 * 
	 * @return
	 */

	public Serial(String n) {
		super(n);
		listeners.put(n, this);

		// outbox.setBlocking(true);
		// outbox.maxQueue = 1;

	}

	public void addByteListener(SerialDataListener listener) {
		addByteListener(listener.getName());
	}

	/**
	 * awesome method - which either sets up the pub/sub remote or assigns a
	 * local reference from the publishing thread
	 * 
	 * good pattern in that all logic is in this method which uses a string
	 * "name" parameter - addByteListener(SerialDataListener listener) will call
	 * this method too rather than implementing its own local logic
	 * 
	 * FIXME - DO THIS STUFF (AND THE PUBLISHING/TESTING) IN THE FRAMEWORK
	 * 
	 * @param name
	 */
	public void addByteListener(String name) {
		ServiceInterface si = Runtime.getService(name);

		// if (si instanceof SerialDataListener && si.isLocal()){
		if (SerialDataListener.class.isAssignableFrom(si.getClass()) && si.isLocal()) {
			// direct callback
			listeners.put(si.getName(), (SerialDataListener) si);
		} else {
			// pub sub
			addListener("publishRX", si.getName(), "onByte");
			addListener("publishConnect", si.getName(), "onConnect");
			addListener("publishDisconnect", si.getName(), "onDisconnect");
		}
	}

	/**
	 * method similar to InputStream's
	 * 
	 * @return
	 */
	public int available() {
		return blockingRX.size();
	}

	/**
	 * clears the rx buffer
	 */
	public void clear() {
		blockingRX.clear();
		outRX.clear();
		outTX.clear();
	}

	/**
	 * for backwards compatibility
	 * 
	 * @param name
	 * @throws IOException
	 */
	public void connect(String name) throws IOException {
		open(name);
	}

	public void connect(String name, int baudRate, int dataBits, int stopBits, int parity) throws IOException {
		open(name);
		setParams(baudRate, dataBits, stopBits, parity);
	}

	public void open(String name) throws IOException {
		open(name, baudrate, databits, stopbits, parity);
	}

	public boolean setParams(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
		try {
			log.info("setParams {} {} {} {}", baudRate, dataBits, stopBits, parity);
			if (port == null || !port.isOpen()) {
				log.error("port is null or not opened");
				return false;
			}

			if (port.setParams(baudRate, dataBits, stopBits, parity)) {
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			throw new IOException(e);
		}
	}

	/**
	 * The main simple connect - it attempts to connect to one of the known
	 * ports in memory if that fails it will try to connect to a hardware port
	 * 
	 * TODO - make "connecting" to pre-existing ports re-entrant !!!
	 * 
	 * connect - optimized to have a SerialDataListener passed in - which will
	 * optimize data streaming back from the port.
	 * 
	 * preference is to have this local optimizaton
	 * 
	 * connect = open + listen
	 * 
	 * @param inPortName
	 * @param listener
	 * @return
	 * @throws IOException
	 * @throws Exception
	 */
	public void open(String inPortName, int baudrate, int databits, int stopbits, int parity) throws IOException {

		info("connect to port %s %d|%d|%d|%d", inPortName, baudrate, databits, stopbits, parity);
		this.baudrate = baudrate;
		this.databits = databits;
		this.stopbits = stopbits;
		this.parity = parity;

		// two possible logics to see if we are connected - look at the
		// state of the port
		// on the static resource - or just check to see if its on the
		// "connectedPort" set

		// #1 check to see if were already connected a port
		// if (this.portName != null && this.portName.equals(portName) &&
		// ports.containsKey(portName)) {
		if (this.portName != null) {
			Port port = ports.get(portName);
			if (port.isOpen() && port.isListening()) {
				info("already connected to %s - disconnect first to reconnect", portName);
				return;
			}
		}

		// #2 connect to a pre-existing
		if (ports.containsKey(inPortName)) {
			connectPort(ports.get(inPortName), null);
			lastPortName = portName;
			return;
		}

		// #3 we dont have an existing port - so we'll try a hardware port
		// connect at defaullt parameters - if you need custom parameters
		// create the hardware port first
		Port port = createHardwarePort(inPortName, baudrate, databits, stopbits, parity);
		if (port == null) {
			return;
		}

		connectPort(port, null);

		lastPortName = portName;
		// even when the JNI says it is connected
		// rarely is everything ready to go
		// give us half a second for all the buffers
		// & hardware to be ready
		// sleep(1500);
	}

	/**
	 * FIXME - implement connects to a FilePlayer details of tx/rx and timing
	 * can be part os a SerialFilePlayer implementation
	 * 
	 * @param name
	 */
	public boolean connectFilePlayer(String name) {
		//
		return false;
	}

	/**
	 * FIXME - implement Baddass loopback null/modem cable - auto creates a new
	 * Serial service and connects to it FIXME - no need for null/modem cable
	 * virtual port ?
	 * 
	 * @param name
	 */
	public boolean connectLoopback(String name) {
		// TODO - implement
		log.info("implement me");
		return false;
	}

	public Port connectPort(Port newPort, SerialDataListener listener) throws IOException {
		port = newPort;
		portName = port.getName();
		ports.put(port.getName(), port);
		portNames.add(port.getName());
		invoke("getPortNames");

		if (listener != null) {
			listeners.put(listener.getName(), listener);
		}
		if (!port.isOpen()) {
			port.open();
		}
		port.listen(listeners);
		connectedPorts.put(portName, newPort);

		// FYI !!!
		// give us a second before we advertise the port
		// is open - often the hardware or JNI buffers
		// are not ready even though we have "opened" it
		// sleep(1000);

		// invoking remote & local onConnect
		invoke("publishConnect", port.getName());
		for (String key : listeners.keySet()) {
			// NOT A GOOD OPTIMIZATION - AS THE "EVENT" IS MUCH MORE IMPORTANT
			// THAN THE SPEED OF THE DATA
			// listeners.get(key).onConnect(portName);
			send(listeners.get(key).getName(), "onConnect", portName);
		}

		// save(); why?
		broadcastState();
		return port;
	}

	public boolean connectTcp(String host, int port) throws IOException {
		Port tcpPort = createTCPPort(host, port, this);
		connectPort(tcpPort, this);
		return true;
	}

	/**
	 * Dynamically create a hardware port - this method is needed to abtract
	 * away the specific hardware library. Its advantageous to have abstraction
	 * when interfacing with a specific implementation (JNI/JNA - other?). The
	 * abstraction allows the service to attempt to choose the correct library
	 * depending on platform or personal user choice.
	 * <commit_msg>Serial port thinks its connected when its not fixed<commit_after>package org.myrobotlab.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.myrobotlab.codec.serial.Codec;
import org.myrobotlab.codec.serial.CodecOutputStream;
import org.myrobotlab.framework.Platform;
import org.myrobotlab.framework.Service;
import org.myrobotlab.framework.ServiceType;
import org.myrobotlab.io.FileIO;
import org.myrobotlab.logging.Level;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.logging.LoggingFactory;
import org.myrobotlab.serial.Port;
import org.myrobotlab.serial.PortQueue;
import org.myrobotlab.serial.SerialControl;
import org.myrobotlab.serial.PortStream;
import org.myrobotlab.service.interfaces.QueueSource;
import org.myrobotlab.service.interfaces.SerialDataListener;
import org.myrobotlab.service.interfaces.ServiceInterface;
import org.slf4j.Logger;

/**
 * 
 * Serial - a service that allows reading and writing to a serial port device.
 *
 */
public class Serial extends Service implements SerialControl, QueueSource, SerialDataListener {

	/**
	 * general read timeout - 0 is infinite > 0 is number of milliseconds to
	 * wait up to, until data is returned timeout = null: wait forever timeout =
	 * 0: non-blocking mode (return immediately on read) timeout = x: set
	 * timeout to x milliseconds
	 */
	private Integer timeoutMS = null;

	private static final long serialVersionUID = 1L;

	// rates
	public final static Integer BAUD_2400 = 2400;
	public final static Integer BAUD_4800 = 4800;
	public final static Integer BAUD_9600 = 9600;
	public final static Integer BAUD_19200 = 19200;
	public final static Integer BAUD_38400 = 38400;
	public final static Integer BAUD_57600 = 57600;
	public final static Integer BAUD_115200 = 115200;

	/**
	 * deprecated hardware library
	 */
	final public static String HARDWARE_LIBRARY_RXTX = "org.myrobotlab.serial.PortRXTX";

	/**
	 * different hardware library - hotspot only
	 */
	final public static String HARDWARE_LIBRARY_JSSC = "org.myrobotlab.serial.PortJSSC";

	/**
	 * Android only bluetooth library
	 */
	final public static String HARDWARE_LIBRARY_ANDROID_BLUETOOTH = "android.somethin";

	transient public final static Logger log = LoggerFactory.getLogger(Serial.class);

	/**
	 * cached list of portnames on the system
	 */
	static HashSet<String> portNames = new HashSet<String>();

	/**
	 * blocking and non-blocking publish/subscribe reading is possible at the
	 * same time. If blocking is not used then the internal buffer will fill to
	 * the BUFFER_SIZE and just be left - overrun data will be lost
	 */
	int BUFFER_SIZE = 1024;

	/**
	 * blocking queue for blocking rx read requests
	 */
	transient BlockingQueue<Integer> blockingRX = new LinkedBlockingQueue<Integer>();

	/**
	 * our set of ports we have access to. This is a shared resource between ALL
	 * serial services. It should be possible simply to iterate through this
	 * list to get all (cached) names .. operating system ports may have changed
	 * and need re-querying
	 * 
	 * it also might be worthwhile to keep a "static" list for remote and
	 * virtual ports so that remote and other services can have access to that
	 * list
	 * 
	 * has to be transient because many Ports are not serializable
	 * 
	 * remote manipulations and identification should always be done through
	 * portNames
	 */
	transient static HashMap<String, Port> ports = new HashMap<String, Port>();

	/**
	 * all the ports we are currently connected to typically there is 0 to 1
	 * connected ports - however the serial service has the ability to "fork"
	 * ports where it is connected to 2 or more ports simultaneously
	 */
	transient HashMap<String, Port> connectedPorts = new HashMap<String, Port>();

	/**
	 * used as the "default" port - now that Serial can multiplex with multiple
	 * ports - the default is used for methods which are not explicit ... e.g.
	 * connect(), disconnect() etc.. are now equivalent to connect(portName),
	 * disconnect(portName)
	 */
	String portName = null;

	/**
	 * last port name which was connected to - still has name when portName is
	 * null and disconnected
	 */
	public String lastPortName;

	/**
	 * "the" port - there is only one although we can fork and multiplex others.
	 * This is the port which we determine if this Serial service is connected
	 * or not
	 */
	transient Port port = null;

	/**
	 * we need to dynamically load our preferred hardware type, because we may
	 * want to change it or possibly the platform does not support it. When it
	 * is null - we will let MRL figure out what is best.
	 */
	String hardwareLibrary = null;

	/**
	 * rx files saved to an output stream
	 */
	transient CodecOutputStream outRX = new CodecOutputStream("rx", this);

	/**
	 * tx bytes saved to an output stream
	 */
	transient CodecOutputStream outTX = new CodecOutputStream("tx", this);

	/**
	 * number of tx bytes
	 */
	int txCount = 0;

	/**
	 * number of received bytes
	 */
	int rxCount = 0;

	/**
	 * default bps
	 */
	int baudrate = 115200;

	/**
	 * default databits
	 */
	int databits = 8;

	/**
	 * default stopbits
	 */
	int stopbits = 1;

	/**
	 * default parity
	 */
	int parity = 0;

	/**
	 * list of RX listeners - if "local" they will be immediately called back by
	 * the serial device's thread when data arrives, if they are "remote" they
	 * should be published to. They can subscribe to the publishRX method when a
	 * lister is added. Serial is the first listener added to this map
	 */
	transient HashMap<String, SerialDataListener> listeners = new HashMap<String, SerialDataListener>();

	/**
	 * conversion utility TODO - support endianess
	 * 
	 * @param bytes
	 * @param offset
	 * @param length
	 * @return
	 */
	public static int bytesToInt(int[] bytes, int offset, int length) {
		return (int) bytesToLong(bytes, offset, length);
	}

	/**
	 * conversion utility TODO - support endianess
	 * 
	 * @param bytes
	 * @param offset
	 * @param length
	 * @return
	 */
	public static long bytesToLong(int[] bytes, int offset, int length) {

		long retVal = 0;

		for (int i = 0; i < length; ++i) {
			retVal |= (bytes[offset + i] & 0xff);
			if (i != length - 1) {
				retVal <<= 8;
			}
		}

		return retVal;
	}

	/*
	 * DEPRECATED BECAUSE SIGNED BYTE ARRAYS ARE SILLY public static byte[]
	 * intArrayToByteArray(int[] src) {
	 * 
	 * if (src == null) { return null; }
	 * 
	 * byte[] ret = new byte[src.length]; for (int i = 0; i < src.length; ++i) {
	 * ret[i] = (byte) src[i]; } return ret; }
	 */

	/**
	 * Static list of third party dependencies for this service. The list will
	 * be consumed by Ivy to download and manage the appropriate resources
	 * 
	 * @return
	 */

	public Serial(String n) {
		super(n);
		listeners.put(n, this);

		// outbox.setBlocking(true);
		// outbox.maxQueue = 1;

	}

	public void addByteListener(SerialDataListener listener) {
		addByteListener(listener.getName());
	}

	/**
	 * awesome method - which either sets up the pub/sub remote or assigns a
	 * local reference from the publishing thread
	 * 
	 * good pattern in that all logic is in this method which uses a string
	 * "name" parameter - addByteListener(SerialDataListener listener) will call
	 * this method too rather than implementing its own local logic
	 * 
	 * FIXME - DO THIS STUFF (AND THE PUBLISHING/TESTING) IN THE FRAMEWORK
	 * 
	 * @param name
	 */
	public void addByteListener(String name) {
		ServiceInterface si = Runtime.getService(name);

		// if (si instanceof SerialDataListener && si.isLocal()){
		if (SerialDataListener.class.isAssignableFrom(si.getClass()) && si.isLocal()) {
			// direct callback
			listeners.put(si.getName(), (SerialDataListener) si);
		} else {
			// pub sub
			addListener("publishRX", si.getName(), "onByte");
			addListener("publishConnect", si.getName(), "onConnect");
			addListener("publishDisconnect", si.getName(), "onDisconnect");
		}
	}

	/**
	 * method similar to InputStream's
	 * 
	 * @return
	 */
	public int available() {
		return blockingRX.size();
	}

	/**
	 * clears the rx buffer
	 */
	public void clear() {
		blockingRX.clear();
		outRX.clear();
		outTX.clear();
	}

	/**
	 * for backwards compatibility
	 * 
	 * @param name
	 * @throws IOException
	 */
	public void connect(String name) throws IOException {
		open(name);
	}

	public void connect(String name, int baudRate, int dataBits, int stopBits, int parity) throws IOException {
		open(name);
		setParams(baudRate, dataBits, stopBits, parity);
	}

	public void open(String name) throws IOException {
		open(name, baudrate, databits, stopbits, parity);
	}

	public boolean setParams(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
		try {
			log.info("setParams {} {} {} {}", baudRate, dataBits, stopBits, parity);
			if (port == null || !port.isOpen()) {
				log.error("port is null or not opened");
				return false;
			}

			if (port.setParams(baudRate, dataBits, stopBits, parity)) {
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			throw new IOException(e);
		}
	}

	/**
	 * The main simple connect - it attempts to connect to one of the known
	 * ports in memory if that fails it will try to connect to a hardware port
	 * 
	 * TODO - make "connecting" to pre-existing ports re-entrant !!!
	 * 
	 * connect - optimized to have a SerialDataListener passed in - which will
	 * optimize data streaming back from the port.
	 * 
	 * preference is to have this local optimizaton
	 * 
	 * connect = open + listen
	 * 
	 * @param inPortName
	 * @param listener
	 * @return
	 * @throws IOException
	 * @throws Exception
	 */
	public void open(String inPortName, int baudrate, int databits, int stopbits, int parity) throws IOException {

		info("connect to port %s %d|%d|%d|%d", inPortName, baudrate, databits, stopbits, parity);
		this.baudrate = baudrate;
		this.databits = databits;
		this.stopbits = stopbits;
		this.parity = parity;

		// two possible logics to see if we are connected - look at the
		// state of the port
		// on the static resource - or just check to see if its on the
		// "connectedPort" set

		// #1 check to see if were already connected a port
		// if (this.portName != null && this.portName.equals(portName) &&
		// ports.containsKey(portName)) {
		if (this.portName != null) {
			Port port = ports.get(portName);
			if (port.isOpen() && port.isListening()) {
				info("already connected to %s - disconnect first to reconnect", portName);
				return;
			}
		}

		// #2 connect to a pre-existing
		if (ports.containsKey(inPortName)) {
			connectPort(ports.get(inPortName), null);
			lastPortName = portName;
			return;
		}

		// #3 we dont have an existing port - so we'll try a hardware port
		// connect at defaullt parameters - if you need custom parameters
		// create the hardware port first
		Port port = createHardwarePort(inPortName, baudrate, databits, stopbits, parity);
		if (port == null) {
			return;
		}

		connectPort(port, null);

		lastPortName = portName;
		// even when the JNI says it is connected
		// rarely is everything ready to go
		// give us half a second for all the buffers
		// & hardware to be ready
		// sleep(1500);
	}

	/**
	 * FIXME - implement connects to a FilePlayer details of tx/rx and timing
	 * can be part os a SerialFilePlayer implementation
	 * 
	 * @param name
	 */
	public boolean connectFilePlayer(String name) {
		//
		return false;
	}

	/**
	 * FIXME - implement Baddass loopback null/modem cable - auto creates a new
	 * Serial service and connects to it FIXME - no need for null/modem cable
	 * virtual port ?
	 * 
	 * @param name
	 */
	public boolean connectLoopback(String name) {
		// TODO - implement
		log.info("implement me");
		return false;
	}

	public Port connectPort(Port newPort, SerialDataListener listener) throws IOException {
		port = newPort;
		// portName = port.getName();
		ports.put(port.getName(), port);
		portNames.add(port.getName());
		invoke("getPortNames");

		if (listener != null) {
			listeners.put(listener.getName(), listener);
		}
		if (!port.isOpen()) {
			port.open();
		}
		port.listen(listeners);
		connectedPorts.put(port.getName(), newPort);

		// FYI !!!
		// give us a second before we advertise the port
		// is open - often the hardware or JNI buffers
		// are not ready even though we have "opened" it
		// sleep(1000);

		// invoking remote & local onConnect
		invoke("publishConnect", port.getName());
		for (String key : listeners.keySet()) {
			// NOT A GOOD OPTIMIZATION - AS THE "EVENT" IS MUCH MORE IMPORTANT
			// THAN THE SPEED OF THE DATA
			// listeners.get(key).onConnect(portName);
			send(listeners.get(key).getName(), "onConnect", port.getName());
		}

		// we have a portName and we are connected
		portName = port.getName();
		
		// save(); why?
		broadcastState();
		return port;
	}

	public boolean connectTcp(String host, int port) throws IOException {
		Port tcpPort = createTCPPort(host, port, this);
		connectPort(tcpPort, this);
		return true;
	}

	/**
	 * Dynamically create a hardware port - this method is needed to abtract
	 * away the specific hardware library. Its advantageous to have abstraction
	 * when interfacing with a specific implementation (JNI/JNA - other?). The
	 * abstraction allows the service to attempt to choose the correct library
	 * depending on platform or personal user choice.
	 * 
{'fraction_non_alphanumeric': 0.10190227256225949, 'fraction_numerical': 0.007768823059609381, 'mean_word_length': 3.4791869918699185, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1544831', 'n_tokens_mistral': 9037, 'n_tokens_neox': 7947, 'n_words': 2962}