// Grid.java
// CS201J Fall 2002

import java.util.Vector;
import java.util.Enumeration;

public class Grid {
    // OVERVIEW: A Grid is a 2-dimensional grid.  Each sqaure in the grid is either
    //    empty or contains a SimObject object.
    //
    //    A typical Grid is: [ [ s_0_0, s_0_1, ..., s_0_columns - 1 ],
    //                         [ s_1_0, s_1_1, ..., s_1_columns - 1 ],
    //                         ... ,
    //                         [ s_row-1_0, s_row-1_1, ..., s_row-1_columns - 1 ] ]
    //
    //    Where each s_i_j is either a SimObject object or empty.
    //

    /*@spec_public@*/ private int rows; //@invariant rows > 0;
    /*@spec_public@*/ private int columns; //@invariant columns > 0;

    // Rep Exposure warning: the objects in simobjects are exposed.
    /*@monitored@*/ private Vector simobjects; // a vector of objects of type SimObject

    // Rep Invariant:
    //@invariant simobjects != null
    //@invariant simobjects.elementType == \type(SimObject)
    //@invariant simobjects.containsNull == false
    // invariant for each s: SimObject in simobjects
    //     s.isInitialized
    //     s.getRow () >= 0 && s.getRow () < rows
    //     s.getCol () >= 0 && s.getCol () < columns
    //     no other t: SimObject in simobjects the same row and column

    // Abstraction Function:
    //  AF(c) =    [ [ s_0_0, s_0_1, ..., s_0_columns - 1 ],
    //               [ s_1_0, s_1_1, ..., s_1_columns - 1 ],
    //               ... ,
    //               [ s_row-1_0, s_row-1_1, ..., s_row-1_columns - 1 ] ]
    //   where s_i_j = empty if there is no s: SimObject in simobject with s.getRow () == i and s.getCol () == j
    //         s_i_j = simobjects[k] if simobjects[k].getRow () == i and simobjects[k].getCol () == j

    Grid (int p_rows, int p_columns)
       //@requires p_rows > 0;
       //@requires p_columns > 0;
       // REQUIRES: p_rows and p_columns must be > 0
       // EFFECTS: Constructs a new grid with specified number of rows and
       //          columns.
    {
        rows = p_rows;
        columns = p_columns;
        simobjects = new Vector();
	//@set simobjects.elementType = \type(SimObject)
	//@set simobjects.containsNull = false
    }

    public int numRows()
    // EFFECTS: Returns the number of rows in the grid.
    //@ensures \result == rows;
    {
        return rows;
    }

    public int numColumns()
       // EFFECTS: Returns the number of columns in the grid.
       //@ensures \result == columns;
    {
        return columns;
    }

    synchronized public void startObjects()
	// EFFECTS: Start all object threads.  Previously started
	//	threads will be resumed.
    {
	Enumeration els = simobjects.elements ();

	while (els.hasMoreElements ()) {
	    SimObject current = (SimObject) els.nextElement ();

	    if (current.isPaused ()) {
		current.resumeObject ();
	    } else {
		Thread simObjectThread = new Thread (current);
		simObjectThread.start();
	    }
	}
    }

    synchronized public void pauseObjects ()
	// EFFECTS: Pause all object threads.
    {
	Enumeration els = simobjects.elements ();

	while (els.hasMoreElements ()) {
	    SimObject current = (SimObject) els.nextElement ();
	    current.pauseObject();
	}
    }

    synchronized public void setObjectAt (int row, int col, Class objectClass)
	// REQUIRES: The class represented by objectClass must be of
	//           parent type SimObject.
    {
        if (objectClass != null) {
	    try {
		SimObject newObject = (SimObject) objectClass.newInstance(); //@nowarn Cast;
		//@assume newObject != null;
		//@assume !newObject.isInitialized; // ESC/Java doesn't know about newInstance
		newObject.init (row, col, this);

		// If an object already exists in the grid with the specified
		// row and column, remove it before adding the new one.
		simobjects.remove(getObjectAt(row, col));
		simobjects.add(newObject);
	    } catch (java.lang.InstantiationException e) {
		e.printStackTrace();
	    } catch (java.lang.IllegalAccessException e) {
		e.printStackTrace();
	    }
	}

    }

    synchronized public void setObjectAt (int row, int col, SimObject newObject)
	// REQUIRES: newObject is not initialized
	// EFFECTS: places newObject on the grid at location row, col (replacing any
	//    object currently on the grid at that location).
    {
	newObject.init (row, col, this);

	// If an object already exists in the grid with the specified
	// row and column, remove it before adding the new one.
	simobjects.remove(getObjectAt(row, col));
	simobjects.add(newObject);
    }

    synchronized public boolean isSquareEmpty (int row, int col) throws RuntimeException
        // REQUIRES: 0 >= row < rows && 0 >= col < columns
        // EFFECTS: Returns the Cell at the specified row and column.
	//          Returns null if nothing is present there.
    {
	return (getObjectAt (row, col) == null);
    }

    public boolean validLocation (int row, int col)
	// EFFECTS: Determines if row, col is a valid grid location.
    {
	if((row < 0) || (row > numRows() - 1)) {
	    return false;
	}

	if((col < 0) || (col > numColumns() - 1)) {
	    return false;
	}

	return true;
    }

    // We need the unsynchronized version of getObjectAt for the display --- we want to
    // be able to display the grid without acquiring a lock.  This runs the risk that
    // the grid will be temporarily displayed in an inconsistent state.

    /* unsynchronized*/ public SimObject grabObjectAt (int row, int col) throws RuntimeException
        // REQUIRES: 0 >= row < rows && 0 >= col < columns
        // EFFECTS: Returns the Cell at the specified row and column.
	//          Returns null if nothing is present there.
    {
        if ((row < 0) || (row > numRows () - 1)) {
            throw new RuntimeException ("Bad row parameter to getObjectAt: " + row);
        }

        if ((col < 0) || (col > numColumns () - 1)) {
            throw new RuntimeException ("Bad column parameter to getObjectAt: " + col);
        }

	Enumeration els = simobjects.elements (); //@nowarn Race // Allow the race condition, but be careful using grab.

	while (els.hasMoreElements ()) {
	    SimObject current = (SimObject) els.nextElement ();
	    //@assume current.isInitialized // couldn't specify this invariant in ESC/Java

	    if ((current.getRow() == row) && (current.getColumn() == col))
		{
		    return current;
		}
	}

	return null;
    }

    synchronized public SimObject getObjectAt (int row, int col) throws RuntimeException
        // REQUIRES: 0 >= row < rows && 0 >= col < columns
        // EFFECTS: Returns the Cell at the specified row and column.
	//          Returns null if nothing is present there.
    {
	return grabObjectAt (row, col);
    }

    synchronized public void clearGrid() {
       for (int i = 0; i < numRows(); i++) {
          for (int j = 0; j < numColumns(); j++) {
             simobjects.remove(getObjectAt(i, j));
          }
       }
    }
}