package sivantoledo.nxt;
import java.util.concurrent.locks.*;

import sivantoledo.win32.SendInput;

public class OldCommunicationChannel {
  
  /* returns the last Windows error message */
  native private static String nlasterror();

  /* returns the last Windows error message */
  native private static String nenumerateserialports();

  /* returns a windows handle */
  private static int handle;
  native private static int nopen(String name);

  /* returns a windows handle to an abort event */
  private static int abortHandle;
  native private static int ngetaborthandle();
  native private static void nabort(int h);

  /* closes a windows handle */
  native private static int nclose(int handle, int abort_handle);

  /* returns an invalid handle */
  native private static int ninvalid();

  /* 
   * returns the size of a handle;
   * if not 4, the code will not work!
   */
  native private static int nhandlesize();

  /*
   *  NXT messages are limited to 59 bytes, so buffer must be
   *  at least as long.
   */
  
  native private static int nreceive(int handle, byte[] buffer, int abort_handle);
  
  /*
   * The buffer may be longer than the message that we want to send.
   */
  
  native private static int nsend   (int handle, byte[] buffer, int length, int abort_handle); 
  
  static { 
    java.io.FileOutputStream dll_f;
    java.io.InputStream      dll_r;
    try {
      System.loadLibrary("NXTCommunicationChannel"); 
    } catch (UnsatisfiedLinkError ule) {
      try {
        dll_r = SendInput.class.getClassLoader().getResourceAsStream("native/win32/NXTCommunicationChannel/release/NXTCommunicationChannel.dll"); 
        dll_f = new java.io.FileOutputStream("NXTCommunicationChannel.dll");
        byte[] buffer = new byte[8192];
        int    bytes_read = 0;
        do {
          dll_f.write(buffer, 0, bytes_read);
          bytes_read = dll_r.read(buffer);
        } while (bytes_read != -1);
        dll_r.close();
        dll_f.close();
        System.loadLibrary("NXTCommunicationChannel");
      } catch (java.io.IOException ioe) {
        System.err.println("Error encountered while trying to copy a DLL to the working directory: "
                           +ioe.getMessage());
      }
    }
    handle = ninvalid();
  }
  
  public OldCommunicationChannel(String port_name) 
    throws java.io.IOException {
    if (nhandlesize() != 4)
      throw new RuntimeException("Cannot use an int to store a Win32 HANDLE");
    //handle = nopen(port_name);
    //if (handle == ninvalid())
    //  throw new java.io.IOException("Failed to open a serial connection to the NXT ("
    //                                +nlasterror()
    //                                +")");
    handle = nopen(port_name);
    if (handle == ninvalid())
      throw new java.io.IOException(nlasterror());
    abortHandle = ngetaborthandle();
    if (abortHandle == ninvalid())
      throw new java.io.IOException(nlasterror());
    
    sendThread = new SendThread();
    sendThread.start();
  }
  
  private SendThread sendThread;
  
  private java.util.Queue sendQueue = new java.util.LinkedList();
  private Lock      lock = new ReentrantLock();
  private Condition notEmpty = lock.newCondition(); 


  public void send(byte[] buffer, int length) 
    throws java.io.IOException {
    byte[] b = new byte[length];
    for (int i=0; i<length; i++) b[i] = buffer[i];
    Log.println(">> GOING TO ENQUEUE A SEND");
    lock.lock();
    try {
      Log.println(">> ENQUEUEING SEND");
      sendQueue.add(b);
      notEmpty.signal();
    } finally {
      lock.unlock();
    }
    Log.println(">> DONE ENQUEUEING SEND");
    
    //sendQueue.notify();
    
    //Log.println("CALLING SEND");
    //int rc = nsend(handle, buffer, length);
    //Log.println("CALLING SEND (DONE)");
    //Log.println("send output = "+rc);
  }

  public void receive(byte[] buffer) 
    throws java.io.IOException {
    int rc = nreceive(handle, buffer, abortHandle);
    Log.println("recv output = "+rc);
  }

  public void close() {
    //int rc = nclose(handle);
    //Log.println("close output = "+rc);
    //if (rc != 0) throw new java.io.IOException();

    Log.println("calling done on threads");
    Log.println("calling done on threads");
    sendThread.done();
    if (listenerThread != null) listenerThread.done();
    Log.println("setting the abort event");
    nabort(abortHandle);
    Log.println("done() returned");
    try {
      Log.println("calling join()");
      sendThread.interrupt();
      if (listenerThread != null) listenerThread.interrupt();
      //listenerThread.destroy();
      sendThread.join();
      if (listenerThread != null) listenerThread.join();
    } catch ( InterruptedException ex ) {}
    Log.println("now closing the handles");
    nclose(handle, abortHandle);
    Log.println("close done");
  }
  
  public interface Listener {
    public void process(byte[] buffer, int length);
  }
  
  private Listener messageListener;
  private ListenerThread listenerThread;
  
  public synchronized void unsetMessageListener() {
    
    if (messageListener != null) {
      Log.println("unsettin old listener");
      listenerThread.done();
      Log.println("done() returned");
      try {
        Log.println("calling join()");
        sendThread.interrupt();
        //listenerThread.destroy();
        listenerThread.join();
        Log.println("join() done");
      } catch ( InterruptedException ex ) {}
    }
    messageListener = null;
  }

  public synchronized void setMessageListener(Listener l) {
    unsetMessageListener();
    messageListener = l;
    listenerThread = new ListenerThread();
    listenerThread.start();
  }
  
