/**
 *
 * Copyright 2004 Hiram Chirino
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.activeio.net;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import junit.framework.TestCase;

import org.activeio.Channel;
import org.activeio.ChannelServer;
import org.activeio.Packet;
import org.activeio.SynchChannel;
import org.activeio.SynchChannelServer;
import org.activeio.adapter.AsynchToSynchChannelAdapter;
import org.activeio.adapter.AsynchToSynchChannelServerAdapter;
import org.activeio.packet.ByteArrayPacket;
import org.activeio.packet.EOSPacket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import EDU.oswego.cs.dl.util.concurrent.Executor;
import EDU.oswego.cs.dl.util.concurrent.QueuedExecutor;

/**
 * Used to test the {@see org.activeio.net.TcpSynchChannel}
 *  
 * @version $Revision$
 */
abstract public class SynchChannelTestSupport extends TestCase {
    
    Log log = LogFactory.getLog(SynchChannelTestSupport.class);
    private SynchChannelServer server;
    private SynchChannel clientChannel;
    private SynchChannel serverChannel;
    Executor sendExecutor = new QueuedExecutor();
    
    public void testSmallSendReceive() throws IOException, URISyntaxException, InterruptedException {
        if( isDisabled() ) {
            log.info("test disabled: "+getName());
            return;
        }
        Packet outboundPacket = new ByteArrayPacket("Hello World".getBytes());
        doSendReceive(outboundPacket.duplicate());
    }

    public void testPeerDisconnect() throws IOException, URISyntaxException, InterruptedException {
        if( isDisabled() ) {
            log.info("test disabled: "+getName());
            return;
        }
        
        Packet outboundPacket = new ByteArrayPacket("Hello World".getBytes());
        doSendReceive(outboundPacket.duplicate());
        // disconnect the client.
        clientChannel.dispose();
        
        // The server should get an EOS packet.
        Packet packet = serverChannel.read(1000);
        assertEquals(EOSPacket.EOS_PACKET, packet);        
    }

    public void testManySmallSendReceives() throws IOException, URISyntaxException, InterruptedException {
        if( isDisabled() ) {
            log.info("test disabled: "+getName());
            return;
        }
        log.info("Start of testManySmallSendReceives");
        Packet outboundPacket = new ByteArrayPacket("Hello World".getBytes());
        long start = System.currentTimeMillis();
        for( int i=0; i < getTestIterations(); i++ ) {
            doSendReceive(outboundPacket.duplicate());
        }
        long end = System.currentTimeMillis();        
        log.info("done. Duration: "+duration(start,end)+", duration per send: "+unitDuration(start, end, getTestIterations()));
    }

    private float unitDuration(long start, long end, int testIterations) {
		return duration(start,end)/testIterations;
	}

	private float duration(long start, long end) {
		return (float) (((float)(end-start))/1000.0f);
	}

	protected int getTestIterations() {
        return 1000;
    }

    protected void setUp() throws Exception {

        log.info("Running: "+getName());

        if( isDisabled() ) {
            return;
        }

        log.info("Bind to an annonymous tcp port.");
        server = AsynchToSynchChannelServerAdapter.adapt(bindChannel());
        server.start();
        log.info("Server Bound at URI: "+server.getBindURI());
                
        log.info("Client connecting to: "+server.getConnectURI());
        clientChannel = AsynchToSynchChannelAdapter.adapt( openChannel(server.getConnectURI()));
        clientChannel.start();
        SocketMetadata socket = (SocketMetadata) clientChannel.narrow(SocketMetadata.class);
        if( socket != null )
            socket.setTcpNoDelay(true);
        log.info("Get connection that was accepted on the server side.");
        
        Channel c = server.accept(1000*5);
	assertNotNull(c);
         
        serverChannel = AsynchToSynchChannelAdapter.adapt(c);
        serverChannel.start();
        socket = (SocketMetadata) serverChannel.narrow(SocketMetadata.class);                
        if( socket != null ) {
            socket.setTcpNoDelay(true);
            log.info("Server Channel's Remote addreess: "+socket.getRemoteSocketAddress());
            log.info("Server Channel's Local addreess: "+socket.getLocalSocketAddress());
        }
    }
    
    /**
     * @param outboundPacket
     * @throws IOException
     * @throws URISyntaxException
     * @throws InterruptedException 
     */
    private void doSendReceive(final Packet outboundPacket) throws IOException, URISyntaxException, InterruptedException {
        ByteArrayPacket inboundPacket = new ByteArrayPacket(new byte[outboundPacket.remaining()]);

        // Do the send async.
        sendExecutor.execute( new Runnable() {
            public void run() {
                try {
                    clientChannel.write(outboundPacket);
                    clientChannel.flush();
                } catch (IOException e) {
                }
            }
        });
        
        while( inboundPacket.hasRemaining() ) {
	        Packet packet = serverChannel.read(1000*5);
	        assertNotNull(packet);
	        packet.read(inboundPacket);
        }        
        outboundPacket.clear();
        inboundPacket.clear();
        assertEquals(outboundPacket.sliceAsBytes(), inboundPacket.sliceAsBytes());
                
    }

    protected void tearDown() throws Exception {
        if( isDisabled() ) {
            return;
        }
        log.info("Closing down the channels.");
        serverChannel.dispose();        
        clientChannel.dispose();
        server.dispose();
    }
    
    protected boolean isDisabled() {
        return false;
    }

    public void assertEquals(byte []b1, byte[] b2 ) {
        assertEquals(b1.length, b2.length);
        for (int i = 0; i < b2.length; i++) {
            assertEquals(b1[i], b2[i]);
        }
    }
    
    abstract protected Channel openChannel(URI connectURI) throws IOException ;
    abstract protected ChannelServer bindChannel() throws IOException, URISyntaxException;
    
    
}
