import java.util.LinkedList; 
//=============================================================================
// The main Order Application
//     args[0] = Number of orders to be processed.  If none 15 is used.
//     args[1] = Number of order taker threads.     If none  2 is used.
//     args[2] = Number of order processor threads. If none  2 is used.
//=============================================================================
public class OrderApp
{
    static int MAX_ORDERS              = 15;            //number of orders to process
    static int ORDER_TAKER_THREADS     = 2;             //number of OrderTaker threads
    static int ORDER_PROCESSOR_THREADS = 2;             //number of OrderProcess threads

    static OrderManager MGR = new OrderManager();       //Launch a manager class

    public static void main(String[] args)
    {
        if (args.length > 0) MAX_ORDERS              = Integer.parseInt(args[0]); 
        if (args.length > 1) ORDER_TAKER_THREADS     = Integer.parseInt(args[1]); 
        if (args.length > 2) ORDER_PROCESSOR_THREADS = Integer.parseInt(args[2]); 

        System.out.println(Thread.currentThread().getName()        + " is creating:");
        System.out.println("- An order queue with maximum orders " + MAX_ORDERS);
        System.out.println("- Starting " + ORDER_TAKER_THREADS     + " order taker threads.    ");
        System.out.println("- Starting " + ORDER_PROCESSOR_THREADS + " order processor threads.");
        System.out.println();
        System.out.println("     OrderTaker threads     \t      OrderProcessor threads    \n"
                         + "============================\t==================================");

        for (int i=1; i <= ORDER_TAKER_THREADS; i++)        //create the OrderTaker threads
        {
            String name  = "Taker-" + i;
            OrderTaker t = new OrderTaker(name);            //create a thread
            t.start();                                      //start it
        }

        try {
            Thread.sleep(500);                              //delay by 1/2 second
        } 
        catch(InterruptedException ex) 
        {
            Thread.currentThread().interrupt();
        }

        for (int i=1; i <= ORDER_PROCESSOR_THREADS; i++)    //create the OrderProcessor threads
        {
            String name      = "Processor-" + i;
            OrderProcessor p = new OrderProcessor(name);    //create a thread
            p.start();                                      //start it
        }
    }
}

//=========================================================================
// a simple class that encapsulates an order
//=========================================================================
class Order
{
    private String orderNum;
    private String fname;
    private String lname;
    private String etc;

    public Order(String orderNum)       //constructor
    {
        this.orderNum = orderNum;
    }

    public String toString()            //toString()
    {
        String x  = "Order #" + orderNum;
        return(x);
    }
}

//==========================================================================
// A class that uses a linkedList object to create an order queue
//     when an order is taken,  the queue notifies the process threads
//     when the queue is empty, the process threads go into waiting state
//==========================================================================
class OrderManager
{
    private static LinkedList<Order> orderQueue = new LinkedList<Order>();   //using generics 


    public synchronized void pushOrder(Order order)     //synchronized method to take an order
    {
        orderQueue.addLast(order);
                                                        //notify all waiting threads that there is an order
        notifyAll();                                    //if you only notify 1 threads instead,
    }                                                   //then you can change the while() below to an if()   
                                                        

    public synchronized Order pullOrder()               //synchronized method to process an order
    {
        while (orderQueue.size() == 0)                  //while the queue is empty.  Why not if?
        {                                               //in case multiple threads are awakened, and another 
            try                                         //thread grabbed the next order from the queue
            {
                System.out.println("\t\t\t\t==>" + Thread.currentThread().getName() + 
                                   " is waiting");
                wait();                                 //wait until notified
            }
            catch (InterruptedException e)
            {}
        }
        Order o =  orderQueue.removeFirst();
        return(o);                                      //return the order for processing
    }
}

//================================================================================================
// The order taker class
// This class extends Thread so that many threads can be started 
// the sleep method here is not necessary, simulate real world orders coming in the door
//================================================================================================
class OrderTaker extends Thread
{
    private static int ordersTaken;                     //keep a count of the orders taken
    private static OrderManager mgr = OrderApp.MGR;     //Access the manager class

    public OrderTaker(String name)                      //constructor
    {
        super(name);                                    //call super & pass it the name
    }

    public void run()                                   //the run method
    {
        Order order;

        while(ordersTaken < OrderApp.MAX_ORDERS)
        {
            addOrdersTaken();                               //add 1 to orders taken 

            String order_num = "000" + ordersTaken;         //generate an order number

            order = new Order(order_num);                   //create an order

            mgr.pushOrder(order);                           //add order to the queue

            System.out.println(this.getName() + " created " + order);

            try
            {                                                   //delay up to 3 seconds
                Thread.sleep( (long) (Math.random() * 3000));   //simulate real world
            }                                                   //orders coming in the door
            catch (InterruptedException e)
            {}                                                  //ignore interruptions
        }
        System.out.println("-->" + this.getName() + " is shutting down");
    }

    private synchronized void addOrdersTaken()          //synchronized so only 1 thread 
    {                                                   //can add to static field at a time
        ordersTaken++;
    }
}

//================================================================================================
// The order processor class
// This class extends Thread so that many threads can be started 
// the sleep method here is not necessary, simulate real world time taking to process orders
//================================================================================================
class OrderProcessor extends Thread
{
    private static int ordersProcessed;                 //keep a count of the orders processed
    private static OrderManager mgr = OrderApp.MGR;     //Access the manager class

    public OrderProcessor(String name)                  //constructor
    {
        super(name);                                    //call super & pass it the name
    }

    public void run()                                   //the run method
    {
        Order order;
        
        while(ordersProcessed < OrderApp.MAX_ORDERS)    //or while(true) to keep running  
        {                                               
            addOrdersProcessed();                       //add 1 to orders processed 

            order = mgr.pullOrder();                    //get next available order

            System.out.println("\t\t\t\t" + this.getName() + " processing " + order);

            try
            {                                                   //delay up to 3 seconds
                Thread.sleep( (long) (Math.random() * 3000));   //to simulate real world
            }                                                   //taking time to process order
            catch (InterruptedException e) 
            {}                                                  //ignore interruptions
        }
        System.out.println("\t\t\t\t-->" + this.getName() + " is shutting down");
    }

    private synchronized void addOrdersProcessed()        //synchronized so only 1 thread 
    {                                                     //can add to static field at a time
        ordersProcessed++;
    }
}