import ceylon.buffer {
    ByteBuffer
}

"Represents a [[FileDescriptor]] that you can `select`. 
 This means that you can register listeners for this file 
 descriptor on a given [[Selector]] object that will be 
 called whenever there is data available to be read or 
 written without blocking the reading/writing thread."
by("Stéphane Épardaud")
shared sealed interface SelectableFileDescriptor 
        satisfies FileDescriptor {

    "Register a reading listener to the given [[selector]]. 
     The reading listener will be invoked by the [[Selector]] 
     whenever data can be read from this file descriptor 
     without blocking.
     
     If you are no longer interested in `read` events from 
     the selector, you should return `false` from your 
     listener when invoked."
    see(`interface Selector`)
    shared void readAsync(Selector selector, 
            void consume(ByteBuffer buffer), 
            ByteBuffer buffer = newBuffer()) {
        blocking = false;
        selector.addConsumer(this, (socket) {
            buffer.clear();
            if(socket.read(buffer) >= 0) {
                buffer.flip();
                // FIXME: should the consumer be allowed to stop us?
                consume(buffer);
                return true;
            }else{
                // EOF
                return false;
            }
        });
    }

    "Register a writing listener to the given [[selector]]. 
     The writing listener will be invoked by the [[Selector]] 
     whenever data can be written to this file descriptor 
     without blocking.
     
     If you are no longer interested in `write` events from 
     the selector, you should return `false` from your 
     listener when invoked."
    see(`interface Selector`)
    shared void writeAsync(Selector selector, 
            void producer(ByteBuffer buffer), 
            ByteBuffer buffer = newBuffer()) {
        blocking = false;
        variable Boolean needNewData = true;
        Boolean writeData(FileDescriptor socket) {
            // get new data if we ran out
            if(needNewData) {
                buffer.clear();
                producer(buffer);
                // flip it for reading
                buffer.flip();
                if(!buffer.hasAvailable) {
                    // EOI
                    return false;
                }
                needNewData = false;
            }
            // try to write it
            if(socket.write(buffer) >= 0) {
                // did we manage to write everything?
                needNewData = !buffer.hasAvailable;
                return true;
            }else{
                // EOF
                return false;
            }
        }
        selector.addProducer(this, writeData);
    }

    // FIXME: revisit, pull up
    "The blocking mode."
    shared formal variable Boolean blocking;
}