/*
 * ***********************************
 * ASL Eye Tracker Serial Driver in Java
 * ***********************************
 *
 * Written by Patryk Laurent.  Copyright (C) 2009 Patryk Laurent.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Dependencies:  Uses the RXTX java io library.
 * To test it out, just run it from the command line (main will fire things up.)
 *
 */
package net.pakl.thesis.drivers;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import java.math.BigInteger;

import net.pakl.thesis.util.*;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Java-to-Serial-Binary Driver to interface with ASL Eye Tracker and get gaze.
 *
 * @author patryk
 */
public class EyeTrackerDriver
{
    public final MessageQueue data = new MessageQueue();
    public int BAUD_RATE = 57600;
    public char CHARACTER_INDICATING_MESSAGE_START = '-';
    public boolean DEBUG_SEND_BYTES_TOO = false;

    /** Connects to serial port and returns message queue that will contain the data. */
    public MessageQueue connect(String serialPortName) throws Exception
    {
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName);
        if (portIdentifier.isCurrentlyOwned())
        {
            System.out.println("Error: Port is currently in use");
            
            throw new RuntimeException("Sorry - selected port is currently in use.");
        }
        else
        {
            CommPort commPort = portIdentifier.open(this.getClass().getName(), 2000);

            if (commPort instanceof SerialPort)
            {
                SerialPort serialPort = (SerialPort) commPort;
                serialPort.setSerialPortParams(BAUD_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                //serialPort.setSerialPortParams(9600,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);

                InputStream in = serialPort.getInputStream();
                OutputStream out = serialPort.getOutputStream();

                (new Thread(new SerialReader(in))).start();
                (new Thread(new SerialWriter(out))).start();

                return data;
            }
            else
            {
                System.out.println("Error: Only serial ports are handled by this example.");
            }
        }
        throw new RuntimeException("Could not connect to the requested port.");
    }

    private static boolean getBit(int b, int pos)
    {
        String s = Integer.toBinaryString(b);
        if (pos > s.length()-1) return false;
        if (s.charAt(s.length()-pos-1) == '1') return true;
        return false;
    }


    /** */
    public class SerialReader implements Runnable
    {

        InputStream in;

        public SerialReader(InputStream in)
        {
            this.in = in;
        }
        int byteCounter = 0;
        int xMSB, xLSB, yMSB, yLSB, overflow1, overflow2;

        public void run()
        {
            byte[] buffer = new byte[1024];
            int withinMessageCounter = 0;

            int len = -1;
            try
            {
                while ((len = this.in.read(buffer)) > -1)
                {
                    for (int i = 0; i < len; i++)
                    {
                        if (DEBUG_SEND_BYTES_TOO) data.put(buffer[i]);

                        withinMessageCounter++;

                        if (withinMessageCounter == 4) xMSB = buffer[i];
                        if (withinMessageCounter == 5) xLSB = buffer[i];
                        if (withinMessageCounter == 6) yMSB = buffer[i];
                        if (withinMessageCounter == 7) overflow1 = buffer[i];
                        if (withinMessageCounter == 8) yLSB = buffer[i];

                        if (withinMessageCounter == 9)
                        {
                            overflow2 = buffer[i];

                            // Decode the bytes.
                            if (getBit(overflow1, 4)) xMSB = xMSB | (1<<7);
                            if (getBit(overflow1, 5)) xLSB = xLSB | (1<<7);
                            if (getBit(overflow1, 6)) yMSB = yMSB | (1<<7);
                            if (getBit(overflow2, 0)) yLSB = yLSB | (1<<7);


                            ASLData d = new ASLData();
                            d.gazeX = xMSB * 256 + xLSB;
                            d.gazeY = yMSB * 256 + yLSB;
                            d.timestamp = System.currentTimeMillis();
                            data.put(d);
                        }

//                        if (byteToString(buffer[i]).charAt(0) == CHARACTER_INDICATING_MESSAGE_START)
//                        {
//                            withinMessageCounter = 0;
//                        }

                        if (getBit(buffer[i], 7))       // leftmost bit is 1?
                        {
                            withinMessageCounter = 0;
                        }

                    }
                }
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }

    /** */
    public class SerialWriter implements Runnable
    {

        OutputStream out;

        public SerialWriter(OutputStream out)
        {
            this.out = out;
        }

        public void run()
        {
            try
            {
                int c = 0;
                while ((c = System.in.read()) > -1)
                {
                    this.out.write(c);
                }
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }

    public static List<String> listCommPorts()
    {
        ArrayList<String> result = new ArrayList<String>();
        try
        {
            java.util.Enumeration pList = CommPortIdentifier.getPortIdentifiers();
            // Process the list.
            while (pList.hasMoreElements())
            {
                CommPortIdentifier cpi = (CommPortIdentifier) pList.nextElement();
                System.out.print("Port " + cpi.getName() + " ");
                if (cpi.getPortType() == CommPortIdentifier.PORT_SERIAL)
                {
                    System.out.println("is a Serial Port: " + cpi);
                    result.add(cpi.getName());
                }
                else
                {
                    if (cpi.getPortType() == CommPortIdentifier.PORT_PARALLEL)
                    {
                        System.out.println("is a Parallel Port: " + cpi);
                    }
                    else
                    {
                        System.out.println("is an Unknown Port: " + cpi);
                    }
                }
            }
        }
        catch (java.lang.UnsatisfiedLinkError ule)
        {
            ule.printStackTrace();
            System.err.println("-------------------------------------------------------");
            System.err.println("You must use -Djava.library.path= and specify the");
            System.err.println("location of the rxtxSerial libraries (i.e., DLLs in ");
            System.err.println("Windows or of librxtxSerial.jnilib in Mac OS X)");
            System.err.println("-------------------------------------------------------");
        }

        if (result.size() == 0)
        {
            result.add("can't access serial library");
        }
        return result;
    }

    public static String byteToString(byte b)
    {
        byte[] subbuffer = new byte[1];
        subbuffer[0] = b;
        BigInteger bi = new BigInteger(subbuffer);
        String s = bi.toString(2);

        switch (s.length())
        {
            case 1:
                s = "0" + s;
            case 2:
                s = "0" + s;
            case 3:
                s = "0" + s;
            case 4:
                s = "0" + s;
            case 5:
                s = "0" + s;
            case 6:
                s = "0" + s;
            case 7:
                s = "0" + s;
        }
        return s;
    }

    public static void main(String args[])
    {
        if (args.length < 1)
        {
            listCommPorts();
            System.err.println("-------------------------------------------------------");
            System.err.println("Error: Please pass a serial port on the command line.");
            System.err.println("-------------------------------------------------------");
            return;
        }
        
        String serial = args[0];

        try
        {
            EyeTrackerDriver e = new EyeTrackerDriver();
            e.connect(serial);
            while (true)
            {
                if (!e.data.isEmpty())
                {
                    Object o = e.data.get();
                    if (o instanceof Byte)
                    {
                        String s = byteToString((Byte) o);
                        System.out.print(s + " ");
                    }
                    else
                    {
                        ASLData data = (ASLData) o;
                        System.out.println("x = " + data.gazeX + "\ty = " + data.gazeY);
                    }

                }
            }

        } catch (Exception x)
        {
            // TODO Auto-generated catch block
            x.printStackTrace();
        }

    }
}

