package org.javascool.proglets.commSerie;

/**
 *  Adaptation for JavaScool of the PSerial - class for serial port goodness
 *  Part of the Processing project - http://processing.org
 *  Copyright (c) 2004-05 Ben Fry & Casey Reas
 */

import gnu.io.CommPortIdentifier;
import java.util.Enumeration;
import gnu.io.SerialPort;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.IOException;
import gnu.io.SerialPortEventListener;
import gnu.io.SerialPortEvent;
import gnu.io.CommPortOwnershipListener;
import java.util.ArrayList;

/** Définit les primitives pour s'interfacer avec un port série.
 <p>Les erreurs d'entrée-sortie sont affichée dans la console (<tt>System.out</tt>).</p>
 <p>Ce code utilise <a href="http://www.jcontrol.org">RXTX</a> (documenté <a href="http://www.jcontrol.org/docs/api">ici</a>)
 *   et s'est largement inspiré du travail de l'outil proposé par <a href="http://processing.org/reference/libraries/serial/index.html">processing</a>.</p>
 <p>Note: La librairie  <a href="http://www.jcontrol.org">RXTX</a> est une bonne alternative
 *   à <a href="http://download.oracle.com/docs/cd/E17802_01/products/products/javacomm/reference/api">javax.comm</a>
 *   analysée <a href="http://www.javaworld.com/javaworld/jw-05-1998/jw-05-javadev.html">ici</a>.</p>
 @see <a href="SerialInterface.java.html">code source</a>
 @serial exclude
 */
public class SerialInterface {
  /** Définit le nom du port série.
   @param name Le nom du port, "COM1" par défaut.
   @return Cet objet permettant la construction <tt>new SerialInterface().setName(..</tt>.
   */
  public SerialInterface setName(String name) {
	this.name = name;
	return this;
  }
  /** Renvoie le nom du port série. */
  public String getName() {
	return name;
  }
  private String name = "COM1";

  /** Définit le débit du port série.
   @param rate Le débit du port en bauds (bits par seconde), 9600 par défaut.
   @return Cet objet permettant la construction <tt>new SerialInterface().setRate(..</tt>.
   */
  public SerialInterface setRate(int rate) {
	this.rate = rate;
	return this;
  }
  /** Renvoie le débit du port série. */
  public int getRate() {
	return rate;
  }
  private int rate = 9600;

  /** Définit la parité du port série.
   @param parity La parité du port : 'N' (par défaut) si pas de parité, 'E' pour even si parité paire, 'O' pour odd si parité impaire.
   @return Cet objet permettant la construction <tt>new SerialInterface().setParity(..</tt>.
   */
  public SerialInterface setParity(char parity) {
	this.parity = parity;
	return this;
  }
  /** Renvoie la parité du port série. */
  public int getParity() {
	return parity;
  }
  private char parity = 'N';

  /** Définit la taille des mots du port série.
   @param size La taille des mots : 8 (par défaut) ou 7..
   @return Cet objet permettant la construction <tt>new SerialInterface().setSize(..</tt>.
   */
  public SerialInterface setSize(int size) {
	this.size = size;
	return this;
  }
  /** Renvoie la taille des mots du port série. */
  public int getSize() {
	return size;
  }
  private int size = 8;

  /** Définit le nombre de bits de stop du port série.
   @param stop Le nombre de bits de stop : 1 (par défaut), 1.5 ou 2.
   @return Cet objet permettant la construction <tt>new SerialInterface().setStop(..</tt>.
   */
  public SerialInterface setStop(double stop) {
	this.stop = stop;
	return this;
  }
  /** Renvoie le nombre de bits de stop du port série. */
  public double getStop() {
	return stop;
  }
  private double stop = 1;

