import javax.swing.*;
import java.awt.*;

public class GridDisplay extends JPanel implements Runnable {
	// OVERVIEW: A GridDisplay is a subtype of JPanel for displaying a Grid.

	private Grid grid;
	//@invariant grid != null

	private SimObjectChooser simobjchooser;
	//@invariant simobjchooser != null
	
	private AgeChooser agechooser; 
	//@invariant agechooser != null

	private boolean stopThread;
	final static int SLEEP_DELAY = 200;

	//@requires grid != null
	//@requires simobjchooser != null
	//@requires agechooser != null
	GridDisplay(Grid grid, SimObjectChooser simobjchooser, AgeChooser agechooser) {
		this.grid = grid;
		this.simobjchooser = simobjchooser;
		this.agechooser = agechooser;
		this.stopThread = true;
		setOpaque(false); // This is necessary to make painting work		
	}

	private int getSquareWidth()
	//@ensures \result > 0
	{
		int swidth = getWidth() / grid.numHorizontal();

		if (swidth > 0) {
			return swidth;
		} else {
			return 1;
		}
	}

	private int getSquareHeight()
	//@ensures \result > 0
	{
		int sheight = getHeight() / grid.numVertical();

		if (sheight > 0) {
			return sheight;
		} else {
			return 1;
		}
	}

	private int getHorizontalOffset() {
		return (getWidth() - (getSquareWidth() * grid.numHorizontal())) / 2;
	}

	private int getVerticalOffset() {
		return (getHeight() - (getSquareHeight() * grid.numVertical())) / 2;
	}

	public void setObjectAt(int display_x, int display_y) {
		if (stopThread) {
			// Only allow grid locations to be set if
			// the simulation is stopped.
			int square_y =
				(display_y - getVerticalOffset()) / getSquareHeight();
			int square_x =
				(display_x - getHorizontalOffset()) / getSquareWidth();
			int age = agechooser.getSelectedAge();
			System.err.println ("Selected age: " + age);
			SimObject newObject;

			try {
				Class c = simobjchooser.getSelectedClass();

				if (c != null) {
					newObject = (SimObject) (c.newInstance()); //@nowarn Cast
				} else {
					System.err.println("Null object chooser!");
					return;
				}

				//@nowarn Cast;
			} catch (InstantiationException e) {
				System.err.println("InstantiationException: " + e);
				e.printStackTrace();
				return;
			} catch (IllegalAccessException e) {
				System.err.println("IllegalAccessException: " + e);
				e.printStackTrace();
				return;
			}

			//@assume newObject != null;
			//@assume !newObject.isInitialized; // ESC/Java doesn't know about newInstance
			
			if (newObject instanceof Human) {
				Human newhuman = (Human) newObject;
				newhuman.setHumanAge(age); 
			}
			newObject.init(square_x, square_y, grid);
			try {
				grid.setObjectAt(square_x, square_y, newObject);
			} catch (BadLocationException ble) {
				// This should never happen
				System.err.println(
					"Bad location: " + square_x + ", " + square_y);
				System.exit(-1);
			}
			repaint();
		}
	}

	public void startObjects() {
		grid.startObjects(); // Start SimObject threads.
		Thread displayThread = new Thread(this);
		displayThread.start();
	}

	public void run() {
		stopThread = false;

		while (!stopThread) {
			repaint();
			try {
				Thread.sleep(SLEEP_DELAY);
			} catch (java.lang.InterruptedException e) {
				System.out.println("Display thread interrupted.");
				break;
			}
		}
	}

	public void pauseObjects() {
		grid.pauseObjects(); // Pause SimObject threads.
		this.stopThread = true;
	}

	public void paintComponent(Graphics g) throws RuntimeException {
		//@ assume g != null;
		int squarewidth = getSquareWidth();
		int squareheight = getSquareHeight();

		int hoffset = getHorizontalOffset();
		int voffset = getVerticalOffset();

		for (int y = 0; y < grid.numVertical(); y++) {
			for (int x = 0; x < grid.numHorizontal(); x++) {
				SimObject tmp = grid.grabObjectAt(x, y);

				if (tmp == null) {
					g.setColor(Color.black);
				} else {
					g.setColor(tmp.getColor());
				}

				g.fillRect(
					hoffset + x * squarewidth,
					voffset + y * squareheight,
					squarewidth - 1,
					squareheight - 1);
			}
		}
	}
}