/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */

package com.ericsson.ssa.container;

import com.sun.grizzly.CallbackHandler;
import com.sun.grizzly.ConnectorHandler;
import com.sun.grizzly.Controller;
import com.sun.grizzly.Controller.Protocol;
import com.sun.grizzly.TCPSelectorHandler;
import com.sun.grizzly.util.ByteBufferInputStream;
import com.sun.grizzly.util.OutputWriter;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

/**
 * Simple <code>ConnectorHandler</code> used to handle outbout write from
 * an already connected connection.
 * 
 * @author Jeanfrancois Arcand
 */
public class TCPOutboundConnectorHandler
        implements ConnectorHandler<TCPSelectorHandler, CallbackHandler>{

    /**
     * The connection's SocketChannel.
     */
    private SocketChannel socketChannel;
    
    
    /**
     * The underlying TCPSelectorHandler used to mange SelectionKeys.
     */
    private TCPSelectorHandler selectorHandler;
    
    
    /**
     * A <code>CallbackHandler</code> handler invoked by the TCPSelectorHandler
     * when a non blocking operation is ready to be processed.
     */
    private CallbackHandler callbackHandler;    
        
    /**
     * A blocking <code>InputStream</code> that use a pool of Selector
     * to execute a blocking read operation.
     */
    private ByteBufferInputStream inputStream = 
            inputStream = new ByteBufferInputStream();
    
    
    /**
     * The internal Controller used (in case not specified).
     */
    private Controller controller;   
    
    
    public TCPOutboundConnectorHandler(){
    }
    
    /**
     * A token decribing the protocol supported by an implementation of this
     * interface
     * @return this <code>ConnectorHandler</code>'s protocol
     */    
    public Protocol protocol() {
        return Protocol.TCP;
    }

    /**
     * Read bytes. If blocking is set to <tt>true</tt>, a pool of temporary
     * <code>Selector</code> will be used to read bytes.
     * @param byteBuffer The byteBuffer to store bytes.
     * @param blocking <tt>true</tt> if a a pool of temporary Selector
     *        is required to handle a blocking read.
     * @return number of bytes read
     * @throws java.io.IOException
     */    
    public long read(ByteBuffer byteBuffer, boolean blocking) throws IOException {
        SelectionKey key = socketChannel.keyFor(selectorHandler.getSelector());
        if (blocking){
            inputStream.setSelectionKey(key);
            return inputStream.read(byteBuffer);
        } else {
            if (callbackHandler == null){
                throw new IllegalStateException
                        ("Non blocking read needs a CallbackHandler");
            }
            int nRead = socketChannel.read(byteBuffer);
            
            if (nRead == 0){
                key.attach(callbackHandler);
                selectorHandler.register(key,SelectionKey.OP_READ);
            }
            return nRead;
        }    
    }

    /**
     * Writes bytes. If blocking is set to <tt>true</tt>, a pool of temporary
     * <code>Selector</code> will be used to writes bytes.
     * @param byteBuffer The byteBuffer to write.
     * @param blocking <tt>true</tt> if a a pool of temporary Selector
     *        is required to handle a blocking write.
     * @return number of bytes written
     * @throws java.io.IOException
     */    
    public long write(ByteBuffer byteBuffer, boolean blocking) throws IOException {
        if (blocking){
            return OutputWriter.flushChannel(socketChannel,byteBuffer);
        } else {
            
            if (callbackHandler == null){
                throw new IllegalStateException
                        ("Non blocking write needs a CallbackHandler");
            }
            
            SelectionKey key = socketChannel.keyFor(selectorHandler.getSelector());
            int nWrite = 1;
            int totalWriteBytes = 0;
            while (nWrite > 0 && byteBuffer.hasRemaining()){
                nWrite = socketChannel.write(byteBuffer);
                totalWriteBytes += nWrite;
            }
            
            if (totalWriteBytes == 0 && byteBuffer.hasRemaining()){
                key.attach(callbackHandler);
                selectorHandler.register(key,SelectionKey.OP_WRITE);
            }
            return totalWriteBytes;
        }
    }

    
    /**
     * Close the underlying connection.
     */
    public void close() throws IOException{
        if (socketChannel != null){
            if (selectorHandler != null){
                SelectionKey key =
                        socketChannel.keyFor(selectorHandler.getSelector());
                
                if (key == null) return;
                
                key.cancel();
                key.attach(null);
                
                selectorHandler.closeChannel(socketChannel);
            } else {
                socketChannel.close();
            }
        }
    }

    
    public void setController(Controller controller) {
        this.controller = controller;
    }

    
    public Controller getController() {
        return controller;
    }
    
    
    public void setSelectorHandler(TCPSelectorHandler selectorHandler) {
        this.selectorHandler = selectorHandler;
    }
    
    
    public TCPSelectorHandler getSelectorHandler() {
        return selectorHandler;
    }

    
    public void setUnderlyingChannel(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }
                
    public SelectableChannel getUnderlyingChannel() {
        return socketChannel;
    }

    
    public CallbackHandler getCallbackHandler() {
        return callbackHandler;
    }

    
    public void setCallbackHandler(CallbackHandler callbackHandler) {
        this.callbackHandler = callbackHandler;
    }

    // -------------------------------------------------------------------- //
    // Not Supported Operations.
    
    public void connect(SocketAddress local, CallbackHandler remote, TCPSelectorHandler selectorHandler) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    public void connect(SocketAddress local, CallbackHandler remote) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    public void connect(SocketAddress local) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    public void connect(SocketAddress local, SocketAddress remote, CallbackHandler ch, TCPSelectorHandler selectorHandler) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    public void connect(SocketAddress local, SocketAddress remote, CallbackHandler ch) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    public void connect(SocketAddress local, SocketAddress remote) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    public void finishConnect(SelectionKey key) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

}
