// 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
    private Direction userdir = new Direction (1,0);
    
    // 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 != null
        //@requires !newObject.isInitialized;
    //@ensures newObject.isInitialized;
    // 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);
    }
    
    public Direction UserDirection () {
        return userdir;
    }
    
    public void setUserDirection(Direction N) {
        userdir = N;
    }
}