package click.runtime.remote;

import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

import jist.runtime.JistAPI;
import jist.runtime.RealtimeController;

/**
 * Socked demultiplexer for click connections. Provides callbacks within an IO
 * Thread.
 *
 * @author kurth
 */
public class ControlSocketSelector implements JistAPI.DoNotRewrite, Closeable {

  public static final Logger log = Logger.getLogger(ControlSocketSelector.class.getName());

  private static final long SHUTDOWN_TIMEOUT_MS = 100;

  /** The singleton instance */
  private static ControlSocketSelector instance;

  /**
   * Starts the IO thread
   */
  public static void start() throws IOException {
    if (null != instance)
      return;
    instance = new ControlSocketSelector();
    if (null == instance.selector) {
      instance.selector = Selector.open();
      instance.ioThread = new Thread(new Runnable() {
        public void run() {
          instance.processIO();
        }
      });
      instance.ioThread.start();
    }
  }

  /**
   * Singleton access.
   * @return the singleton
   */
  public static ControlSocketSelector getInstance() {
    return instance;
  }

  /**
   * Cleanup.s
   */
  public static void releaseInstance() {
    if (null == instance)
      return;
    instance.close();
    instance = null;
  }

  private static class Entry {
    public Entry(SocketChannel channel, int ops, ClickInterfaceRemoteImpl obj) {
      this.obj = obj;
      this.ops = ops;
      sChannel = channel;
    }
    SocketChannel sChannel;
    int ops;
    ClickInterfaceRemoteImpl obj;
  }

  /** NIO selector for channels */
  private Selector selector;

  /** the IO thread */
  private Thread ioThread;

  /** stop indicator */
  private boolean stopIoThread;

  private List pendingRegisters;

  public ControlSocketSelector() {
    stopIoThread = false;
    pendingRegisters = Collections.synchronizedList(new ArrayList());
  }

  /*
   * (non-Javadoc)
   * @see java.io.Closeable#close()
   */
  public void close() {
    this.stopIoThread = true;
    if (null != selector)
      selector.wakeup();
    if (ioThread.isAlive()) {
      try {
        ioThread.join(SHUTDOWN_TIMEOUT_MS);
//        controller.join();
      } catch (InterruptedException e) {}
      if (ioThread.isAlive())
        ioThread.stop();
    }
    selector = null;
    ioThread = null;
//    controller = null;
  }

  /**
   * Register channel with selector.
   *
   * @param sChannel the channel to add.
   * @param obj the callback object.
   * @throws IOException
   */
  public static void addChannel(SocketChannel sChannel,
      ClickInterfaceRemoteImpl obj) throws IOException {
//    ControlSocketSelector instance = getInstance();
//    Entry entry = new Entry(sChannel, SelectionKey.OP_READ | SelectionKey.OP_WRITE, obj);
    instance.pendingRegisters.add(new Entry(sChannel, SelectionKey.OP_READ, obj));
    instance.selector.wakeup();
  }

  public static void addChannel(SocketChannel sChannel, int ops,
      ClickInterfaceRemoteImpl obj) throws IOException {
//    if (instance.pendingRegisters.size() == 0) {
      instance.pendingRegisters.add(new Entry(sChannel, ops, obj));
//    }
    instance.selector.wakeup();
  }

  public void register(SocketChannel channel, int ops,
      ClickInterfaceRemoteImpl obj) throws IOException {
    channel.register(selector, ops, obj);
  }

  /**
   * IO thread control loop.
   */
  protected void processIO() {
    // Wait for events
    while (!stopIoThread) {
      try {
        // Wait for an event
        selector.select();

        while (pendingRegisters.size() > 0) {
          Entry entry = (Entry) pendingRegisters.remove(0);
          entry.sChannel.register(selector, entry.ops, entry.obj);
        }
      } catch (IOException e) {
        // Handle error with selector
        e.printStackTrace();
        break;
      }

      // Get list of selection keys with pending events
      Iterator it = selector.selectedKeys().iterator();

      // Process each key at a time
      while (it.hasNext()) {
        // Get the selection key
        SelectionKey selKey = (SelectionKey)it.next();

        // Remove it from the list to indicate that it is being processed
        it.remove();

        if (!selKey.channel().isOpen()) {
          log.warn("Connection to " + selKey.attachment() + " closed");
          selKey.cancel();
        }

        try {
          if (selKey.isValid() && selKey.isReadable()) {
            SocketChannel sChannel = (SocketChannel)selKey.channel();
            ClickInterfaceRemoteImpl adapter =
              (ClickInterfaceRemoteImpl) selKey.attachment();
            adapter.readFromRemote(sChannel);
          }
          if (selKey.isValid() && selKey.isWritable()) {
            SocketChannel sChannel = (SocketChannel)selKey.channel();
            ClickInterfaceRemoteImpl adapter =
              (ClickInterfaceRemoteImpl) selKey.attachment();
            adapter.writeToRemote(sChannel);
          }
        } catch (IOException e) {
          // Handle error with channel and unregister
          selKey.cancel();
          RealtimeController.stop("IO thread encountered exception", e);
        }
      }
    }
    try {
      if (selector.isOpen())
        selector.close();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    selector = null;
  }

}
