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();
	 }
	 }
	
	public void killObjectAt(int row, int col) {
	//Requires: Class represented by Object class must be of parent type SimObject
		
		
		simobjects.remove(getObjectAt(row,col));
		
	}


	 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) throws BadLocationException
		//@requires newObject != null
		//@requires row >= 0 && row < xsize
		//@requires col >= 0 && col < ysize
		// EFFECTS: places newObject on the grid at location (row,col) (replacing any
		//    object currently on the grid at that location).
		{ //@nowarn Deadlock
			if (validLocation (row, col)) {
				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);
				//simobjects[x][y] = newObject; //@nowarn ArrayStore
			} else {
				throw new BadLocationException ("" + row + ", " + col);
			}
		} //@nowarn Exception
		
	synchronized public void setObjectAt(int row, int col, StationaryObstacle newObject) throws BadLocationException
			//@requires newObject != null
			//@requires row >= 0 && row < xsize
			//@requires col >= 0 && col < ysize
			// EFFECTS: places newObject on the grid at location (row,col) (replacing any
			//    object currently on the grid at that location).
			{ //@nowarn Deadlock
				if (validLocation (row, col)) {
					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);
					//simobjects[x][y] = newObject; //@nowarn ArrayStore
				} else {
					throw new BadLocationException ("" + row + ", " + col);
				}
			} //@nowarn Exception

	 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;
	 }

	//this function getObjectAt will lock the grid before calling 
	//grabObjectAt so that it is guaranteed that there is no other
	//object in a certain location.  Especially helpful for the 
	//executeTurn function.
	 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);
	 }
 }