package org.activeio.adapter;

import java.io.IOException;
import java.io.InterruptedIOException;

import org.activeio.AsynchChannel;
import org.activeio.ChannelFactory;
import org.activeio.FilterAsynchChannel;
import org.activeio.Packet;

import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
import EDU.oswego.cs.dl.util.concurrent.Channel;
import EDU.oswego.cs.dl.util.concurrent.Executor;
import EDU.oswego.cs.dl.util.concurrent.Latch;
import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;

public class AsynchWriteAsynchChannelAdapter extends FilterAsynchChannel {
    
    static public class ObjectDispatcherX implements Runnable {
        
        private final Executor executor;
        private final Channel queue;
        private final SynchronizedInt size = new SynchronizedInt(0);
        private final AsynchWriteAsynchChannelAdapter objectListener; 
        private long pollDelay=10;
        
        public ObjectDispatcherX(AsynchWriteAsynchChannelAdapter objectListener) {
            this(objectListener, 10);
        }

        public ObjectDispatcherX(AsynchWriteAsynchChannelAdapter objectListener, int queueSize) {
            this(objectListener, ChannelFactory.DEFAULT_EXECUTOR, new BoundedBuffer(queueSize));
        }

        public ObjectDispatcherX(AsynchWriteAsynchChannelAdapter objectListener, Executor executor, Channel queue) {
            this.objectListener = objectListener;
            this.executor = executor;
            this.queue=queue;
        }
        
        public void add(Object o) throws InterruptedException {
            int t = size.increment();
            queue.put(o);
            if( t==1 ) {
                executor.execute(this);
            }
        }

        synchronized public void run() {
            int t = size.get();
            while( t > 0 ) {
                int count=0;
                try {
                    Object o;
                    while( (o=queue.poll(pollDelay))!=null ) {
                        count++;
                        objectListener.onObject(o);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                } finally {
                    t = size.subtract(count);
                }
            }                
        }

    }
    static public class ObjectDispatcher {
        
        private final PooledExecutor executor;
        private final AsynchWriteAsynchChannelAdapter objectListener;
        
        public ObjectDispatcher(AsynchWriteAsynchChannelAdapter objectListener) {
            this(objectListener, 10);
        }

        public ObjectDispatcher(AsynchWriteAsynchChannelAdapter objectListener, int queueSize) {
            this.objectListener = objectListener;
            executor = new PooledExecutor(new BoundedBuffer(queueSize), 1);
            executor.waitWhenBlocked();
        }
        
        public void add(final Object o) throws InterruptedException {
            executor.execute(new Runnable(){
                public void run() {
                    objectListener.onObject(o);
                }
            }); 
        }
    }

    private final ObjectDispatcher dispatcher;
    private static final Object FLUSH_COMMAND = new Object();
    
    public AsynchWriteAsynchChannelAdapter(AsynchChannel next) {
        this(next, 10);
    }
    
    public AsynchWriteAsynchChannelAdapter(AsynchChannel next, int queueSize) {
        super(next);
        this.dispatcher = new ObjectDispatcher(this, queueSize);
    }

    public void onObject(Object o) {
        try {
            if( o == FLUSH_COMMAND ) {
                next.flush();
                return;
            }
            if( o.getClass() == Latch.class ) {
                next.flush();
                ((Latch)o).release();
                return;
            }
            next.write((Packet)o);
        } catch (IOException e) {
            channelListener.onPacketError(e);
        }
    }
            
    public void write(Packet packet) throws IOException {
        try {
            dispatcher.add(packet);
        } catch (InterruptedException e) {
            throw new InterruptedIOException();
        }
    }

    public void flush() throws IOException {
        flush(NO_WAIT_TIMEOUT);
    }
    
    public void stop(long timeout) throws IOException {
        flush(WAIT_FOREVER_TIMEOUT);        
    }
    

    /**
     * @param timeout
     * @throws InterruptedIOException
     */
    private void flush(long timeout) throws InterruptedIOException {
        try {
            if( timeout == NO_WAIT_TIMEOUT ) {
                dispatcher.add(FLUSH_COMMAND);
            } else if( timeout == WAIT_FOREVER_TIMEOUT ) {
                Latch l = new Latch();
                dispatcher.add(l);
                l.acquire();
            } else {
                Latch l = new Latch();
                dispatcher.add(l);
                l.attempt(timeout);
            }
        } catch (InterruptedException e) {
            throw new InterruptedIOException();
        }
    }
}
