import java.util.*;
//-----------------------------------------------------------------------------
// This program simulates a shopping queue
// You can pass thru args:
//  args[0] = Number of emmployee servers   (if not provided = 4)
//  args[1] = Average customer arrival time (if not provided = 10 sesonds)
//  args[2] = Average service time          (if not provided = 60 seconds)
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Queue class that extends from LinkedList
// overrides and de-activates some of the methods
//-----------------------------------------------------------------------------
class Queue<E>  extends LinkedList<E>                   //Notice use of generic
{
    public void push(E item)                            //receives a generic
    {
        super.addLast(item);                            //Java has a push( ) --> addFirst( )
    }                                                   //except, I want to add to the end

    public E pop()                                      //not needed
    {
        return super.removeFirst();                     //Java has a pop( ) --> removeFirst( )
    }                                                   //no need to overwrite

    public void add(int i, E item) {}                   //disable add(int i, E item)
    public void addFirst(E item) {}                     //disable addFirst(E item)
}
//-----------------------------------------------------------------------------
// Encapsulates all fields/methods needed for a server employee 
//-----------------------------------------------------------------------------
class Server 
{
    int    number;
    String name;
    String cust_name;
    int    svc_duration;
    int    svc_start_time;
    int    svc_end_time;
    int    busyTimer;    
    int    totBusyTime;    
    int    totIdleTime;    
    
    Server(int number)                                              //constructor
    {
        this.number = number;
        this.name   = "Server" + number;
    }
    static String getBusyTimers(ArrayList<Server> servers)          //get all servers busy timers
    {
        String busy_timers = "[ ";                           
        for (Server emp : servers)
            busy_timers += "srv" + emp.number + "=" + emp.busyTimer + " ";                        
        busy_timers += "]";                          
        return busy_timers; 
    }
    static int getTotalBusyTime(ArrayList<Server> servers)          //get total servers busy time
    {
        int total_busy_time = 0;                           
        for (Server emp : servers)
            total_busy_time += emp.totBusyTime;            
        return total_busy_time; 
    }
    static int getTotalIdleTime(ArrayList<Server> servers)          //get total servers idle time
    {
        int total_idle_time = 0;                           
        for (Server emp : servers)
            total_idle_time += emp.totIdleTime;            
        return total_idle_time; 
    }
    static boolean allIdle(ArrayList<Server> servers)                //are all servers idle?
    {
        boolean areIdle = true;
        for (Server emp : servers)
            if (emp.busyTimer > 0) areIdle = false;                        
        return areIdle; 
    }
    public String toString()
    {
        String s = name + "   Service_duration=" +svc_duration+ "sec Remaining_time=" +busyTimer 
                        + " Start_at="         +svc_start_time+ " end_at=" +svc_end_time; 
        return s; 
    }
}
//-----------------------------------------------------------------------------
// Encapsulates all fields/methods needed for a customer 
//-----------------------------------------------------------------------------
class Customer 
{
    int    number;
    String name;
    int    arrival_time;
    String status;                              //waiting or serviced
    int    waitTimer;
    String server_name;
    int    svc_duration;                        //how long does the order take to complete
    int    svc_start_time;
    int    svc_end_time;
    
    Customer(int number)                        //constructor
    {
        this.number = number;
        this.name   = "Customer" + number;
    }
    public String inQueue()
    {
        String c = name + " Arrived_at=" +arrival_time+ " Status=" +status+ " for=" +waitTimer+ " sec";
        return c; 
    }
    public String toString()
    {
        String c = name + " Arrived_at=" +arrival_time+ " Status=" +status+ " by=" +server_name+
                          " waited=" +waitTimer+ " Serviced_at=" +svc_start_time+ " end_at=" +svc_end_time;
        return c; 
    }
}
    
