/**
 *
 * 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.benchmark;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;

import org.activeio.AcceptListener;
import org.activeio.AsynchChannel;
import org.activeio.AsynchChannelListener;
import org.activeio.AsynchChannelServer;
import org.activeio.Channel;
import org.activeio.ChannelFactory;
import org.activeio.Packet;
import org.activeio.adapter.SynchToAsynchChannelAdapter;
import org.activeio.packet.EOSPacket;
import org.activeio.stats.CountStatisticImpl;
import org.apache.commons.beanutils.BeanUtils;

import EDU.oswego.cs.dl.util.concurrent.Latch;

/**
 * Implements a simple tcp echo server for use in benchmarking 
 * activeio channel implementations.
 * 
 * @version $Revision$
 */
public class Server implements Runnable, AcceptListener {

    private URI url;
    private Latch shutdownLatch;
    private long requestDelay = 0;
    private long sampleInterval = 1000;
    
    private final CountStatisticImpl activeConnectionsCounter = new CountStatisticImpl("activeConnectionsCounter","The number of active connection attached to the server.");
    private final CountStatisticImpl echoedBytesCounter = new CountStatisticImpl("echoedBytesCounter","The number of bytes that have been echoed by the server.");

    public static void main(String[] args) throws URISyntaxException, IllegalAccessException, InvocationTargetException {

        Server server = new Server();
        
        HashMap options = new HashMap();       
        for( int i=0; i < args.length; i++ ) {
            
            String option = args[i];
            if( !option.startsWith("-") || option.length()<2 || i+1 >= args.length ) {
                System.out.println("Invalid usage.");
                return;
            }
            
            option = option.substring(1);
            options.put(option, args[++i]);            
        }        
        BeanUtils.populate(server, options);
        
        System.out.println();
        System.out.println("Server starting with the following options: ");
        System.out.println(" url="+server.getUrl());
        System.out.println(" sampleInterval="+server.getSampleInterval());
        System.out.println(" requestDelay="+server.getRequestDelay());
        System.out.println();
        server.run();

    }

    private void printSampleData() {
        long now = System.currentTimeMillis();
        float runDuration = (now - activeConnectionsCounter.getStartTime())/1000f;
        System.out.println("Active connections: "+activeConnectionsCounter.getCount());
        System.out.println("Echoed bytes: " + (echoedBytesCounter.getCount()/1024f) + " kb");
        echoedBytesCounter.reset();
    }
    

    public void run() {
        try {
            
            activeConnectionsCounter.reset();
            echoedBytesCounter.reset();
            
            shutdownLatch = new Latch();
                
            ChannelFactory factory = new ChannelFactory();
            AsynchChannelServer server = factory.bindAsynchChannel(url);
            System.out.println("Server accepting connections on: "+server.getConnectURI());
            server.setAcceptListener(this);
            server.start();
            
            while(!shutdownLatch.attempt(sampleInterval)) {
                printSampleData();
            }
            
            System.out.println("Stopping server.");
            server.stop(1000*5);
            server.dispose();
            
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
        }
    }

    public String getUrl() {
        return url.toString();
    }

    public void setUrl(String url) throws URISyntaxException {
        this.url = new URI(url);
    }

    class ServerConnectionHandler implements AsynchChannelListener {

        private final AsynchChannel asynchChannel;
        private boolean disposed;
        
        public ServerConnectionHandler(AsynchChannel asynchChannel) {
            this.asynchChannel = asynchChannel;
            activeConnectionsCounter.increment();
        }

        public void onPacket(Packet packet) {
            
            if( packet == EOSPacket.EOS_PACKET ) {
                System.out.println("Peer disconnected.");
                dispose();
                return;
            }
            
            try {
                if( requestDelay > 0 ) {
                    Thread.sleep(requestDelay);
                }
                
                echoedBytesCounter.add(packet.remaining());
                asynchChannel.write(packet);
                asynchChannel.flush();
                
            } catch (IOException e) {
                onPacketError(e);
            } catch (InterruptedException e) {
                System.out.println("Interrupted... Shutting down.");
                dispose();
            }
        }

        public void onPacketError(IOException error) {
            error.printStackTrace();
            dispose();
        }

        private void dispose() {
            if( !disposed ) {
                asynchChannel.dispose();
                activeConnectionsCounter.decrement();
                disposed=true;
            }
        }
    }
    
    public void onAccept(Channel channel) {
        try {
            
            AsynchChannel asynchChannel = SynchToAsynchChannelAdapter.adapt(channel);
            asynchChannel.setAsynchChannelListener(new ServerConnectionHandler(asynchChannel));
            asynchChannel.start();
            
        } catch (IOException e) {
            onAcceptError(e);
        }
    }

    public void onAcceptError(IOException error) {
        error.printStackTrace();
        shutdownLatch.release();
    }
    
    /**
     * @return Returns the requestDelay.
     */
    public long getRequestDelay() {
        return requestDelay;
    }
    /**
     * @param requestDelay The requestDelay to set.
     */
    public void setRequestDelay(long requestDelay) {
        this.requestDelay = requestDelay;
    }
    /**
     * @return Returns the sampleInterval.
     */
    public long getSampleInterval() {
        return sampleInterval;
    }
    /**
     * @param sampleInterval The sampleInterval to set.
     */
    public void setSampleInterval(long sampleInterval) {
        this.sampleInterval = sampleInterval;
    }
}