  private class ListenerThread extends Thread {
    private volatile boolean stopSignal = false;
    public void done() { stopSignal = true; }
    public void run() {
      byte[] buffer = new byte[256];
      while (!stopSignal) {
        //Log.println("calling receive");
        int length = nreceive(handle, buffer, abortHandle);
        //Log.println("message length = "+length);
        if (length > 0)
          messageListener.process(buffer, length);
      }
    }
  }

  private class SendThread extends Thread {
    private volatile boolean stopSignal = false;
    public void done() { stopSignal = true; }
    public void run() {
      byte[] buffer;
      while (!stopSignal) {
        lock.lock();
        try {
          while (sendQueue.isEmpty()) {            
            try {
              notEmpty.await();
            } catch (InterruptedException e) {
              Log.println("send thread interrupted");
              if (stopSignal) return;
            }
          }

          Log.println("DEQUEUEING BEFORE SEND");
          buffer = (byte[]) sendQueue.poll();
        } finally {
          lock.unlock();
        }
        Log.println("CALLING SEND");
        int rc = nsend(handle, buffer, buffer.length, abortHandle);
        Log.println("CALLING SEND (DONE)");
        Log.println("send output = "+rc);
      } 
        
    }
  }
  
  public static java.util.Map<String,String> enumerateChannels() {
    java.util.Map<String,String> map = new java.util.LinkedHashMap<String,String>();
    
    String s = nenumerateserialports();
    //Log.println(s);
    
    int eq = 0, semi = 0, current;
    String internal_name, external_name;
    
    current = 0;
    
    do {
      eq   = s.indexOf('=',current);
      semi = s.indexOf(';',current);
      
      //Log.println(current+","+eq+","+semi);

      if (eq != -1 && semi != -1) {
        internal_name = s.substring(current,eq);
        external_name = s.substring(eq+1,semi);
        map.put(internal_name, external_name);
        //Log.println(internal_name+" ~ "+external_name);
      }
      current = semi+1;
    } while (semi != -1);
    
    return map;
  }

  public static void main(String[] args) {
    //Log.println("xxx");
    //Log.println(nenumerateserialports());
    enumerateChannels();
  }
  
  public static void xmain(String[] args) {
    
    Log.println("xxx");
    byte[] buffer = new byte[256];
    
    Log.println("handlesize: "+nhandlesize());
    Log.println("invalid: "+ninvalid());

    /*
    buffer[0] = (byte) 0x00; // response required
    buffer[1] = 0x13; // read message
    buffer[2] = 0x00; // remote mailbox
    buffer[3] = 0x00; // local mailbox
    buffer[4] = 0x01; // remove message on remote mailbox

    buffer[0] = (byte) 0x80; // response not required
    buffer[1] = 0x03; // play tone
    buffer[2] = 0x00; // freq lsb
    buffer[3] = 0x04; // freq msb
    buffer[4] = 0x00; // duration lsb
    buffer[5] = 0x04; // duration msb
    */
   
    handle = nopen("//./COM14");

    if (handle == ninvalid()) {
      Log.println("Failed to open the channel");
    } else {
      Log.println("open: "+handle);
/*
      for (int i=0; i<64; i++) buffer[i] = 0;
      buffer[0] = (byte) 0x00; // system command, response required
      buffer[1] = (byte) 0x9b; // get device info
*/
      buffer[0] = (byte) 0x80;
      buffer[1] = 0x09;
      buffer[2] = 0x02;
      buffer[3] = 0x02;
      buffer[4] = 'x';
      buffer[5] = 0x00;
      buffer[6] = 0x00;
 
      for (int i=0; i<2; i++) {
        Log.printf("%02x %c\n",buffer[i], (char) buffer[i]);
      }
      //Log.println("send: "+nsend(handle, buffer, 6, ));
      /*
      int l;
      l = nreceive(handle,buffer);
      Log.println("msg length = "+ l);
      for (int i=0; i<l; i++) {
        Log.printf("%02x %c\n",buffer[i], (char) buffer[i]);
      }
      */
      /*
      l = nreceive(handle,buffer);
      Log.println("msg length = "+ l);
      for (int i=0; i<l; i++) {
        Log.printf("%02x %c\n",buffer[i], (char) buffer[i]);
      }
*/
      /*
      buffer[0] = (byte) 0x80;
      buffer[1] = 0x09;
      buffer[2] = 0x02;
      buffer[3] = 0x03;
      buffer[4] = 0x52;
      buffer[5] = 0x54;
      buffer[6] = 0x00;
      */
      //long t1 = System.currentTimeMillis();
      //while (System.currentTimeMillis() < t1 + 300) ;
      /*
      for (int i=0; i<8; i++) {
        Log.printf("%02x %c\n",buffer[i], (char) buffer[i]);
      }
      Log.println("send: "+nsend(handle, buffer, 6));
      */
      /*
      for (int i=0; i<64; i++) buffer[i] = 0;
      buffer[0] = (byte) 0x02; // response
      buffer[1] = 0x13; // to read message
      buffer[2] = 0x00; // status ok
      buffer[3] = 0x02; // mailbox 0==a
      buffer[4] = 0x03; // message length
      buffer[5] = 0x54;
      buffer[6] = 0x54;
      buffer[7] = 0x00;
      
      for (int i=0; i<8; i++) {
        Log.printf("%02x %c\n",buffer[i], (char) buffer[i]);
      }
      Log.println("send: "+nsend(handle, buffer, 64));
      */
    }  

   //nclose(handle);
  }
}
