package simulator;

/*
 * AmusementPark.java
 * Created on Jan 6, 2005
 *
 */

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;


/**
 *  AmusementPark - This is the class from which the simulation
 *  is started.  Amusement park contains all the rides and customers
 *  in the park and keeps track of the time.
 *
 *  @author Ping
 *
 */
public class AmusementPark implements ClockedThing
{
    /*
     * Note: In the future, we might want to split up the rides
     * into separate lists as well (from an efficiency prospective).
     */
    // This stores all the rides in the park
    // Although we use a vector here, another possible implementation
    // might want to use Hashtable.  However, we iterate through all
    // rides more often than we look up rides by name.
    protected ArrayList<Attraction> rides;

    // This list stores all the customers waiting to enter
    // the park
    protected MyPriorityQueue<Customer, Time> customersWaitingToEnter;
    // This stores all the customers that are in the park
    // but in transit between rides
    protected ArrayList<Customer> customersInTransit;
    // This list stores all the customers that are done and
    // have left the park
    protected ArrayList<Customer> customersDone;

    // These variables store all the necessary information about time
    protected Time openingTime;
    protected Time closingTime;
    protected Time currTime;

    // These two variables store the entrance and exit of the park
    protected Location entrance;
    protected Location exit;

    // This is just a flag to determine if the park is open or not
    protected boolean parkIsOpen;

    protected Logger logger;

	boolean simRanOnce = false;

    public AmusementPark(Collection<Attraction> rides, Location entrance,
            Location exit, Time openingTime, Time closingTime, Logger logger)
    {
        this.rides = new ArrayList<Attraction>(rides);

        customersInTransit = new ArrayList<Customer>();
        customersDone = new ArrayList<Customer>();
        this.entrance = entrance;
        this.exit = exit;
        this.openingTime = openingTime;
        this.closingTime = closingTime;
        this.logger = logger;
        currTime = null;
        parkIsOpen = false;
    }



    /**
     * This function officially opens the park.  It opens all the
     * rides and takes the list of customers that will enter the park
     * throughout the day
     *
     *  @param customers - All the customers that will be visiting the park
     *  today.
     */
    public void openPark(Collection<Customer> customers)
    {
    	for(Attraction a : rides)
        {
     		// Open all the rides
            a.openRide();
        }
        
    	customersWaitingToEnter = new MyPriorityQueue<Customer, Time>();
        
        // now go through all the customers
        for(Customer c : customers)
        {
        	customersWaitingToEnter.enqueue(c, c.getTimeEntered());
        }
        
        try
        {
            currTime = (Time)openingTime.clone();
        }
        catch(CloneNotSupportedException e)
        {
        	e.printStackTrace();
        }
        logger.parkOpened(currTime);
        parkIsOpen = true;
    }



    /**
     * @return Returns the entrance of the park.
     */
    public Location getEntrance()
    {
        return entrance;
    }

    /**
     * @return Returns the exit of the park.
     */
    public Location getExit()
    {
        return exit;
    }

    /**
     * This function returns true if the park is open, false otherwise
     * @return Returns the parkIsOpen.
     */
    public boolean isParkOpen()
    {
        return parkIsOpen;
    }

    /**
     * This function returns an instance of the current time.
     * Note it returns a reference, so any changes made by this class
     * will also affect the returned clock.
     *
     * @return Time - a reference to the current clock
     */
    public Time getCurrTime()
    {
        return currTime;
    }

    /**
     * This function returns a reference to the opening time
     *
     * Note: Whatever changes made to the returned time instance will
     * also affect this park's closing time and vice versa.
     * @return
     */
    public Time getOpeningTime()
    {
        return openingTime;
    }

    /**
     * This function returns a reference to the closing time
     *
     * Note: Whatever changes made to the returned time instance will
     * also affect this park's closing time and vice versa.
     * @return
     */
    public Time getClosingTime()
    {
        return closingTime;
    }

    /**
     * This function returns an enumeration of all the customers who have
     * finished the simulation (they have exited the park).
     *
     * @return
     */
    public Iterator<Customer> getAllFinishedCustomers()
    {
        return customersDone.iterator();
    }

    /**
     * This function gets a particular attraction based on that attraction's
     * name.
     * @param rideName - The name of the attraction
     * @return A reference to the attraction
     */
    public Attraction getAttraction(String rideName)
    {
        for(Attraction a : rides)
        {
            if(a.getName().equalsIgnoreCase(rideName))
            {
                return a;
            }
        }
        return null;
    }

    /**
     * This function removes the customer from the in-transit list
     * since the customer is no longer in transit.  THe customer however
     * does not get put on another list that is managed by the park
     * (it should now be in one of the lists managed by the individual rides)
     * @param customer The customer to remove
     */
    public void customerArrivedAtDestination(Customer customer)
    {
        customersInTransit.remove(customer);
    }

    /**
     * This function is to let the amusement park know that this customer
     * is now in transit and can move around the park (not in any rides).
     *
     * @param customer The customer that is now in transition
     */
    public void customerInTransit(Customer customer)
    {
        customersInTransit.add(customer);
    }

    /**
     * This returns all the rides in the park.
     * @return
     */
    public Iterator<Attraction> getAllRides()
    {
    	return rides.iterator();
    }




    /**
     * This is to signal one minute has passed in the simulation
     * During each minute, new customers may enter the park, customers
     * in transition can move 1 distance unit, and the ride also
     * process the waiting line as well as the cars on the ride.
     *
     * @return This function returns true if the simulation should keep running
     * false when all the customers have left the park and the park is closed
     */
    public boolean onClockTick()
    {
		// Check to see if the park should still be open
		if(parkIsOpen && currTime.compareTo(closingTime) >= 0)
		{
			parkIsOpen = false;
			// tell each customer in transit that the park is closing
			for(Customer c : customersInTransit)
			{
				c.parkClosing();
			}

			for(Attraction a : rides)
			{
				a.closeRide();
			}
		}

		// now let people enter the park if its time
		if(parkIsOpen)
		{
			while(!customersWaitingToEnter.isEmpty())
			{
				// Keep examining all the customers that can
				// enter
				Customer c = customersWaitingToEnter.peek();
				if(c.getTimeEntered().compareTo(currTime) <= 0)
				{
					// remove the customer from the waiting to enter
					// list
					customersWaitingToEnter.dequeue();

					// now put the customer in the transit queue
					// so he is "in the park"
					customersInTransit.add(c);
					c.enterPark(this, currTime, entrance, logger);
				}
				else
				{
					// since the list is a priority queue sorted
					// based on enter time, as soon as we
					// encounter someone that can't enter yet,
					// we are done.
					break;
				}
			}
		}

		for(int i = 0; i < customersInTransit.size(); i++)
		{
			Customer c = customersInTransit.get(i);
			boolean stillInPark = c.onClockTick();

			if(!stillInPark)
			{
				// this customer is done and has left the park
				customersInTransit.remove(i);
				i--;
				customersDone.add(c);
			}
		}

		// this flag determines some rides still need processing
		// like if they still have people in line or on the ride
		boolean stillNeedToProcessRides = false;
		for(Attraction a : rides)
		{
			boolean rideNotDone = a.onClockTick();
			if(rideNotDone)
			{
				stillNeedToProcessRides = true;
			}
		}

		currTime.onClockTick();

		boolean isSimulationGoing = stillNeedToProcessRides ||
			parkIsOpen || !customersInTransit.isEmpty();

		if(!isSimulationGoing)
		{
			// everything is done, so lets close it all up
			logger.parkClosed();
		}

		return isSimulationGoing;
    }
}

