import java.util.List;

/**
 *  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, number of threads,
 *    (optional) "--verbose" for verbose output
 *
 *  Parallel version with dynamic assignment of tasks to threads.
 */
public class MasterWorkerParDynamic {

    /* variables to be used by all threads */

    private int numThreads;
    private TaskQueue taskQueue;
    private boolean verbose = false;
    private int totalTaskTime = 0;
    private int minThreadTotalTaskTime;
    private int maxThreadTotalTaskTime;

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

        String usageMessage = 
            "parameters:  numTasks maxTaskTime numThreads [--verbose]";
        final int REQUIRED_PARMS = 3;

        int numTasks = 0;
        int maxTaskTime = 0;
        int numThreads = 0;
        boolean verbose = false;

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

        /* do processing */
        new MasterWorkerParDynamic(numTasks, maxTaskTime, numThreads, verbose);
    }

    /** Constructor (actually where processing occurs too). */
    public MasterWorkerParDynamic(int numTasks, int maxTaskTime, 
            int _numThreads, boolean _verbose)
    {
        numThreads = _numThreads;
        verbose = _verbose;

        maxThreadTotalTaskTime = 0;
        minThreadTotalTaskTime = maxTaskTime * numTasks;

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

        /* build queue of tasks */
        taskQueue = new TaskQueue(numTasks, maxTaskTime);

        /* create threads */
        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < threads.length; ++i) {
            threads[i] = new Thread(new Worker(i));
        }

        /* start them up */
        for (int i = 0; i < threads.length; ++i) {
            threads[i].start();
        }

        /* wait for them to finish */
        for (int i = 0; i < threads.length; ++i) {
            try {
                threads[i].join();
            }
            catch (InterruptedException e) {
                System.err.println("this should not happen");
                e.printStackTrace();
            }
        }

        /* 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);
    }

    /* methods for use by threads */

    /**
     * Update global variables at end of thread.
     */
    private synchronized void updateGlobals(int threadTotalTaskTime) {
        maxThreadTotalTaskTime =
            Math.max(maxThreadTotalTaskTime, threadTotalTaskTime);
        minThreadTotalTaskTime =
            Math.min(minThreadTotalTaskTime, threadTotalTaskTime);
        totalTaskTime += threadTotalTaskTime;
    }

    /**
     * Class to contain code to run in each thread.
     */
    private class Worker implements Runnable {

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

        public Worker(int ID) {
            myID = ID;
        }

        public void run() {

            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);
            /* thread-safe update to globals */
            updateGlobals(threadTotalTaskTime);
        }
    }

    /**
     * Class for task queue (thread-safe).
     */
    private static class TaskQueue {
        private List<FakeTasks.Task> tasks;

        public TaskQueue(int numTasks, int maxTaskTime) {
            tasks = FakeTasks.randomTasks(numTasks, maxTaskTime);
        }

        /** Get a task (returns null if queue is empty). */
        public synchronized FakeTasks.Task get() {
            if (tasks.isEmpty()) {
                return null;
            }
            else {
                return tasks.remove(0);
            }
        }
    }
}