/**
 *  Generic example of master/worker program.  
 *
 *  "Tasks" just consist of sleeping for a specified time; times to wait are
 *  generatedly randomly from 1 to a specified maximum.
 *
 *  Command-line arguments:  total tasks, maximum task time, seed, threads,
 *    (optional) "--verbose" for verbose output
 *
 *  Multithreaded version with dynamic assignment of tasks to threads.
 */
package csci3366.sample.masterworker.threads;

import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import csci3366.sample.masterworker.FakeTasks;

public class MasterWorkerParDynamic {

    /* variables to be used by all threads */

    private static boolean verbose = false;
    private static int numThreads = 0;
    private static FakeTasks.SynchTaskQueue taskQueue;

    /** 
     * Main method.
     */
    public static void main(String[] args) {

        String usageMessage = 
            "parameters:  numTasks maxTaskTime seed numThreads [--verbose]";

        int numTasks = 0;
        int maxTaskTime = 0;
        int seed = 0;

        /* process command-line arguments */
        if (args.length < 4) {
            System.err.println(usageMessage); System.exit(1);
        }
        try {
            numTasks = Integer.parseInt(args[0]);
            maxTaskTime = Integer.parseInt(args[1]);
            seed = Integer.parseInt(args[2]);
            numThreads = Integer.parseInt(args[3]);
        }
        catch (NumberFormatException e) {
            System.err.println(usageMessage); System.exit(1);
        }
        if (args.length > 4) {
            if (args[4].equals("--verbose"))
                verbose = true;
            else {
                System.err.println(usageMessage); System.exit(1);
            }
        }

        int totalTaskTime = 0;
        int maxThreadTotalTaskTime = 0;
        int minThreadTotalTaskTime = maxTaskTime * numTasks;

        /* start timing */
        long startTime = System.currentTimeMillis();

        /* build queue of tasks */
        taskQueue = new FakeTasks.SynchTaskQueue(FakeTasks.randomTasks(
                    numTasks, maxTaskTime, seed));

        /* create executor for threads */
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);

        /* 
         * create tasks (that return total task time in thread) 
         * and send to executor, creating Future objects so we can wait for 
         * tasks to finish (and get the returned values) 
         */
        List<Future<Integer>> times = new ArrayList<Future<Integer>>();
        for (int i = 0; i < numThreads; ++i) {
            times.add(executor.submit(new Worker(i)));
        }

        /* shut down executor and wait for threads to finish */
        executor.shutdown();
        for (Future<Integer> t : times) {
            try {
                int threadTotalTaskTime = t.get();
                maxThreadTotalTaskTime =
                    Math.max(maxThreadTotalTaskTime, threadTotalTaskTime);
                minThreadTotalTaskTime =
                    Math.min(minThreadTotalTaskTime, threadTotalTaskTime);
                totalTaskTime += threadTotalTaskTime;
            }
            catch (ExecutionException e) {
                System.err.println("should not happen");
            }
            catch (InterruptedException e) {
                System.err.println("should not happen");
            }
        }

        /* end timing and print result */
        long endTime = System.currentTimeMillis();
        System.out.printf("\nthreaded parallel version with %d threads" +
                ", dynamic assignment of tasks\n", 
                numThreads);
        System.out.printf("number of tasks = %d\n", numTasks);
        System.out.printf("max task time = %d\n", maxTaskTime);
        System.out.printf("total task time = %d\n", totalTaskTime);
        System.out.printf("total task time in threads ranges from %d to %d\n",
                minThreadTotalTaskTime, maxThreadTotalTaskTime);
        System.out.printf("running time = %g\n", 
                (double) (endTime - startTime) / 1000);
    }

    /**
     * Class to contain code to run in each thread.
     */
    private static class Worker implements Callable<Integer> {

        private int myID;
        private int threadNumTasks = 0;
        private int threadTotalTaskTime = 0;

        /** Constructor. */
        public Worker(int ID) {
            myID = ID;
        }

        /** Code for thread.  Returns total task time. */
        public Integer call() {

            FakeTasks.Task t = null;
            /* get tasks from queue until queue is empty */
            while ((t = taskQueue.get()) != null) {
                ++threadNumTasks;
                threadTotalTaskTime += t.time;
                FakeTasks.TaskResult tr = t.execute();
                if (verbose) {
                    System.out.printf("(thread %d) %s\n",
                            myID, FakeTasks.toString(t, tr));
                }
            }
            System.out.printf(
                    "thread %d number of tasks = %d, total task time = %d\n", 
                    myID, threadNumTasks, threadTotalTaskTime);
            return new Integer(threadTotalTaskTime);
        }
    }
}