package org.nio4r;

import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel;

import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.load.Library; import org.jruby.runtime.builtin.IRubyObject;

import org.nio4r.ByteBuffer; import org.nio4r.Monitor; import org.nio4r.Selector;

public class Nio4r implements Library {

private Ruby ruby;

public void load(final Ruby ruby, boolean bln) {
    this.ruby = ruby;

    RubyModule nio = ruby.defineModule("NIO");

    RubyClass selector = ruby.defineClassUnder("Selector", ruby.getObject(), new ObjectAllocator() {
        public IRubyObject allocate(Ruby ruby, RubyClass rc) {
            return new Selector(ruby, rc);
        }
    }, nio);

    selector.defineAnnotatedMethods(Selector.class);

    RubyClass monitor = ruby.defineClassUnder("Monitor", ruby.getObject(), new ObjectAllocator() {
        public IRubyObject allocate(Ruby ruby, RubyClass rc) {
            return new Monitor(ruby, rc);
        }
    }, nio);

    monitor.defineAnnotatedMethods(Monitor.class);

    RubyClass byteBuffer = ruby.defineClassUnder("ByteBuffer", ruby.getObject(), new ObjectAllocator() {
        public IRubyObject allocate(Ruby ruby, RubyClass rc) {
            return new ByteBuffer(ruby, rc);
        }
    }, nio);

    byteBuffer.defineAnnotatedMethods(ByteBuffer.class);
    byteBuffer.includeModule(ruby.getEnumerable());

    ruby.defineClassUnder("OverflowError",  ruby.getIOError(), ruby.getIOError().getAllocator(), byteBuffer);
    ruby.defineClassUnder("UnderflowError", ruby.getIOError(), ruby.getIOError().getAllocator(), byteBuffer);
    ruby.defineClassUnder("MarkUnsetError", ruby.getIOError(), ruby.getIOError().getAllocator(), byteBuffer);
}

public static int symbolToInterestOps(Ruby ruby, SelectableChannel channel, IRubyObject interest) {
    if(interest == ruby.newSymbol("r")) {
        if((channel.validOps() & SelectionKey.OP_ACCEPT) != 0) {
          return SelectionKey.OP_ACCEPT;
        } else {
          return SelectionKey.OP_READ;
        }
    } else if(interest == ruby.newSymbol("w")) {
        if(channel instanceof SocketChannel && !((SocketChannel)channel).isConnected()) {
            return SelectionKey.OP_CONNECT;
        } else {
            return SelectionKey.OP_WRITE;
        }
    } else if(interest == ruby.newSymbol("rw")) {
        int interestOps = 0;

        /* nio4r emulates the POSIX behavior, which is sloppy about allowed modes */
        if((channel.validOps() & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0) {
            interestOps |= symbolToInterestOps(ruby, channel, ruby.newSymbol("r"));
        }

        if((channel.validOps() & (SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT)) != 0) {
            interestOps |= symbolToInterestOps(ruby, channel, ruby.newSymbol("w"));
        }

        return interestOps;
    } else {
        throw ruby.newArgumentError("invalid interest type: " + interest);
    }
}

public static IRubyObject interestOpsToSymbol(Ruby ruby, int interestOps) {
    switch(interestOps) {
        case SelectionKey.OP_READ:
        case SelectionKey.OP_ACCEPT:
            return ruby.newSymbol("r");
        case SelectionKey.OP_WRITE:
        case SelectionKey.OP_CONNECT:
            return ruby.newSymbol("w");
        case SelectionKey.OP_READ | SelectionKey.OP_CONNECT:
        case SelectionKey.OP_READ | SelectionKey.OP_WRITE:
            return ruby.newSymbol("rw");
        case 0:
            return ruby.getNil();
        default:
            throw ruby.newArgumentError("unknown interest op combination");
    }
}

}