package org.limewire.nio.statemachine;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.nio.NIODispatcher;
import org.limewire.nio.observer.Shutdownable;
import org.limewire.util.FileUtils;

/**
 * Blocks until the state machine finishes; use <code>BlockingStateMachine</code> 
 * as an alternative to {@link IOStateMachine}. 
 * <p>
 * <code>BlockingStateMachine</code> is useful if you want to use the same state
 * code from an NIO portion where only <code>OutputStreams</code> and 
 * <code>InputStreams</code> are available.
 */
public class BlockingStateMachine implements Closeable, Shutdownable {
    
    private static final Log LOG = LogFactory.getLog(BlockingStateMachine.class);

    /** The states this will use while handshaking.*/
    private final List<IOState> states;
    /** A reading channel wrapping an InputStream. */
    private final ReadableByteChannel readChannel;
    /** A writing channel wrapping an OutputStream. */
    private final WritableByteChannel writeChannel;
    /** The ByteBuffer to use for reading. */
    private final ByteBuffer readBuffer;
    
    public BlockingStateMachine(List<IOState> states, InputStream in, OutputStream out) {
        this(states, 2048, in, out);
    }

    public BlockingStateMachine(List<IOState> states, int bufferSize, InputStream in, OutputStream out) {
        this.states = states;
        this.readBuffer = NIODispatcher.instance().getBufferCache().getHeap(bufferSize);
        this.readChannel = Channels.newChannel(in);
        this.writeChannel = Channels.newChannel(out);
    }
    
    /**
     * Adds a new state to process.
     * 
     * @param newState
     */
    public void addState(final IOState newState) {
        if(LOG.isDebugEnabled())
            LOG.debug("Adding single state: " + newState);
        states.add(newState);
    }
    
    /** Adds a collection of new states to process. */
    public void addStates(final List<? extends IOState> newStates) {
        if(LOG.isDebugEnabled())
            LOG.debug("Adding multiple states: " + newStates);
        states.addAll(newStates);        
    }
    
    /** Adds an array of new states to process. */
    public void addStates(final IOState... newStates) {
        if(LOG.isDebugEnabled())
            LOG.debug("Adding multiple states...");
        for(int i = 0; i < newStates.length; i++) {
            if(LOG.isDebugEnabled())
                LOG.debug(" state[" + i + "]: " + newStates[i]);
            states.add(newStates[i]);
        }        
    }
    
    /** Aborts this statemachine. */
    public void close() {
        FileUtils.close(readChannel);
        FileUtils.close(writeChannel);
    }
    
    /** Releases any data the statemachine retained. */
    public void shutdown() {
        NIODispatcher.instance().getBufferCache().release(readBuffer);
    }
    
    /** Begins processing through the states. Returns only when every state is processed. */
    public void process() throws IOException {
        for(Iterator<IOState> i = states.iterator(); i.hasNext(); ) {
            IOState current = i.next();
            if(current.isReading())
                current.process(readChannel, readBuffer);
            else if(current.isWriting())
                current.process(writeChannel, null);
            else
                throw new IllegalStateException("expected reading | writing state");
            i.remove();
        }
    }
    
    
    
    
    
}
