/**
 *  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, port number, 
 *  (optional) "--verbose" for verbose output
 *
 *  Master process for client/server version using sockets and a
 *  less-centralized approach (with the number of workers not known to
 *  the server).  
 *
 *  This version does not try to measure total execution time; it's not
 *  clear that would be meaningful.
 */
package csci3366.sample.masterworker.sockets;

import java.io.IOException;
import java.io.Serializable;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.LinkedList;

import csci3366.sample.masterworker.sockets.Master;

public class Master2 extends Master {

    /* variables to be used by multiple threads */

    private ServerSocket serverSocket;
    private volatile boolean serverSocketShutDownOnEmptyQueue = false;
    private List<Thread> workerThreads = new LinkedList<Thread>();

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

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

        int numTasks = 0;
        int maxTaskTime = 0;
        int seed = 0;
        int port = 0;
        boolean verbose = false;

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

        /* do processing */
        new Master2(
                numTasks, maxTaskTime, seed, port, verbose).doProcessing();
    }

    /** Constructor. */
    public Master2(int numTasks_, int maxTaskTime_, int seed_,
            int port_, boolean verbose_)
    {
        super(numTasks_, maxTaskTime_, seed_, port_, verbose_);
    }

    /** Do processing. */
    public void doProcessing() {

        initialize();

        /* build queue of tasks */
        buildTaskQueue(numTasks, maxTaskTime, seed);

        /* start thread to accept connections from other workers */
        Thread serverSocketThread = new Thread(new Runnable() {
            public void run() {
                try {
                    /* create server socket */
                    serverSocket = new ServerSocket(port);
                    if (verbose) {
                        System.out.println("waiting for workers");
                    }
                    /*
                     * accept connections until main thread closes serverSocket
                     *
                     * FIXME?:  figure out some nicer way to do this?
                     *   there seems to be no easier way to distinguish the 
                     *   exception generated by a normal shutdown from errors
                     */
                     while (true) {
                        acceptConnection();
                    }
                }
                /* FIXME?:  improve error handling? */
                catch (IOException e) {
                    if (serverSocketShutDownOnEmptyQueue) {
                        if (verbose) {
                            System.out.println("server socket closed");
                        }
                    }
                    else {
                        System.err.println("error with server socket:\n\t" + e);
                        System.exit(1);
                    }
                }
            }
        });
        serverSocketThread.start();

        /* wait for task queue to become empty */
        if (verbose) {
            System.out.println("waiting for task queue to become empty");
        }
        waitUntilTaskQueueEmpty();

        /* close server socket to force shutdown of associated thread */
        try {
            serverSocketShutDownOnEmptyQueue = true;
            serverSocket.close();
        }
        catch (IOException e) {
            System.err.println("error shutting down server socket:\n\t" + e);
        }
        if (verbose) {
            System.out.println("waiting for server socket thread to finish");
        }
        waitForThreadToComplete(serverSocketThread);

        /* wait for worker threads to finish */
        if (verbose) {
            System.out.println("waiting for worker threads to finish");
        }
        for (Thread t : workerThreads) {
            waitForThreadToComplete(t);
        }

        /* print result */
        System.out.printf(
                "\nsockets-based distributed client/server version\n");
        System.out.printf("number of workers = %d\n", workerThreads.size());
        printSummaryInfo(numTasks, maxTaskTime);
    }

    private void acceptConnection() throws IOException {
        Socket s = serverSocket.accept();

        synchronized (workerThreads) {
            int workerID = workerThreads.size();
            if (verbose) {
                System.out.printf("connection from %s, worker %d\n", 
                        clientAddress(s), workerID);
            }
            Thread w = new Thread(new WorkerTalker(workerID, s));
            workerThreads.add(w);
            w.start();
        }
    }

}