  /** Ouvre le port série avec les paramètres actuels.
   @return Cet objet permettant la construction <tt>new SerialInterface().set*(.. ). open()</tt>.
   */
  public SerialInterface open() {
	close();
	data.clear();
	try {
	  for(Enumeration< ? > list = CommPortIdentifier.getPortIdentifiers(); list.hasMoreElements();) {
		CommPortIdentifier id = (CommPortIdentifierlist.nextElement();
		if((id.getPortType() == CommPortIdentifier.PORT_SERIAL&& id.getName().equals(name)) {
		  open(id);
		  return this;
		}
	  throw new IOException("Impossible d'ouvrir un port série de nom :" + name + " (il n'existe pas)");
	catch(Throwable e) {
	  output = null;
	  input = null;
	  port = null;
	  System.out.println("Erreur à l'ouverture du port série \n\t" this "\n\t : " + e);
	  return this;
	}
  }
  // Opens on a given port identifier
  private void open(CommPortIdentifier idthrows Exception {
	int timeout = 5000// milliseconds
	port = (SerialPortid.open("Java'sCool commSerie", timeout);
	output = port.getOutputStream();
	input = port.getInputStream();
	id.addPortOwnershipListener(new CommPortOwnershipListener() {
								  @Override
								  public void ownershipChange(int type) {
									switch(type) {
									case PORT_OWNED :
									  System.out.println("Notice: le port vient d'être pris par javascool");
									  break;
									case PORT_UNOWNED:
									  System.out.println("Notice: le port vient d'être relaché par javascool");
									  break;
									case PORT_OWNERSHIP_REQUESTED:
									  System.out.println("Notice: une demande de prise en main du port par une autre application est émise");
									  break;
									default:
									  System.out.println("Notice: une demande de prise en main parasite est émise");
									  break;
									}
								  }
								}
								);
	port.setSerialPortParams(rate, size,
							 stop == 2.0 ? SerialPort.STOPBITS_2 : stop == 1.5 ? SerialPort.STOPBITS_1_5 : SerialPort.STOPBITS_1,
							 parity == 'E' ? SerialPort.PARITY_EVEN : parity == 'O' ? SerialPort.PARITY_ODD : SerialPort.PARITY_NONE);
	port.addEventListener(listener = new SerialPortEventListener() {
							synchronized public void serialEvent(SerialPortEvent serialEvent) {
							  if(serialEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
								try {
								  while(input.available() 0) {
									reading(input.read());
								  }
								catch(Exception e) {
								  System.out.println("Erreur à la réception d'un caractère sur le port série \n\t" this "\n\t : " + e);
								}
							  }
							}
						  }
						  );
	port.notifyOnDataAvailable(true);
  }
  private SerialPort port = null;
  private OutputStream output = null;
  private InputStream input = null;
  private Reader reader = null;
  private Writer writer = null;
  private ArrayList<Integer> data = new ArrayList<Integer>();
  private SerialPortEventListener listener = null;

  /** Ferme le port série. */
  public void close() {
	try {
	  if (listener != null) {
  port.removeEventListener();
  listener = null;
	  }
	  if(output != null) {
		output.close();
		output = null;
	  }
	  if(input != null) {
		input.close();
		input = null;
	  }
	  if(port != null) {
		port.close();
		port = null;
	  }
	catch(Exception e) {
	  output = null;
	  input = null;
	  port = null;
	  System.out.println("Erreur à la fermeture du port série \n\t" this "\n\t : " + e);
	}
  }
  /** Teste si le port est ouvert.
   @return Renvoie true si le port a été ouvert sans erreur.
   */
  public boolean isOpen() {
	return port != null;
  }
  /** Ecrit un octet sur le port série.
   @param value L'octet à écrire.
   */
  public void write(int value) {
	try {
	  if(writer != null) {
		writer.writing(value);
	  }
	  if(output == null) { 
  throw new IOException("Port série fermé ou en erreur");
	  }
	  output.write(value & 0xff);
	  output.flush();
	catch(Exception e) {
	  System.out.println("Erreur à l'écriture de '" + value + "' sur le port série \n\t" this "\n\t : " + e);
	}
  }
  /** Ecrit les octets d'une chaîne de caractère sur le port série.
   @param value Les octets à écrire. La chaîne ne doit contenir que des caractères ASCII.
   @return La valeur true, si la chaîne est bien de l'ASCII, et fausse sinon. Dans ce cas aucun caratère n'est écrit.
   */
  public boolean write(String value) {
	if(isASCII(value)) {
	  for(char c : value.toCharArray())
		write((intc);
	  return true;
	else {
	  return false;
	}
  }
  /** Teste si une  chaîne ne contient que des caractères l'ASCII.
   @param value La chaîne à tester.
   @return La valeur true, si la chaîne est bien de l'ASCII, et fausse sinon.
   */
  public static boolean isASCII(String value) {
	boolean ok = true;
	for(char c : value.toCharArray()) {
	  int v = (intc;
	  if((c < 0|| (255 < c)) {
		System.out.println("Impossible d'écrire la chaîne \"" + value + "\" qui contient le caratère non-ASCII '" + c + "'");
		ok = false;
	  }
	}
	return ok;
  }
  /** Définit un moniteur des caractères émis. */
  public interface Writer {
	/** Routine appellée à l'écriture de chaque caractère.
	 @param c Le caractère écrit.
	 */
	public void writing(int c);
  }

  /** Connecte un writer au port série.
   @param writer Un writer qui est appellé à chaque caratère écrit.
   @return Cet objet permettant la construction <tt>new SerialInterface().setWriter(..</tt>.
   */
  public SerialInterface setWriter(Writer writer) {
	this.writer = writer;
	return this;
  }
  /** Lit un octet sur le port série.
   <p>L'octet lu est retiré du buffer de lecture, après l'appel de cette fonction.</p>
   @return La valeur de l'octet à lire ou -1 si il n'y a pas d'octet à lire.
   */
  public int read() {
	if(data.size() == 0) {
	  return -1;
	else {
	  int value = data.get(0);
	  data.remove(0);
	  return value;
	}
  }
  // Lecture de l'octet à son arrivée
  private void reading(int c) {
	data.add(c);
	if(reader != null) {
	  reader.reading(c);
	}
  }
  /** Renvoie tous les octets actuellement dans le buffer.
   @return Un tableau avec tous les octets actuellement  dans le buffer.
   */
  int[] getChars() {
	int chars[] new int[data.size()], i = 0;
	for(int c : data)
	  chars[i++= c;
	return chars;
  }
  /** Définit un lecteur de caractère. */
  public interface Reader {
	/** Routine appellée à l'arrivée de chaque caractère.
	 @param c Le caractère reçu.
	 */
	public void reading(int c);
  }

  /** Connecte un reader au port série.
   @param reader Un reader qui est appellé à l'arrivée de chaque caractère en lecture.
   @return Cet objet permettant la construction <tt>new SerialInterface().setReader(..</tt>.
   */
  public SerialInterface setReader(Reader reader) {
	this.reader = reader;
	return this;
  }
  /** Ajoute des octets entrée pour simuler une lecture.
   @param string Les octets en entrée.
   */
  public void simulReading(String string) {
	if(isASCII(string)) {
	  for(char c : string.toCharArray())
		reading((intc);
	}
  }
  /** Renvoie une description des paramètres du port série. */
  public String toString() {
	return "<port name=\"" + name + "\" rate=\"" + rate + "\" parity=\"" + parity + "\" size=\"" + size + "\" stop=\"" + stop +
		   "\" open=\"" + isOpen() "\" input-buffer-size =\"" + data.size() "\"/>";
  }
  /** Renvoie la liste des noms de ports séries disponibles. */
  public static String[] getPortNames() {
	try {
	  ArrayList<String> names = new ArrayList<String>();
	  for(Enumeration< ? > list = CommPortIdentifier.getPortIdentifiers(); list.hasMoreElements();) {
  CommPortIdentifier id = (CommPortIdentifierlist.nextElement();
  show(id);
  if(id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
	names.add(id.getName());
  }
	  }
	  return names.toArray(new String[names.size()]);
	catch(Throwable e) {
	  return new String[0];
	}
  }
  // Affiche les paramètres du port.
  private static void show(CommPortIdentifier id) {
	try {
	  System.out.println("Le port " +
						 (id.getPortType() == CommPortIdentifier.PORT_SERIAL ? "(série)" :
						  id.getPortType() == CommPortIdentifier.PORT_PARALLEL ? "(parallèle)" :
						  "(de type inconnu)"+
						 " " + id.getName() " est détecté, " +
						 " il appartient à " + id.getCurrentOwner() " et est actuellement " (id.isCurrentlyOwned() "pris" "non-pris"));
	catch(Exception e) {
	  System.out.println("Le port " + id.getName() " est détecté mais génère une erreur lors de l'affichage de ses paramètres");
	}
  }
  /** Renvoie la liste des des noms de ports séries disponibles ce qui teste l'installation des librairies.
   @param usage <tt>java -cp javascool-proglets.jar org.javascool.proglets.commSerie.SerialInterface</tt>
   */
  public static void main(String usage[]) {
	String names[] = getPortNames();
	{
	  System.out.print("Il y a " + names.length + " ports série:");
	  for(String name : names)
		System.out.print(" " + name);
	  System.out.println();
	}
  }
}