/**
 * Copyright 2011, 2012 Niall Gallagher
 *
 * 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 com.googlecode.mobilityrpc.benchmarks.rmi;

import com.googlecode.mobilityrpc.MobilityRPC;
import com.googlecode.mobilityrpc.controller.MobilityController;
import com.googlecode.mobilityrpc.controller.impl.MobilityControllerImpl;
import com.googlecode.mobilityrpc.network.ConnectionId;
import com.googlecode.mobilityrpc.protocol.pojo.ExecutionMode;
import com.googlecode.mobilityrpc.session.MobilitySession;

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author Niall Gallagher
 */
public class BenchmarkMultithreaded {

    private static final int NUM_THREADS = 1;
    private static final int NUM_REQUESTS_PER_THREAD = 100000;
    private static final int REQUEST_SIZE = 1; // number of objects to send in each request

    static class RmiBenchmark {
        public static void main(String[] args) {
            try {
                // Set up RMI connection...
                final RmiServer.RmiHandler rmiHandler = (RmiServer.RmiHandler) Naming.lookup("//127.0.0.1/RmiHandler");

                final AtomicLong numIterations = new AtomicLong();
                final AtomicLong numObjectsSent = new AtomicLong();
                final AtomicLong sumOfLatencyNanos = new AtomicLong();

                class BenchmarkTask implements Callable<Collection<? extends Comparable>> {
                    @Override
                    public Collection<? extends Comparable> call() {
                        Collection<? extends Comparable> input = Util.createCollection(REQUEST_SIZE);
                        Collection<? extends Comparable> output = null;
                        long startTime = System.nanoTime();
                        for (int iterationNumber = 0; iterationNumber < NUM_REQUESTS_PER_THREAD; iterationNumber++) {
                            output = processRemotelyViaRmi(input, rmiHandler);
                        }
                        long timeTakenNanos = System.nanoTime() - startTime;
                        numIterations.addAndGet(NUM_REQUESTS_PER_THREAD);
                        numObjectsSent.addAndGet(REQUEST_SIZE * NUM_REQUESTS_PER_THREAD);
                        sumOfLatencyNanos.addAndGet(timeTakenNanos);
                        return output;
                    }
                }
                Future<Collection<? extends Comparable>> result = null;

                // Warm up (run the test code but discard results)...
                ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREADS);
                for (int i = 0; i < NUM_THREADS; i++) {
                    result = executorService.submit(new BenchmarkTask());
                }
                executorService.shutdown();
                executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

                // Run test...
                executorService = Executors.newFixedThreadPool(NUM_THREADS);
                numIterations.set(0);
                numObjectsSent.set(0);
                sumOfLatencyNanos.set(0);

                for (int i = 0; i < NUM_THREADS; i++) {
                    result = executorService.submit(new BenchmarkTask());
                }

                executorService.shutdown();
                executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

                System.out.println("Finished. Final result was: " + ((result == null) ? null : result.get()));
                System.out.println("RMI Num Threads\tRMI Request Size\tRMI Requests per sec\tRMI Latency Per Request(ns)");
                System.out.println(NUM_THREADS + "\t" + (((double)numObjectsSent.get()) / numIterations.get()) + "\t" + (numIterations.get() / (sumOfLatencyNanos.get() / 1000000000.0)) + "\t" + (((double) sumOfLatencyNanos.get()) / numIterations.get()));
            }
            catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
    }

    static class MobilityBenchmark {
        public static void main(String[] args) {
            try {
                // Set up Mobility connection...
                final MobilityController mobilityController = MobilityRPC.newController();
                final MobilitySession session = mobilityController.getSession(UUID.randomUUID());
                final ConnectionId connectionId = new ConnectionId("127.0.0.1", 5739);

                final AtomicLong numIterations = new AtomicLong();
                final AtomicLong numObjectsSent = new AtomicLong();
                final AtomicLong sumOfLatencyNanos = new AtomicLong();

                class BenchmarkTask implements Callable<Collection<? extends Comparable>> {
                    @Override
                    public Collection<? extends Comparable> call() {
                        Collection<? extends Comparable> input = Util.createCollection(REQUEST_SIZE);
                        Collection<? extends Comparable> output = null;
                        long startTime = System.nanoTime();
                        for (int iterationNumber = 0; iterationNumber < NUM_REQUESTS_PER_THREAD; iterationNumber++) {
                            output = processRemotelyViaMobility(input, session, connectionId);
                        }
                        long timeTakenNanos = System.nanoTime() - startTime;
                        numIterations.addAndGet(NUM_REQUESTS_PER_THREAD);
                        numObjectsSent.addAndGet(REQUEST_SIZE * NUM_REQUESTS_PER_THREAD);
                        sumOfLatencyNanos.addAndGet(timeTakenNanos);
                        return output;
                    }
                }
                Future<Collection<? extends Comparable>> result = null;

                // Warm up (run the test code but discard results)...
                ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREADS);
                for (int i = 0; i < NUM_THREADS; i++) {
                    result = executorService.submit(new BenchmarkTask());
                }
                executorService.shutdown();
                executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

                // Run test...
                executorService = Executors.newFixedThreadPool(NUM_THREADS);
                numIterations.set(0);
                numObjectsSent.set(0);
                sumOfLatencyNanos.set(0);

                for (int i = 0; i < NUM_THREADS; i++) {
                    result = executorService.submit(new BenchmarkTask());
                }

                executorService.shutdown();
                executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
                mobilityController.destroy();

                System.out.println("Finished. Final result was: " + ((result == null) ? null : result.get()));
                System.out.println("Mobility Num Threads\tMobility Request Size\tMobility Requests per sec\tMobility Latency Per Request(ns)");
                System.out.println(NUM_THREADS + "\t" + (((double)numObjectsSent.get()) / numIterations.get()) + "\t" + (numIterations.get() / (sumOfLatencyNanos.get() / 1000000000.0)) + "\t" + (((double) sumOfLatencyNanos.get()) / numIterations.get()));
            }
            catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
    }

    @SuppressWarnings("unchecked")
    static <T extends Comparable> Collection<T> processRemotelyViaRmi(final Collection<T> input, RmiServer.RmiHandler rmiHandler) {
        try {
            return rmiHandler.processRequest(input);
        }
        catch (RemoteException e) {
            throw new IllegalStateException(e);
        }
    }

    @SuppressWarnings("unchecked")
    static <T extends Comparable> Collection<T> processRemotelyViaMobility(final Collection<T> input, MobilitySession session, ConnectionId connectionId) {
        return session.execute(connectionId, ExecutionMode.RETURN_RESPONSE,
            new Callable<Collection<T>>() {
                public Collection<T> call() throws Exception {
                    return ServerBusinessLogic.processRequest(input);
                }
            }
        );
    }

    static class Util {
        @SuppressWarnings("unchecked")
        static Collection<? extends Comparable> createCollection(int numItems) {
            ArrayList<Person> collection = new ArrayList<Person>();
            for (int i = 0; i < numItems; i++) {
                collection.add(new Person(
                        i,
                        "Joe_" + i,
                        "Bloggs_" + i,
                        Arrays.asList("phone_" + (i + 1), "phone_" + (i + 2)),
                        i,
                        "Street_" + i,
                        "City_" + i,
                        "Country_" + i
                ));
            }
            return collection;
        }
    }
}