//-----------------------------------------------------------------------------
// Simulates a real life customer entry and servicing within a shop 
//-----------------------------------------------------------------------------
public class Simulation
{                                                       //drivers
    static int num_servers = 4;                         //number of server employees
    static int avg_arrival = 10;                        //average number of seconds between customer arrivals (6 cust per minute)  
    static int avg_service = 60;                        //average number of seconds to service each customer  (1-120 seconds)
    static int iteration   = 180;                       //run simulation for 3 minutes 3*60
    static int clock;                                   //constantly ticking clock timer 
    static int cust_number; 
    static int total_customers;
    static int total_wait_time;                         //total all customers wait time  
    static int max_wait_time;  
    static int max_queue_size;
    static int next_arrival;
    static boolean accepting_customers = true;

    static Random arv = new Random(123123);             //random numbers with fixed seeds
    static Random svc = new Random(654321);             //to enable repeated sequences

    static ArrayList<Server> servers;                   //arrayList to hold servers 
    static Queue<Customer>   customers;                 //queue to hold customers

    public static void main(String[] args)
    {                
        if (args.length > 0)
            num_servers = Integer.parseInt(args[0]);        //if args[0] provided, override num of servers
        if (args.length > 1)
            avg_arrival = Integer.parseInt(args[1]);        //if args[1] provided, override avg arrival time
        if (args.length > 2)
            avg_service = Integer.parseInt(args[2]);        //if args[2] provided, override avg service time

        servers   = new ArrayList<Server>();                //create an arrayList to hold servers         
        customers = new Queue<Customer>();                  //create a queue to hold customers
                                    
        for (int emp=1; emp <= num_servers; emp++)          //populate the servers ArrayList
        {
            Server server = new Server(emp);                //create a server object
            servers.add(server);                            //add to server ArrayList                           
        }

        System.out.println("RUNNING SIMULATION ====================================================");                                                            
        System.out.println("Time:");            

        while(true)                                             //run the simulation
        {
            clock++;
            System.out.println(clock);                          //print the clock timer           
            if (clock <= iteration)                             //as long as the shop doors are open (180 seconds) 
                arrival();                                      //check for possible customer ARRIVAL          
//          Collections.shuffle(servers);                       //shuffle the servers so they server randomly                               
            for (Server emp : servers)							//iterate for every server
                service(emp);                                   //check for possible customer SERVICING
            update_wait_time();                                 //update all customers wait time
            if (clock == iteration)                             //simulation Part 1 is done, shop doors are closed
                printStats("Part1");
            if (clock >= iteration && Server.allIdle(servers) )    //simulation is over, and all servers are now idle (customers all serviced)
                break;                                             //the end
        }    
        printStats("Final");                                       //print final stats
    }    

