/* Copyright (C) 2012 Brian P. Hinz
 *
 * This 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 2 of the License, or
 * (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */
package com.tigervnc.network;

import java.io.IOException;

import java.net.SocketAddress;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;

import java.util.Set;
import java.util.Iterator;

import com.tigervnc.rdr.Exception;

public class SocketDescriptor implements FileDescriptor {

  public SocketDescriptor() throws Exception {
    DefaultSelectorProvider();
    try {
      channel = SocketChannel.open();
      channel.configureBlocking(false);
      writeSelector = Selector.open();
      readSelector = Selector.open();
    } catch (IOException e) {
      throw new Exception(e.getMessage());
    }
    try {
      channel.register(writeSelector, SelectionKey.OP_WRITE);
      channel.register(readSelector, SelectionKey.OP_READ);
    } catch (java.nio.channels.ClosedChannelException e) {
      throw new Exception(e.getMessage());
    }
  }

  public void shutdown() throws IOException {
    try {
      channel.socket().shutdownInput();
      channel.socket().shutdownOutput();
    } catch(IOException e) {
      throw new IOException(e.getMessage());
    }
  }

  public void close() throws IOException {
    try {
      channel.close();
    } catch(IOException e) {
      throw new IOException(e.getMessage());
    }
  }

  private static SelectorProvider DefaultSelectorProvider() {
    // kqueue() selector provider on OS X is not working, fall back to select() for now
    String os = System.getProperty("os.name");
    if (os.startsWith("Mac OS X"))
      System.setProperty("java.nio.channels.spi.SelectorProvider","sun.nio.ch.PollSelectorProvider");
    return SelectorProvider.provider();
  }

  synchronized public int read(byte[] buf, int bufPtr, int length) throws Exception {
    int n;
    ByteBuffer b = ByteBuffer.allocate(length);
    try {
      n = channel.read(b);
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    if (n <= 0)
      return (n == 0) ? -1 : 0;
    b.flip();
    b.get(buf, bufPtr, n);
    b.clear();
    return n;

  }

  synchronized public int write(byte[] buf, int bufPtr, int length) throws Exception {
    int n;
    ByteBuffer b = ByteBuffer.allocate(length);
    b.put(buf, bufPtr, length);
    b.flip();
    try {
      n = channel.write(b);
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    b.clear();
    return n;
  }

  synchronized public int select(int interestOps, Integer timeout) throws Exception {
    int n;
    Selector selector;
    if ((interestOps & SelectionKey.OP_READ) != 0) {
      selector = readSelector;
    } else {
      selector = writeSelector;
    }
    selector.selectedKeys().clear();
    try {
      if (timeout == null) {
        n = selector.select();
      } else {
        int tv = timeout.intValue();
        switch(tv) {
        case 0:
          n = selector.selectNow();
          break;
        default:
          n = selector.select((long)tv);
          break;
        }
      }
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    return n;
  }

  public int write(ByteBuffer buf) throws Exception {
    int n = 0;
    try {
      n = channel.write(buf);
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    return n;
  }

  public long write(ByteBuffer[] buf, int offset, int length)
        throws IOException
  {
    long n = 0;
    try {
      n = channel.write(buf, offset, length);
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    return n;
  }

  public int read(ByteBuffer buf) throws IOException {
    int n = 0;
    try {
      n = channel.read(buf);
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    return n;
  }

  public long read(ByteBuffer[] buf, int offset, int length)
        throws IOException
  {
    long n = 0;
    try {
      n = channel.read(buf, offset, length);
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    return n;
  }

  public java.net.Socket socket() {
    return channel.socket();
  }

  public SocketAddress getRemoteAddress() throws IOException {
    if (isConnected())
      return channel.socket().getRemoteSocketAddress();
    return null;
  }

  public SocketAddress getLocalAddress() throws IOException {
    if (isConnected())
      return channel.socket().getLocalSocketAddress();
    return null;
  }

  public boolean isConnectionPending() {
    return channel.isConnectionPending();
  }

  public boolean connect(SocketAddress remote) throws IOException {
    return channel.connect(remote);
  }

  public boolean finishConnect() throws IOException {
    return channel.finishConnect();
  }

  public boolean isConnected() {
    return channel.isConnected();
  }

  protected void implConfigureBlocking(boolean block) throws IOException {
    channel.configureBlocking(block);
  }

  protected synchronized void implCloseSelectableChannel() throws IOException {
    channel.close();
    notifyAll();
  }

  protected void setChannel(SocketChannel channel_) {
    try {
      if (channel != null)
        channel.close();
      if (readSelector != null)
        readSelector.close();
      if (writeSelector != null)
        writeSelector.close();
      channel = channel_;
      channel.configureBlocking(false);
      writeSelector = Selector.open();
      readSelector = Selector.open();
    } catch (java.io.IOException e) {
      throw new Exception(e.getMessage());
    }
    try {
      channel.register(writeSelector, SelectionKey.OP_WRITE);
      channel.register(readSelector, SelectionKey.OP_READ);
    } catch (java.nio.channels.ClosedChannelException e) {
      System.out.println(e.toString());
    }
  }

  protected SocketChannel channel;
  protected Selector writeSelector;
  protected Selector readSelector;

}
