import gnu.io.*;
import java.io.*;
import java.util.*;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.TooManyListenersException;
import org.jfree.data.time.Millisecond;

/* CAMBIOS:
 - el metodo connect(); ahora posee un argumento de entrada -> connect(String selectedport)
   de forma que la decision de que puerto usar no corresponda al comunicador en si.
 - Creacion del atributo ConfigCOM, debido a que mucha de la informacion desplegada por via "txtlog"
   requeria una referencia al panel de configuraciones, donde se piensa colocar un campo tipo consola
   (por lo que Communicator no solo queda referenciada por GUIFrame, sino tamb por ConfigCOMFrame.)
 - se elimino el metodo setwindow();

   supuestos: las clases del diagrama realizado en paint existen
 */

 /**
 * Clase que crea el objeto que maneja comunicacion serial
 * 
 */
public class Communicator implements SerialPortEventListener {
    // passed from main GUI
    private GUIFrame GUI = null;
    private ConfigCOMFrame ConfigCOM = null;

    // for containing the ports that will be found
    private Enumeration ports = null;

    // map the port names to CommPortIdentifiers
    private HashMap portMap            = new HashMap();
    private ArrayList<String> keyPorts = new ArrayList<String>();

    // this is the object that contains the opened port
    private CommPortIdentifier selectedPortIdentifier = null;
    private SerialPort serialPort = null;

    // input and output streams for sending and receiving data
    private InputStream input   = null;
    private OutputStream output = null;

    // just a boolean flag that i use for enabling
    // and disabling buttons depending on whether the program
    // is connected to a serial port or not
    private boolean bConnected = false;

    // the timeout value for connecting with the port
    final static int TIMEOUT = 2000;

    // some ascii values for for certain things
    final static int SPACE_ASCII    = 32;
    final static int DASH_ASCII     = 45;
    final static int NEW_LINE_ASCII = 10;

    // a string for recording what goes on in the program
    // this string is written to the GUI
    private String logText = "";

    // print stream for recording data to file
    private PrintStream out = null;

    // scanner for recording data from a string
    // this string contains serial data recived.
    private static String str = "";
	/**
	* Crea objeto que maneja comunicacion serial
	* @param window Frame principal de la GUI 
	* @param COM Frame que muestra informacion de comunicacion serial
	*/
    public Communicator(GUIFrame window, ConfigCOMFrame COM) {

        GUI       = window;
        ConfigCOM = COM;

        try {
            out = new PrintStream("datos.csv");
        }
        catch (FileNotFoundException f) {
        }

        str = new String();
        searchForPorts();
    }

    // Search for all the serial ports
    // pre:  none
    // post: adds all the found ports to HashMap "portMap"      R*
	/**
	* Busca todos los puertos seriales disponibles
	*/
    @SuppressWarnings("unchecked")
    private void searchForPorts() {
        keyPorts.clear();
        portMap.clear();
        ports = CommPortIdentifier.getPortIdentifiers();

        while (ports.hasMoreElements()) {
            CommPortIdentifier curPort = (CommPortIdentifier)ports.nextElement();
            // Get only serial ports
            if (curPort.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                keyPorts.add(curPort.getName());
                portMap.put(curPort.getName(), curPort);
            }
        }

        // Only for test....
        // Permite agregar entradas al listado de puertos, verificando el correcto
        // funcionamiento del ConfigCOM.
/*      int i = 1;
        while(i < 4)
            keyPorts.add("TEST" + i++);
*/
    }

    // Connect to the port with name "thisport"
    // pre:  ports are already found by using the searchForPorts method
    // post: the connected comm port is stored in commPort, otherwise,
    //       an exception is generated                          R*
    /**
	* Se conecta a un puerto de comunicacion serial
	* @param selectedPort Puerto seleccionado para conexion
	*/
	public void connect(String selectedPort) {
        selectedPortIdentifier = (CommPortIdentifier)portMap.get(selectedPort);
        CommPort commPort = null;
        try {
            // the method below returns an object of type CommPort
            commPort = selectedPortIdentifier.open("TigerControlPanel", TIMEOUT);
            // the CommPort object can be casted to a SerialPort object
            serialPort = (SerialPort)commPort;
            // for controlling GUI elements
            setConnected(true);
            // logging
            logText = selectedPort + " opened successfully.";
            ConfigCOM.get_txtLog().setForeground(Color.BLACK);
            ConfigCOM.get_txtLog().append(logText + "\n");
            logText = "" + serialPort.getBaudRate();
            ConfigCOM.get_txtLog().append("BaudRate: " + logText + "\n");
            logText = "" + serialPort.getDataBits();
            ConfigCOM.get_txtLog().append("DataBits: " + logText + "\n");
            logText = "" + serialPort.getStopBits();
            ConfigCOM.get_txtLog().append("StopBits: " + logText + "\n");
            logText = "" + serialPort.getParity();
            ConfigCOM.get_txtLog().append("Parity: " + logText + "\n");
            // enables the controls on the GUI if a successful connection is made
        }
        catch (PortInUseException e) {
            logText = selectedPort + " is in use. (" + e.toString() + ")";
            ConfigCOM.get_txtLog().setForeground(Color.RED);
            ConfigCOM.get_txtLog().append(logText + "\n");
        }
        catch (Exception e) {
            logText = "Failed to open " + selectedPort + " (" + e.toString() + ")";
            ConfigCOM.get_txtLog().setForeground(Color.RED);
            ConfigCOM.get_txtLog().append(logText + "\n");
        }
    }