    //-------------------------------------------------------
    // Used to accept a new customer into the queue
    // This method is called every ticking second 
    //-------------------------------------------------------
    public static void arrival()                             //accept a new customer 
    { 
        if (accepting_customers)                                                      
        {
            int rand_arv_time = arv.nextInt(avg_arrival*2)+1;   //generate random arrival time (0-19)+1 avg 10)
            next_arrival  = clock + rand_arv_time;              //next customer will arrive at
            accepting_customers = false;                        //temporarily stop accepting customers 
        }                                                       //until next customer walks in the door
        
        if (next_arrival == clock)                           //the clock is equal to next arrival time
        {                                                    //customer walks in the door													
            cust_number++;
            Customer cust     = new Customer(cust_number);   //create a customer object
            cust.arrival_time = clock;
            cust.status       = "waiting";
            total_customers   +=1;
            customers.push(cust);                            //add a customer to the queue

            if (customers.size() > max_queue_size)
                max_queue_size = customers.size();

            System.out.println("ARRIVAL ---------------------------------------------------------------");
            System.out.println(cust.name + " arrived_at=" + cust.arrival_time);
            printQueue();
            accepting_customers = true; 
        }
    }
    //------------------------------------------------------------------------
    // Used to service a customer waiting in the queue
    // This method is called every ticking second for each server  
    //------------------------------------------------------------------------
    public static void service(Server server)               //attempt to service a customer
    {
        if (server.busyTimer >= 1)                          //if server is busy                               
            server.busyTimer--;                             //reduce server timer by 1 second
        else
            server.totIdleTime++;                           //add 1 second to server idle time
            
        if (server.busyTimer == 0                           //if server is available to service next customer  
        && customers.peekFirst() != null)                   //and there is a customer in the queue
        {
            Customer cust = customers.pop();                //remove customer from queue

            System.out.println("SERVICING -------------------------------------------------------------");
            System.out.println(server.name + "   servicing " + cust.name);
            total_wait_time  += cust.waitTimer;

            int rand_svc_time = svc.nextInt(avg_service*2)+1;   //random service time (avg svc 60 seconds)
            
            server.cust_name      = cust.name;                  //update server object
            server.svc_duration   = rand_svc_time;
            server.svc_start_time = clock;
            server.svc_end_time   = clock + rand_svc_time;
            server.busyTimer      = rand_svc_time;
            server.totBusyTime   += rand_svc_time;

            cust.server_name      = server.name;                //update customer object
            cust.status           = "serviced";
            cust.svc_duration     = rand_svc_time;
            cust.svc_start_time   = clock;
            cust.svc_end_time     = clock + rand_svc_time;

            System.out.println(server);
            System.out.println(cust);
            System.out.println("-----------------------------------------------------------------------");
        }       
    }
    //--------------------------------------------------------
    // Update customers wait time 
    //--------------------------------------------------------
    public static void update_wait_time()
    {
        for (Customer cust : customers)                     //customers in the queue
        {
            cust.waitTimer++;                               //update wait time for each customer in the queue
            if (cust.waitTimer > max_wait_time)             //update max_wait_time if any
                max_wait_time  = cust.waitTimer;
        }
    }
    //--------------------------------------------------------
    // Print server busy timers, and customers in queue
    //--------------------------------------------------------
    public static void printQueue()
    {
        System.out.println("CUSTOMER QUEUE --------------------------------------------------------");        
        String srv_busy_timers = Server.getBusyTimers(servers); 
        System.out.println("Servers status/remaining busy time: " + srv_busy_timers);
        if (customers.size() == 0)
            System.out.println("Queue is empty");                      
        for (Customer cust : customers)                     //customers in the queue
            System.out.println(cust.inQueue()); 
        System.out.println("-----------------------------------------------------------------------");        
    }
    //--------------------------------------------------------
    // Print intermediate and final statistics
    //--------------------------------------------------------
    public static void printStats(String opt)
    {
        printQueue();
        int customers_serviced = total_customers - customers.size();    //total customers - customers in queue
        int total_svc_time     = Server.getTotalBusyTime(servers);
        int total_idle_time    = Server.getTotalIdleTime(servers);
        float avg_wait_time    = (float) total_wait_time / customers_serviced;
        float avg_svc_time     = (float) total_svc_time  / customers_serviced;
        float avg_idle_time    = (float) total_idle_time / num_servers;
 
        System.out.println("|===END OF SIMULATION (" + opt + ")=========================================="); 
        System.out.println("| Number of servers.........: " + num_servers); 
        System.out.println("| Elapsed time..............: " + clock + " seconds"); 
        System.out.println("| Customers serviced........: " + customers_serviced); 
        System.out.println("| Customers still in queue..: " + customers.size() ); 
        System.out.println("| Average wait time.........: " + avg_wait_time);     
        System.out.println("| Maximum wait time.........: " + max_wait_time);     
        System.out.println("| Maximum customers queue...: " + max_queue_size);     
        System.out.println("| Average service time......: " + avg_svc_time);     
        System.out.print  ("| Average server idle time..: " + avg_idle_time); 
        System.out.printf ("  = %.2f%s \n", (avg_idle_time/clock*100) , "%");
        System.out.println("|======================================================================");
        if ( ! opt.equals("Final"))
            System.out.println("\nSERVICING EXISTING & REMAINING CUSTOMERS ==============================");    
    }
}