import java.util.*;

class GasStation {
    public GasStation(Scheduler s){
        scheduler = s;
    }

        // *** Methods corresponding to each of the events.

        // Find a pump for the driver, if there is one, otherwise
        // add them to the queue.
    public void arrival(Driver d){
        // See if there is a pump free.
        Pump pumpToUse = findPump(d.getFuelType());

        if(pumpToUse != null){
            assignPump(pumpToUse,d);
        }
        else{
            addToPumpQueue(d);
        }
    }

    public void startPumping(Driver d){
        try{
            long amountDelivered = d.fillUp();
            // Schedule the StopPumping event.
            Scheduler scheduler = getScheduler();
            scheduler.schedule(new StopPumping(scheduler.getTime()+
                                    amountDelivered*timePerLitre,d));
        }
        catch(Pump.FuelTypeNotAvailable e){
            System.out.println("Assigned pump doesn't have the right fuel type.");
        }
    }

        // The driver has finished delivery of fuel. Go
        // to pay for it.
    public void stopPumping(Driver d){
        Cashier[] cashiers = getCashiers();
        for(int i = 0; i < cashiers.length; i++){
            final Cashier c = cashiers[i];
            if(c.isFree()){
                assignCashier(c,d);
                return;
            }
        }
        // No free cashier.
        addToCashierQueue(d);
    }

    public void startPaying(Driver d){
        // Free the pump.
        releasePump(d.getPump());
        // Schedule the StopPaying event.
        PaymentMethod payment = d.getPaymentMethod();
        Scheduler scheduler = getScheduler();
        scheduler.schedule(
                new StopPaying(scheduler.getTime()+payment.paymentTime(),d));
    }

        // The driver has finished paying and is about to leave.
    public void stopPaying(Driver d) throws RuntimeException {
        // Schedule a DriverDeparture event, for statistical purposes.
        Scheduler s = getScheduler();
        s.schedule(new DriverDeparture(s.getTime(),d));

        // Free the cashier for the next in line.
        Cashier c = d.getCashier();
        if(c != null){
            c.release();
            Queue q = getCashierQueue();
            if(q.size() > 0){
                Driver nextDriver = (Driver) q.next();
                assignCashier(c,nextDriver);
            }
        }
        else{
            throw new RuntimeException("Driver without a Cashier.");
        }
    }

        // The driver is about to leave, either through completing
        // purchase of fuel, or because no fuel was available.
    public void departure(Driver d){
        // Collect the driver's events for statistical analysis.
        // ... Omitted for clarity ...
    }

        // Refill all of the tanks.
    public void refill(){
        Tank[] tanks = getTanks();
        for(int i = 0; i < tanks.length; i++){
            System.out.println(tanks[i].getFuelType()+
                                " down to: "+tanks[i].getLevel());
            tanks[i].refill();
        }
    }

        // *** Methods concerned with manipulation of the pumps and cashiers.

        // Allocate the free pump to the given driver.
    protected void assignPump(Pump p,Driver d){
        Scheduler s = getScheduler();

        if(p.canSupply(d.getFuelType())){
            // Claim the pump.
            p.lock();
            // Inform the driver.
            d.setPump(p);
            s.schedule(new StartPumping(s.getTime(),d));
        }
        else{
            // We have no fuel of this type. The driver will leave.
            s.schedule(new DriverDeparture(s.getTime(),d));
            System.out.println("*** No fuel ***");
        }
    }

        // Find a free pump supplying the given type of fuel.
    protected Pump findPump(FuelType type){
        Pump[] pumps = getPumps();
        for(int i = 0; i < pumps.length; i++){
            if(pumps[i].isFree() && pumps[i].hasFuelType(type)){
                return pumps[i];
            }
        }
        return null;
    }

        // The pump has been released by the driver it was
        // assigned to paying the cashier.
    protected void releasePump(Pump p) throws RuntimeException {
        if(p != null){
            p.release();
            ArrayList list = getPumpQueue();
            final int numWaiting = list.size();
            for(int i = 0; i < numWaiting; i++){
                Driver d = (Driver) list.get(i);
                Pump pumpToUse = findPump(d.getFuelType());
                if(pumpToUse != null){
                    // Found one who can use it.
                    list.remove(i);
                    assignPump(pumpToUse,d);
                    return;
                }
            }
            // No one can use the pump that has been freed.
        }
        else{
            throw new RuntimeException("No pump assigned to Driver.");
        }
    }

        // Assign the free cashier to the given driver.
    protected void assignCashier(Cashier c,Driver d){
        c.lock();
        d.setCashier(c);
        Scheduler s = getScheduler();
        s.schedule(new StartPaying(s.getTime(),d));
    }

        // Add the driver to the queue.
    protected void addToPumpQueue(Driver d){
        ArrayList list = getPumpQueue();
        list.add(d);
        System.out.println("Pump queue length = "+list.size());
    }

        // Add the driver to the queue.
    protected void addToCashierQueue(Driver d){
        Queue q = getCashierQueue();
        q.add(d);
        System.out.println("Cashier queue length = "+q.size());
    }

        // *** General accessors.

    protected ArrayList getPumpQueue(){
        return waitingForPump;
    }

    protected Queue getCashierQueue(){
        return waitingForCashier;
    }

    protected Tank[] getTanks(){
        return tanks;
    }

    protected Pump[] getPumps(){
        return pumps;
    }

    protected Cashier[] getCashiers(){
        return cashiers;
    }

    protected Scheduler getScheduler(){
        return scheduler;
    }

        // *** Attributes

    // Queue of drivers waiting for a free pump.
    // Removal could be from arbitrary locations.
    private final ArrayList waitingForPump = new ArrayList();

    // Queue of drivers waiting for a free Cashier.
    // Removal will be queue-like.
    private final Queue waitingForCashier = new Queue();

    private final long tankCapacity = 90000L;
    private final int numTanks = 3;

        // Attributes relating to fuel and pumps.
    // Have access to a FuelFactory.
    private static final FuelFactory fuelFactory = FuelFactory.getInstance();

    // Indices for the FuelType objects stored in tanks[].
    private final int DIESEL = 0, LEADED = 1, UNLEADED = 2;

    // One Tank for each fuel type.
    private final Tank[] tanks = new Tank[numTanks];
    {
        tanks[DIESEL] = new Tank(tankCapacity,fuelFactory.getFuel("Diesel"));
        tanks[LEADED] = new Tank(tankCapacity,fuelFactory.getFuel("Leaded"));
        tanks[UNLEADED] = new Tank(tankCapacity,fuelFactory.getFuel("Unleaded"));
    }

    // How long to deliver a litre of fuel, in seconds.
    private final long timePerLitre = 1L;

    private final int numPumps = 7;
    // Connect the pumps to the tanks.
    private final Pump[] pumps = new Pump[numPumps];
    {
        pumps[0] = new Pump(new Tank[] {tanks[UNLEADED],tanks[LEADED]});
        pumps[1] = new Pump(new Tank[] {tanks[UNLEADED],tanks[LEADED]});
        pumps[2] = new Pump(new Tank[] {tanks[UNLEADED],tanks[DIESEL]});
        pumps[3] = new Pump(new Tank[] {tanks[UNLEADED]});
        pumps[4] = new Pump(new Tank[] {tanks[UNLEADED]});
        pumps[5] = new Pump(new Tank[] {tanks[UNLEADED]});
        pumps[6] = new Pump(new Tank[] {tanks[DIESEL]});
    }

        // Create the required number of cashiers.
    private final Cashier[] cashiers = {
        new Cashier(),
    };

    // The simulation scheduler.
    private final Scheduler scheduler;
}