    // open the input and output streams
    // pre:  an open port
    // post: initialized intput and output streams for use to communicate data    R*
	/**
	* Inicia la salida y entrada de datos en un puerto serial
	* @return confirmacion de apertura
	*/
    public boolean initIOStream() {
        // return value for whather opening the streams is successful or not
        boolean successful = false;
        try {
            input  = serialPort.getInputStream();
            output = serialPort.getOutputStream();
            successful = true;
            return successful;
        }
        catch (IOException e) {
            logText = "I/O Streams failed to open. (" + e.toString() + ")";
            ConfigCOM.get_txtLog().setForeground(Color.RED);
            ConfigCOM.get_txtLog().append(logText + "\n");
            return successful;
        }
    }

    // starts the event listener that knows whenever data is available to be read
    // pre:  an open serial port
    // post: an event listener for the serial port that knows when data is recieved   R*
    /**
	* Inicia los listener que saben cuando los datos estan listos para ser leidos
	*/
	public void initListener() {
        try {
            serialPort.addEventListener(this);
            serialPort.notifyOnDataAvailable(true);
        }
        catch (TooManyListenersException e) {
            logText = "Too many listeners. (" + e.toString() + ")";
            ConfigCOM.get_txtLog().setForeground(Color.RED);
            ConfigCOM.get_txtLog().append(logText + "\n");
        }
    }

    // disconnect the serial port
    // pre:  an open serial port
    // post: clsoed serial port                                     R*
	
	/**
	* Desconecta puerto y termina transmision serial
	*/
    public void disconnect() {
        // close the serial port
        try {
            writeData(0, 0);
            serialPort.removeEventListener();
            serialPort.close();
            input.close();
            output.close();
            setConnected(false);
            logText = "Disconnected.";
            ConfigCOM.get_txtLog().setForeground(Color.red);
            ConfigCOM.get_txtLog().append(logText + "\n");
        }
        catch (Exception e) {
            logText = "Failed to close " + serialPort.getName() + "(" + e.toString() + ")";
            ConfigCOM.get_txtLog().setForeground(Color.red);
            ConfigCOM.get_txtLog().append(logText + "\n");
        }
    }
	
	/**
	* Obtiene estado de conexion de puerto
	* @return estado actual de puerto serial
	*/
    final public boolean getConnected() {
        return bConnected;
    }
    
	/**
	* Ajusta bandera de estado de conexion
	* @param bConnected estado de conexion a establecer
	*/
    public void setConnected(boolean bConnected) {
        this.bConnected = bConnected;
    }

    // what happens when data is received
    // pre:  serial event is triggered
    // post: processing on the data it reads
    @Override
    public void serialEvent(SerialPortEvent evt) {
        if (evt.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
            try {
                byte singleData = (byte)input.read();
                
                if (singleData != NEW_LINE_ASCII) {
                    logText = new String(new byte[] {singleData});
                    ConfigCOM.get_txtLog().append(logText);
                    if (singleData != 13) {
                        out.print(logText);
                        str = str + logText;
                    }
                }
                else {
                    GUI.setVariables(str);
                    ConfigCOM.get_txtLog().append("\n");
                    out.print(new Millisecond().toString() + ";");
                    out.print("\n");
                    str = "";
                }
            }
            catch (Exception e) {
                logText = "Failed to read data. (" + e.toString() + ")";
                ConfigCOM.get_txtLog().setForeground(Color.red);
                ConfigCOM.get_txtLog().append(logText + "\n");
            }
        }
    }

    // *******************************************************************
    // Este metodo no es utilizado en esta aplicacion, pero se ha dejado
    // aqui en caso si alguien mas requiere de esta funcionalidad.
    //
    // Las variables utilizadas en este pertenecen al codigo original del
    // cual se extrajo esta clase.
    // *******************************************************************
    // method that can be called to send data
    // pre:  open serial port
    // post: data sent to the other device

    public void writeData(int leftThrottle, int rightThrottle) {
        try {
            output.write(leftThrottle);
            output.flush();
            
            // this is a delimiter for the data
            output.write(DASH_ASCII);
            output.flush();
            
            output.write(rightThrottle);
            output.flush();
            
            // will be read as a byte so it is a space key
            output.write(SPACE_ASCII);
            output.flush();
        }
        catch (Exception e) {
            logText = "Failed to write data. (" + e.toString() + ")";
            ConfigCOM.get_txtLog().setForeground(Color.red);
            ConfigCOM.get_txtLog().append(logText + "\n");
        }
    }


    /**
	* Obtiene nombres de puertos seriales 
	* @return nombre de puertos seriales
	*/
    public ArrayList getPortNames(){
        return keyPorts;
    }
}