/**
 * Protocols test
 * Copyright (C) 2007 Bull S.A.S.
 * Contact: jonas-team@objectweb.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.protocols.test.client;

import java.lang.reflect.InvocationTargetException;
import java.rmi.RemoteException;

import javax.ejb.EJBMetaData;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;

import org.ow2.jonas.protocols.test.ejbs.MyService;
import org.ow2.jonas.protocols.test.ejbs.MyServiceHome;


/**
 * client program
 * @author Francois Waeselynck
 */
public class MyClient implements Runnable {

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		status = RUNNING;
		// System.err.println("Client " + clientName + " running.");
		int count = 0;
		long tb = 0;
		long te = 0;
		long deltaT = 0;
		long result = 0;

		while ((count <= iteration) && !Thread.currentThread().isInterrupted()) {

			/*
			 * Compute statistics every PERIOD calls
             * Stored in measures[]
			 */
			if ((count % PERIOD) == 0) {
				if (tb != 0) {
					te = System.currentTimeMillis();
					deltaT = te - tb;
					chrono += deltaT;

					measures[count / PERIOD - 1] = new Measure(clientName, te,
							1.0 * deltaT / PERIOD, 1.0 * (result - lastResult) / PERIOD);
					tb = te;
					lastResult = result;
				} else {
					tb = System.currentTimeMillis();
					te = 0;
				}
			}

			/*
			 * Call facade EJB
			 */
			try {
				result += callFacadeEJB();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace(System.err);
			} catch (NamingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			count++;

            /*
			 * Sleep for a while
			 */
			if (thinktime > 0) {
				try {
					Thread.sleep(thinktime);
				} catch (InterruptedException e) {
					// nothing to do
				}
			}

		}
		status = COMPLETE;
	}

    /**
     * invoke the EJB
     * @return
     * @throws NamingException
     * @throws RemoteException
     */
	private long callFacadeEJB() throws NamingException, RemoteException {

		long result = 0;
		if (isLookupCreateRemote) {
			InitialContext ctx = new InitialContext();
			optHome = (MyServiceHome) PortableRemoteObject
					.narrow(ctx.lookup("java:comp/env/ejb/MyService"),
							MyServiceHome.class);
			optService = optHome.create();
			result = optService.call("LookupCreateRemote");
		} else if (isCreateRemote) {
			optService = optHome.create();
			result = optService.optHomeCall("CreateRemote");
		} else if (isRemoteOnly) {
			result = optService.optCall("Remote");
		} else if (isMetaData) {
            optService = (MyService) callFacadeEJBMetaData();
            result = optService.callMetaData("MetaData");
        }
		return result;
	}

    /**
     * Uses the getEJBMetaData() method for getting the home and remote interface
     * and performs the invocation
     * @return
     * @throws NamingException
     * @throws RemoteException
     */
    private static Object callFacadeEJBMetaData() throws NamingException, RemoteException {

        Object im = null;

        InitialContext ctx = new InitialContext();
        optHome = (MyServiceHome) PortableRemoteObject
                .narrow(ctx.lookup("java:comp/env/ejb/MyService"),
                        MyServiceHome.class);

        // Identify the home & remote class
        EJBMetaData meta=null;
        try {
            meta = optHome.getEJBMetaData();
        } catch (RemoteException e) {
            e.printStackTrace(System.err);
        }

        // Narrow for the right type
        Class homeClass = meta.getHomeInterfaceClass();
        Object imHome = PortableRemoteObject.narrow(optHome, homeClass);

        // Invoke the create method
        try {
            im = homeClass.getMethod("create", (java.lang.Class[]) null).invoke(imHome, (java.lang.Object[]) null);
        } catch (IllegalArgumentException e) {
            e.printStackTrace(System.err);
        } catch (SecurityException e) {
            e.printStackTrace(System.err);
        } catch (IllegalAccessException e) {
            e.printStackTrace(System.err);
        } catch (InvocationTargetException e) {
            e.printStackTrace(System.err);
        } catch (NoSuchMethodException e) {
            e.printStackTrace(System.err);
        }

        // Try a narrow on the "remoteAble" class
        Class remoteClass = meta.getRemoteInterfaceClass();
        String imAbleClassName = remoteClass.getName() + "Able";

        ClassLoader cloader = Thread.currentThread().getContextClassLoader();
        try {
            Class imAbleClass = cloader.loadClass(imAbleClassName);
            if (!imAbleClass.isAssignableFrom(im.getClass())) {
                im = PortableRemoteObject.narrow(im, imAbleClass);

            }
        } catch (Exception ex) {
            //ex.printStackTrace(System.err);
        }
        return im;

    }

    /**
     * Thread initialization
     *
     */
	public void init() {

		if (isInitialized)
			return;

		try {
			InitialContext ctx = new InitialContext();
			optHome = (MyServiceHome) PortableRemoteObject
					.narrow(ctx.lookup("java:comp/env/ejb/MyService"),
							MyServiceHome.class);
			optService = optHome.create();
			measures = new Measure[iteration / PERIOD];
			isInitialized = true;
		} catch (NamingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace(System.err);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace(System.err);
		}

	}

    /**
     * Constructor
     * @param name
     * @param iterations
     * @param thinkTime
     */
	public MyClient(String name, int iterations, long thinkTime) {
		int q = iterations / PERIOD;
		int r = iterations % PERIOD;
		iteration = (q + (r != 0 ? 1 : 0)) * PERIOD;
		this.thinktime = thinkTime;
		this.clientName = name;
	}

	public int getIterations() {
		return iteration;
	}

	public static int targetIterations(int iter) {
		int q = iter / PERIOD;
		int r = iter % PERIOD;
		return (q + (r != 0 ? 1 : 0)) * PERIOD;

	}

	public static void process(int nbClients, int iterations, long thinkTime) {

		/*
		 * clients initialization
		 */
		MyClient[] clients = new MyClient[nbClients];
		boolean initOK = true;
		for (int i = 1; i <= nbClients; i++) {
			clients[i - 1] = new MyClient("cl-" + Integer.toString(i),
					iterations, thinkTime);
			clients[i - 1].init();
			initOK = initOK && clients[i - 1].isInitialized;
		}
		if (!initOK)
			return;

		/*
		 * Run clients
		 */
		Thread[] th = new Thread[nbClients];
		for (int i = 0; i < nbClients; i++) {
			th[i] = new Thread(clients[i]);
			th[i].setDaemon(true);
			th[i].start();
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		}

		/*
		 * Wait for completion
		 */
		boolean goOn = true;
		while (goOn) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
			}
			boolean complete = true;
			for (int i = 0; i < nbClients; i++) {
				complete = complete && (clients[i].getStatus() == COMPLETE);
			}
			goOn = !complete;
		}

		/*
		 * Print statistics
		 */
		System.out.println("time;source;responseTime;inFacadeMethod");
        double sumClient=0;
        double sumEjb=0;
        int measureNb = 0;
		for (int i = 0; i < nbClients; i++) {
			Measure[] dots = clients[i].getMeasures();

			for (int j = 0; j < dots.length; j++) {
                measureNb++;
                sumClient+=dots[j].getResponseTime() - thinkTime;
                sumEjb+=dots[j].getInEJB();

				System.out.println(dots[j].getTimestamp() + ";"
						+ dots[j].getSource() + ";"
						+ (dots[j].getResponseTime() - thinkTime) + ";"
						+ dots[j].getInEJB());
			}
		}
        System.err.println("responseTime;inFacadeMethod");
        System.err.println(sumClient/measureNb + ";"+ sumEjb/measureNb);

	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		int iterations = 128;
		int nbCl = 1;
		long thinktime = 0;

		if (args.length > 0) {
			nbCl = Integer.parseInt(args[0]);
		}

		if (args.length > 1) {
			iterations = Integer.parseInt(args[1]);
		}

		if (args.length > 2) {
			thinktime = Long.parseLong(args[2]);
		}


		if (args.length > 3) {
			if ("Remote".equalsIgnoreCase(args[3])) {
				isRemoteOnly = true;
				isCreateRemote = false;
				isLookupCreateRemote = false;
			}
			if ("CreateRemote".equalsIgnoreCase(args[3])) {
				isRemoteOnly = false;
				isCreateRemote = true;
				isLookupCreateRemote = false;
			}
			if ("LookupCreateRemote".equalsIgnoreCase(args[3])) {
				isRemoteOnly = false;
				isCreateRemote = false;
				isLookupCreateRemote = true;
			}
            if ("MetaData".equalsIgnoreCase(args[3])) {
                isRemoteOnly = false;
                isCreateRemote = false;
                isLookupCreateRemote = false;
                isMetaData = true;
            }
		}

		System.err.println("clients: " + nbCl);
		System.err.println("iterations: " + targetIterations(iterations));
		System.err.println("think: " + thinktime);

		process(nbCl, iterations, thinktime);

	}

	public Measure[] getMeasures() {
		return measures;
	}

	public int getStatus() {
		return status;
	}

	public static final int CREATED = 1;

	public static final int RUNNING = 2;

	public static final int COMPLETE = 4;

	private static MyServiceHome optHome = null;

	private static MyService optService = null;

	private boolean isInitialized = false;

	private long chrono = 0;

	private long lastResult = 0;

	private int iteration = 0;

	private long thinktime = 0;

	private String clientName = "client";

	private Measure[] measures = null;

	private int status = CREATED;

	/*
	 * cardinality of sample set for statistics
	 */
	private static int PERIOD = 16;

	private static boolean isRemoteOnly = false;

	private static boolean isCreateRemote = false;

	private static boolean isLookupCreateRemote = true;
    private static boolean isMetaData = false;
}
