/*
 * Class GridPuzzleGenerator
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.io.*;
import java.util.Calendar;
import java.util.Vector;

/** <dt><b>Direct Known Subclasses:</b></dt><dd><a href="GridPuzzleGenerator.Canvas.html">GridPuzzleGenerator.Canvas</a><br><br></dd>
 * GridPuzzleGenerator generates an MDP Grid-type problem input files to be used by the PosgInputFileParser object
 * @author Mark Gruman -- UMass RBR Lab <br>
 * Computer Science Department <br>
 * University of Massachusetts Amherst <br>
 * @version 1.0 <br>
 * Created on July 08, 2004 <br><br>
 */
public class GridPuzzleGenerator  
{
	private Canvas m_canvas;
	private String file_name;
	private BufferedWriter m_log_writer;
	private GridInputFileParser m_parser;
	private int rows, cols;
	private int m_agents;
	private int m_states;
	private String[] m_acs;
	private String[] m_obs;
	private String[][] visible_obs;
	private double[] t_prob;

	public static final int DECIMALS = 5;

	/** @return the number of rows specified by the user */
	public int getRows()
	{
		return rows;
	}

	/** @return the number of columns specified by the user */
	public int getCols()
	{
		return cols;
	}

	/** This method sets the total number of possible states */
	private void setStates()
	{
		m_states = (int)Math.pow(((getRows()-1)*(getCols()-1)),2);
		try {
			m_log_writer.write("States: " + m_states);
			m_log_writer.newLine();
			m_log_writer.flush();
			m_parser.parse();
		}
		catch (IOException ioe) { System.out.println(ioe); }
	}

	/** This method defines all agents' actions*/
	private void createActions()
	{
		String acs = "up down left right stay";
		writeParam("Actions: ", acs);
		m_acs = acs.split("\\s");
	}

	/** This method writes parameters and their corresponding values to the pre-defined text file 
	 * @param a the paramter tag ("actions" or "observations")
	 * @param s the enumerated parameter values
	 */
	private void writeParam(String a, String s)
	{
		try {
			m_log_writer.newLine();
			for (int i=0; i<m_agents; i++) {
				m_log_writer.write(a + s);
				m_log_writer.newLine();
				a = "";
			}
			m_log_writer.flush();
			m_parser.parse();
		}
		catch (IOException ioe) { System.out.println(ioe); }
	}

	/** This method defines all agents' observations*/
	private void createObservations()
	{
		String[][] temp = { {"y", "y", "y"}, {"y", "y", "y"}, {"y", "y", "y"} };
		visible_obs = temp;

		for (int i=1; i<4; i++)
			for (int j=1; j<4; j++)
				if (((Grid)getCanvas().getScreen(3)).getData()[i][j] != 2) 
					visible_obs[i-1][j-1] = "n";

		Vector obs = new Vector();
		String res = "";

		for (int r=1; r<((Grid)getCanvas().getScreen(2)).getData().length-1; r++)
			for (int c=1; c<((Grid)getCanvas().getScreen(2)).getData()[r].length-1; c++) {
				String value = getObsValue(r, c, visible_obs);
				if (!obs.contains(value)) {
					obs.addElement(value);
					res += value + " ";
				}
			}
		writeParam("Observations: ",res);
		m_obs = res.split("\\s");
	}

	/** This method returns an observation corresponding to the grid x and y position of the agent
	 * @param x the given agent's x position
	 * @param y the given agent's y position
	 * @param v the observation feasibility matrix
	 * @return the corresponding state
	 */
	private String getObsValue(int x, int y, String[][] v)
	{
		String temp_value = "";
		for (int i=x-1; i<=x+1; i++)
			for (int j=y-1; j<=y+1; j++) {
				if (((Grid)getCanvas().getScreen(2)).getData()[i][j] != -1)
					temp_value += v[i-x+1][j-y+1];
				else
					temp_value += "n";
			}
		return temp_value;
	}

	/** This method sets the probabilities for the Observations matrix*/
	private void setObsrvProbs()
	{
		try {
			m_log_writer.newLine();
			for (int s1=0; s1<m_states; s1++)
 				m_log_writer.write("O: * " + s1 + 
				" " + getObservationSet(setStateBoard(s1)) + " 1.0\r\n");
			m_log_writer.flush();
			m_parser.parse();
		}
		catch (IOException ioe) { System.out.println(ioe); }
	}


	/** This method sets the probabilities for the Transitions matrix*/
	private void setTransProbs()
	{
		try {
			m_log_writer.newLine();
			for (int s1=0; s1<m_states; s1++)
				for (int a1=0; a1<m_acs.length; a1++)
					for (int a2=0; a2<m_acs.length; a2++) {
						int[] position = setStateBoard(s1);
						double[][] p1_res = getNewPos(position[0], position[1], a1);
						double[][] p2_res = getNewPos(position[2], position[3], a2);
						writeProbs(p1_res, p2_res, a1, a2, s1);
				}
			m_log_writer.flush();
			m_parser.parse();
		}
		catch (IOException ioe) { System.out.println(ioe); }
	}

	/** This method writes the transitional and observational probabilities to the pre-defined text file
	 * @param ag1 the transitional/observational probabilities of agent 1
	 * @param ag2 the transitional/observational probabilities of agent 2
	 * @param a1 agent 1's action
	 * @param a2 agent 2's action
	 * @param state the agents' starting state
	 */
	private void writeProbs(double[][] ag1, double[][] ag2, int a1, int a2, int state)
	{
		for (int i=0; i<ag1[0].length; i++) {
			if (ag1[0][i] == 0.0)
				continue;

			for (int j=0; j<ag2[0].length; j++) {
				if (ag2[0][j] == 0.0)
					continue;

				int[] end_state = { (int)ag1[1][i], (int)ag1[2][i], (int)ag2[1][j], (int)ag2[2][j] };
				try {
					m_log_writer.write("T: " + m_acs[a1] + " " + m_acs[a2] + " " + state +
						" " + getStateValue(end_state) + " " + updatePrecision(ag1[0][i], ag2[0][j]) + "\r\n");
				}
				catch (IOException ioe) { System.out.println(ioe); }
			}
		}
	}

	/** This method creates a probabilities matrix of all possible moves and their corresponding 
	 * likelihood, given the agent's starting position and action
	 * @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @param act the agent's action
	 * @return the probabilities matrix of all possible moves
	 */
	private double[][] getNewPos(int x, int y, int act)
	{
		double[][] res = new double[3][5];

		switch (act) {
			case 0: //move up
				move(res, x, y, 0, 1, 2, 3, 4);
				break;

			case 1: //move down
				move(res, x, y, 3, 2, 1, 0, 4);
				break;

			case 2: //move left
				move(res, x, y, 2, 0, 3, 1, 4);
				break;

			case 3: //move right
				move(res, x, y, 1, 3, 0, 2, 4);
				break;

			case 4: //stay
				move(res, x, y, 1, 2, 3, 4, 0);
				res[0][0] = 1.0;
				for (int i=1; i<res[0].length; i++)
					res[0][i] = 0.0;
				break;
		}
		return res;
	}

	/** This method sets the probabilities of all possible moves given the agent's starting position
	 * @param temp the probabilities matrix to be updated
	 * @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @param up the column in the probabilities matrix corresponding to the action "up"
	 * @param left the column in the probabilities matrix corresponding to the action "left"
	 * @param right the column in the probabilities matrix corresponding to the action "right"
	 * @param down the column in the probabilities matrix corresponding to the action "down"
	 * @param stay the column in the probabilities matrix corresponding to the action "stay"
	 */
	private void move(double[][] temp, int x, int y, int up, int left, int right, int down, int stay)
	{
				moveUp(temp, x, y, up);
				moveLeft(temp, x, y, left);
				moveRight(temp, x, y, right);
				moveDown(temp, x, y, down);
				stay(temp, x, y, stay);
	}

	/** This method sets the probability that the agent moves up given its current position
	 * @param temp the probabilities matrix to be updated
	 * @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @param col the column in the probabilities matrix corresponding to the action "up"
	 */
	private void moveUp(double[][] temp, int x, int y, int col)
	{
		temp[1][col] = x-1;
		temp[2][col] = y;
		if (movePossible((x-1), y))
			temp[0][col] = t_prob[col];
		else 
			temp[0][4] += t_prob[col];
	}

	/** This method sets the probability that the agent moves up given its current position
	 * @param temp the probabilities matrix to be updated
	 * @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @param col the column in the probabilities matrix corresponding to the action "left"
	 */
	private void moveLeft(double[][] temp, int x, int y, int col)
	{
		temp[1][col] = x;
		temp[2][col] = y-1;
		if (movePossible(x, (y-1)))
			temp[0][col] = t_prob[col];
		else 
			temp[0][4] += t_prob[col];
	}

	/** This method sets the probability that the agent moves up given its current position
	 * @param temp the probabilities matrix to be updated
	 * @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @param col the column in the probabilities matrix corresponding to the action "right"
	 */
	private void moveRight(double[][] temp, int x, int y, int col)
	{
		temp[1][col] = x;
		temp[2][col] = y+1;
		if (movePossible(x, (y+1)))
			temp[0][col] = t_prob[col];
		else 
			temp[0][4] += t_prob[col];
	}

	/** This method sets the probability that the agent moves up given its current position
	 * @param temp the probabilities matrix to be updated
	 * @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @param col the column in the probabilities matrix corresponding to the action "down"
	 */
	private void moveDown(double[][] temp, int x, int y, int col)
	{
		temp[1][col] = x+1;
		temp[2][col] = y;
		if (movePossible((x+1), y))
			temp[0][col] = t_prob[col];
		else 
			temp[0][4] += t_prob[col];
	}

	/** This method sets the probability that the agent moves up given its current position
	 * @param temp the probabilities matrix to be updated
	 * @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @param col the column in the probabilities matrix corresponding to the action "stay"
	 */
	private void stay(double[][] temp, int x, int y, int col)
	{
		temp[0][col] += t_prob[col];
		temp[1][col] = x;
		temp[2][col] = y;
	}

	/** @param x the agent's starting x position
	 * @param y the agent's starting y position
	 * @return true if an agent can move to the grid cell with the given x,y coordinates, false otherwise
	 */
	private boolean movePossible(int x, int y)
	{
		return (((Grid)getCanvas().getScreen(2)).getData()[x+1][y+1] != -1);
	}

	/** This method returns the x,y positions of both agents in the specified state
	 * @param state the state the agents are in
	 * @return the corresponding x,y poitions of the agents
	 */
	private int[] setStateBoard(int state)
	{
		int x1 = (state / (int)Math.sqrt(m_states)) / (((Grid)getCanvas().getScreen(2)).getData()[0].length-2);
		int y1 = (state / (int)Math.sqrt(m_states)) % (((Grid)getCanvas().getScreen(2)).getData()[0].length-2);

		int x2 = (state % (int)Math.sqrt(m_states)) / (((Grid)getCanvas().getScreen(2)).getData()[0].length-2);
		int y2 = (state % (int)Math.sqrt(m_states)) % (((Grid)getCanvas().getScreen(2)).getData()[0].length-2);

		int[] res = {x1, y1, x2, y2};
		return (res);
	}

	/** This method returns the state given both agent's positions
	 * @param pos the x,y positions of both agents
	 * @return the corresponding state of the agents
	 */
	private int getStateValue(int[] pos)
	{
		return (pos[0]*(int)Math.sqrt(m_states)*(((Grid)getCanvas().getScreen(2)).getData()[0].length-2) + 
			pos[1]*(int)Math.sqrt(m_states) + pos[2]*(((Grid)getCanvas().getScreen(2)).getData()[0].length-2) + pos[3]);
	}

	/** This method returns the set of observations made by both agents given their positions
	 * @param pos the x,y positions of both agents
	 * @return the corresponding set of observations made by both agents
	 */
	private String getObservationSet(int[] pos)
	{
		return( getObsValue(pos[0]+1, pos[1]+1, visible_obs) + " " + getObsValue(pos[2]+1, pos[3]+1, visible_obs) );
	}

	/** This method multiplies two doubles with a pre-defined precision factor
	 * @param a the first double to be multiplied
	 * @param b the second double to be multiplied
	 * @return the product of the two doubles
	 */
	private double updatePrecision(double a, double b)
	{
		String temp = Double.toString(a*b);
		if (temp.length() < (DECIMALS+2) )
			for (int i=temp.length(); i<(DECIMALS+2); i++)
				temp += "0";
		return Double.parseDouble(temp.substring(0,(DECIMALS+2)));
	}

	/** This method sets the rewards probabilities and writes them to the pre-defined text file */
	private void setRwrdProb()
	{
		try {
			m_log_writer.newLine();
			for (int i=0; i<m_states; i++)
				if ( i % ((getRows()-1)*(getCols()-1)+1) == 0 )
					m_log_writer.write("R: * * " + i + " * 1.0\r\n");

			m_log_writer.flush();
			m_parser.parse();
		}
		catch (IOException e) {	System.out.println(e); } 
	}

	/** This method sets the starting state probabilities and writes them to the pre-defined text file */
	private void setStart()
	{
		String start = "Start: ";
		int s = 0;
		int agnt = 0;
		for (int r=1; r<((Grid)getCanvas().getScreen(2)).getData().length-1; r++)
			for (int c=1; c<((Grid)getCanvas().getScreen(2)).getData()[r].length-1; c++) 
				if (((Grid)getCanvas().getScreen(2)).getData()[r][c] == 1) {
					agnt++;
					if (agnt == 1) //first agent encountered
						s = ((r-1)*(((Grid)getCanvas().getScreen(2)).getData()[r].length-2)+(c-1))*(int)Math.sqrt(m_states);
					else
						s += (r-1)*(((Grid)getCanvas().getScreen(2)).getData()[r].length-2)+(c-1);
				}

		for (int i=0; i<m_states; i++) {
			if (i == s)
				start += "1.0 ";
			else
				start += "0.0 ";
		}

		try {
			m_log_writer.write(start);
			m_log_writer.newLine();
			m_log_writer.flush();
		}
		catch (IOException e) {	System.out.println(e); }
	}

	/** The main method creates a new instance of the MdpPuzzleGenerator class. */
	public static void main(String[] args) 
	{
		GridPuzzleGenerator pg = new GridPuzzleGenerator();
		pg.getCanvas().start();
	}

	/** Constructor creates a new canvas */
	private GridPuzzleGenerator()
	{
		m_agents = 2;
		m_states = 1;
		m_canvas = new Canvas();
	}

	/** This method returns the associated Canvas object 
	 * @return the corresponding Canvas
	 */
	private Canvas getCanvas()
	{
		return m_canvas;
	}

	/** This method returns the associated PosgInputFileParser object 
	 * @return the corresponding PosgInputFileParser
	 */
	public GridInputFileParser getParser()
	{
		return m_parser;
	}

	/** This method processes all data entered by users in GUI screen 1 and transfers the data into a
	 * newly-created and properly-formatted corresponding text file
	 * @return true if new file was succesfully created and all required data was properly formatted,
	 * false otherwise
	 */
	private boolean processScreenOne()
	{
		String s = "";
		Component[] c = getCanvas().getScreen(1).getComponents();
		try {
			for (int i=0; i<c.length-2; i+=2) {
				try {
					s = ((JLabel)c[i]).getText();

					if (s.equals("File: ")) {
						file_name = ((JTextField)c[i+1]).getText();
						if (file_name.equals("")) 
							return false;
						File file = new File(file_name);

						m_log_writer = new BufferedWriter(new FileWriter(file));
						m_log_writer.write("# File: " + file_name);
						m_log_writer.newLine();
						m_log_writer.write("# Created on: " + Calendar.getInstance().getTime());
						m_log_writer.newLine();
						m_log_writer.newLine();
						m_log_writer.write("Agents: " + m_agents);
						m_log_writer.newLine();

					} else if (s.equals("Values: ")) {
						if (!((JComboBox)c[i+1]).getSelectedItem().equals(" -- ")) {
							m_log_writer.write(s + ((JComboBox)c[i+1]).getSelectedItem());
							m_log_writer.newLine();
						}
						else
							return false;

					} else if (((JTextField)c[i+1]).getText().equals("")) {
						return false;

					} else if (s.equals("Rows: ")) {
						rows = Integer.parseInt(((JTextField)c[3]).getText())+1;

					} else if (s.equals("Cols: ")) {
						cols = Integer.parseInt(((JTextField)c[5]).getText())+1;

					} else  {
						m_log_writer.write(s + ((JTextField)c[i+1]).getText());
						m_log_writer.newLine();
					}
				}
				catch (Exception e) {
					System.out.println(e + " in " + s);
					System.out.println("Data not formatted properly in screen 1");
					System.exit(1);
				}
			}
			m_log_writer.flush();
			m_parser = new GridInputFileParser(file_name);
		}
		catch (IOException e) {
			System.out.println(e);
			System.exit(1);
		}
		return true;
	}

	/** This method processes all data entered by users in GUI screen 2 and transfers the data into a
	 * newly-created and properly-formatted corresponding text file
	 * @return true if new file was succesfully created and all required data was properly formatted,
	 * false otherwise
	 */
	private boolean processScreenTwo()
	{
		int temp_agents = 0;
		for (int i=0; i<((Grid)getCanvas().getScreen(2)).getData().length; i++)
			for (int j=0; j<((Grid)getCanvas().getScreen(2)).getData()[i].length; j++)
				if ( ((Grid)getCanvas().getScreen(2)).getData()[i][j] == 1 )
					temp_agents++;

		if (temp_agents != m_agents) {
			System.out.println("Incorrect Number of Agents");
			return false;
		}

		int temp_obs = 0;
		for (int i=0; i<((Grid)getCanvas().getScreen(3)).getData().length; i++)
			for (int j=0; j<((Grid)getCanvas().getScreen(3)).getData()[i].length; j++)
				if ( ((Grid)getCanvas().getScreen(3)).getData()[i][j] == 2 )
					temp_obs++;

		if (temp_obs < 1) {
			System.out.println("At least one cell surrounding the agent's location must be observed");
			return false;
		}

		return true;
	}

	/** This method processes all data entered by users in GUI screen 3 and transfers the data into a
	 * newly-created and properly-formatted corresponding text file
	 * @return true if new file was succesfully created and all required data was properly formatted,
	 * false otherwise
	 */
	private boolean processScreenThree()
	{
		t_prob = new double[5];
		double total = 0.0;
		Component[] c = getCanvas().getScreen(4).getComponents();

		for (int i=2; i<c.length; i+=2) {
			try {
				t_prob[i/2-1] = Double.parseDouble(((JTextField)c[i]).getText());
				total += t_prob[i/2-1];
			}

			catch (Exception e) 
			{
				System.out.println(e + "\r\nImproper Transitional Values Entered"); 
				return false;
			}
		}

		if ((int)((1.0-total)*10*DECIMALS) != 0)  // 1.0 - total >= epsilon
			return false;
		return true;
	}


//	**************************
//	*** Child Class Canvas ***
//	**************************

	/** Canvas is a GUI for the GridPuzzleGenerator class */
	class Canvas extends Thread implements ActionListener, MouseListener, MouseMotionListener
	{
		private JFrame m_frame;  // main frame window
		private JPanel m_screen1, m_screen3;  // the viewable screens
		private Grid obs_grid, m_screen2;  // the viewable grids
		int x_off=0, y_off=0, x_start=0, y_start=0;  // offsets and starting locations
		int agents;
		Point a_cord, o_cord;
		String s;

		/** This method displays the appropriate screen when buttons are clicked
		 * @param e an ActionEvent invoked by the JVM when a button is clicked
		 */
		public void actionPerformed(ActionEvent e) 
		{
			if (e.getActionCommand().equals("Generate Grid")) {
				if (processScreenOne()) {
					setStates();
					createScreen2();
					m_frame.getContentPane().remove(m_screen1);
					m_screen2.setOpaque(false);
					m_frame.getContentPane().add(m_screen2);
					m_frame.pack();
					m_frame.getContentPane().add(obs_grid);
					m_frame.validate();
				} else
					System.out.println("Insufficient Data or Incorrect Data Format.\r\nPlease remember to enter the entire URL in the filename field");

			} else if (e.getActionCommand().equals("Process Grid")) {
				if (processScreenTwo()) {
					setStart();
					createActions();
					createObservations();
					createScreen3();
					m_frame.getContentPane().remove(m_screen2);
					m_frame.getContentPane().remove(obs_grid);
					m_frame.getContentPane().add(m_screen3);
					m_frame.pack();
				}
	
			} else if (e.getActionCommand().equals("Finish and Exit")) {
				if (processScreenThree()) {
					setTransProbs();
					setObsrvProbs();
					setRwrdProb();
					System.exit(0);
/*	createScreen1();
	m_frame.getContentPane().remove(m_screen3);
	m_frame.getContentPane().add(m_screen1);
	m_frame.pack();
*/				}
			}
		}

		/** This method sets the specified obervations in the observations grid
		 * @param e a MouseEvent invoked by the JVM when the mouse is clicked 
		 */
		public void mouseClicked(MouseEvent e) 
		{
			if (obs_grid.containsPoint(e.getX(), e.getY())) {
				obs_grid.fillObs(e.getX(), e.getY()); 
				m_frame.repaint();
			}
		}

		/* @param e a MouseEvent invoked by the JVM when the mouse enters */
		public void mouseEntered(MouseEvent e) { }

		/* @param e a MouseEvent invoked by the JVM when the mouse exits */
		public void mouseExited(MouseEvent e) { }

		/** This method record the starting location of a moving Agent or Observation marker
		 * and creates a new marker of the same type in its origin if it has moved away from it
		 * @param e a MouseEvent invoked by the JVM when the mouse is pressed 
		 */
		public void mousePressed(MouseEvent e) 
		{
			JComponent c = (JComponent)e.getSource();
			if (c == obs_grid)
				return;

			x_off = e.getX();
			y_off = e.getY(); 
			s = ((JLabel)e.getComponent()).getText();

			x_start = c.getX();
			y_start = c.getY();
			if (x_start == (int)a_cord.getX() && (y_start == (int)a_cord.getY() || y_start == (int)o_cord.getY()))
				insertLabel(s, m_screen2, new Point(x_start, y_start), 44); 				
		} 

		/** This method sets the permanent new location of a moving Agent or Observation marker 
		 * if its move was valid
		 * @param e a MouseEvent invoked by the JVM when the mouse is released 
		 */
		public void mouseReleased(MouseEvent e) 
		{
			JComponent c = (JComponent)e.getSource();
			if (c == obs_grid)
				return;

			if (!m_screen2.contains(c)) {
			//released off the grid
				if (m_screen2.containsPoint(x_start, y_start)) {
					m_screen2.getData()[y_start/100][x_start/100] = 0;
					agents++;
				}
				m_screen2.remove(c);
				m_frame.repaint();

			} else if (x_start/100 == c.getX()/100 && y_start/100 == c.getY()/100) { 
			//released onto same place
				center(c);

			} else if (m_screen2.getData()[c.getY()/100][c.getX()/100] != 0) {
			//released onto another taken square
				if (m_screen2.containsPoint(x_start, y_start))
					insertLabel(s, m_screen2, new Point(x_start, y_start), 44);
				m_screen2.remove(c);
				m_frame.repaint();

			} else if (s.equals("A") && agents == 0 && !m_screen2.containsPoint(x_start, y_start)) {
			//too many agents defined
				System.out.println("Cannot add agent... exceeds agent limit");
				if (m_screen2.containsPoint(x_start, y_start))
					insertLabel(s, m_screen2, new Point(x_start, y_start), 44);
				m_screen2.remove(c);
				m_frame.repaint();

			} else {
				center(c);
				if (m_screen2.containsPoint(x_start, y_start)) {
					m_screen2.getData()[y_start/100][x_start/100] = 0;
					agents++;
				}

				if (s.equals("A")) {
					m_screen2.getData()[c.getY()/100][c.getX()/100] = 1;
					agents--;
				}
				else if (s.equals("O"))
					m_screen2.getData()[c.getY()/100][c.getX()/100] = -1;
			}
		} 

		/** This method sets the temporary new location of a moving Agent or Observation marker
		 * so that it may be displayed in its new location
		 * @param e a MouseEvent invoked by the JVM when the mouse is dragged 
		 */
		public void mouseDragged(MouseEvent e) 
		{
			JComponent c = (JComponent)e.getSource();
			c.setBounds(c.getX()+e.getX()-x_off, c.getY()+e.getY()-y_off, 
			  c.getPreferredSize().width, c.getPreferredSize().height);
		} 

		/* @param e a MouseEvent invoked by the JVM when the mouse moves */
		public void mouseMoved(MouseEvent e) 
		{ }

		/** This method centers the Agent or Observation marker in the center of its grid cell
		 * @param c the component representing the Agent or Observation marker
		 */
		private void center(JComponent c)
		{
			int x = c.getX() - c.getX()%100 + 50 - c.getPreferredSize().width/2;
			int y = c.getY() - c.getY()%100 + 50 - c.getPreferredSize().height/2;
			c.setBounds(x, y, c.getPreferredSize().width, c.getPreferredSize().height);
		}

		/** The constructor creates a viewable frame and calls for the creation 
		 * of all relating screens
		 */
		private Canvas()
		{
			try {
				JFrame.setDefaultLookAndFeelDecorated(true);
				m_frame = new JFrame("Grid Puzzle Generator");
				m_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				m_frame.setVisible(true);
			}
			catch (HeadlessException he) { }

			agents = m_agents;
			createScreen1();
		}

		/** This method creates the first screen for the MdpPuzzleGenerator 
		 * This screen allows the user to specify the discount, values, grid rows, grid columns,
		 * and file.
		 */
		private void createScreen1()
		{
			m_screen1 = new JPanel();
			m_screen1.setLayout(null);

			int x_offset = 25, y_offset = 25;
			int x_tot=0, y_tot=0;
			String[] titles = { " ", "File: ", "Rows: ", "Cols: ", "Discount: ", "Values: " };
			String[] val = { " -- ", "cost", "reward" };

			for (int i=1; i<titles.length; i++) {
				int temp=0;

				JLabel jl = new JLabel(titles[i]);
				insert(m_screen1, jl, x_offset, y_offset, jl.getPreferredSize().width, jl.getPreferredSize().height);
				x_offset += jl.getPreferredSize().width+25;
				temp = jl.getPreferredSize().height;

				if (titles[i].equals("Values: ")) {
					JComboBox jcb = new JComboBox(val);
					jl.setLabelFor(jcb);
					insert(m_screen1, jcb, x_offset, y_offset, jcb.getPreferredSize().width, jcb.getPreferredSize().height);
					x_offset += jcb.getPreferredSize().width+25;
					temp = Math.max(temp, jcb.getPreferredSize().height);
				} else {
					JTextField jtf = new JTextField(7);
					jl.setLabelFor(jtf);
					insert(m_screen1, jtf, x_offset, y_offset, jtf.getPreferredSize().width, jtf.getPreferredSize().height);
					x_offset += jtf.getPreferredSize().width+25;
					temp = Math.max(temp, jtf.getPreferredSize().height);
				}

				if (i%2 == 1) {
					x_tot = Math.max(x_tot, x_offset);
					x_offset = 25;
					y_offset = y_offset+temp+25;
				}
			}

			y_tot = y_offset;
			JButton b = new JButton("Generate Grid");
			b.addActionListener(this);
			insert(m_screen1, b, (x_tot - b.getPreferredSize().width)/2, y_tot, 
				b.getPreferredSize().width, b.getPreferredSize().height	);

			m_screen1.setPreferredSize(new Dimension(x_tot, y_tot+25+b.getPreferredSize().height));
			m_frame.getContentPane().add(m_screen1);
			m_frame.pack();
		}

		/** This is a helper method designed to insert JComponents into a JPanel
		 * @param jc the pre-defined JComponent that will be added to the JPanel
		 * @param jp the corresponding JPanel to which the JComponent will be added
		 * @param x the starting X-coordinate of the JComponent to be added
		 * @param y the starting Y-coordinate of the JComponent to be added
		 * @param w the width of the JComponent to be added
		 * @param h the height of the JComponent to be added
		 */
		private void insert(JPanel jp, JComponent jc, int x, int y, int w, int h)
		{
			jc.setBounds(x, y, w, h);
			jp.add(jc);
		}

		/** This method creates the Agent and Observation markers
		 * @param s the marker's value ("A" or "O")
		 * @param jp the JPanel into which the marker is to be inserted
		 * @param start the marker's starting location
		 * @param size the marker's font size
		 * @return the next available location for another marker or object
		 */
		private Point insertLabel(String s, JPanel jp, Point start, int size)
		{
			JLabel label = new JLabel(s);
			label.setFont(new Font(label.getFont().getFontName(), label.getFont().getStyle(), size));//label.getFont().getSize()*2));
			label.addMouseListener(this);
			label.addMouseMotionListener(this);
			label.setBounds((int)(start.getX()), (int)(start.getY()), label.getPreferredSize().width, label.getPreferredSize().height);	
			jp.add(label);
			return (new Point(	((int)(start.getX()) + label.getPreferredSize().width + 25), 
						((int)(start.getY()) + label.getPreferredSize().height + 25)));
		}

		/** This method creates the second screen for the MdpPuzzleGenerator 
		 * This screen allows the user to specify the starting locations of the agents,
		 * the obstacles in the grid, and the possible observations the agents can make
		 */
		private void createScreen2()
		{
			Point p;
			m_screen2 = new Grid(100, 0, 0, getRows(), getCols(), false);
			m_screen2.setLayout(null);

			p = new Point((getCols()+1)*100, 25);
			a_cord = new Point(p);
			p = insertLabel("A", m_screen2, p, 44);

			p = new Point((getCols()+1)*100, (int)(p.getY()));
			o_cord = new Point(p);
			p = insertLabel("O", m_screen2, p, 44);

			obs_grid = new Grid(25, getCols()*100+50, (int)(p.getY()), 4, 4, true);
			obs_grid.setPreferredSize(new Dimension(100, 100));
			obs_grid.setBounds(getCols()*100+50, (int)(p.getY()),
				obs_grid.getPreferredSize().width, obs_grid.getPreferredSize().height);
			obs_grid.addMouseListener(this);

			JButton b = new JButton("Process Grid");
			b.addActionListener(this);
			insert(m_screen2, b, ((int)(p.getX())+25 - b.getPreferredSize().width)/2, (getRows()+1)*100, 
				b.getPreferredSize().width, b.getPreferredSize().height	);
			
			m_screen2.setPreferredSize(new Dimension((int)(p.getX()) + 25, (getRows()+1)*100+b.getPreferredSize().height+25));
			m_screen2.setBounds(0, 0, m_screen2.getPreferredSize().width, m_screen2.getPreferredSize().height);
		}

		/** This method creates the last (third) screen for the MdpPuzzleGenerator 
		 * This screen allows the user to specify the likelihood of moves given any action
		 */
		private void createScreen3()
		{
			m_screen3 = new JPanel();
			m_screen3.setLayout(null);

			int x_offset = 25, y_offset = 25;
			int x_tot=0, y_tot=0;
			String[] titles = { "Correct: ", "Perp. Left: ", "Perp. Right: ", "Opposite: ", "Stay: " };

			JLabel l = new JLabel("Distribution of Actions Based on Action Specified ( Probabilities must sum to 1.0 )");
			insert(m_screen3, l, x_offset, y_offset, l.getPreferredSize().width, l.getPreferredSize().height);
			x_offset = 25;
			y_offset += l.getPreferredSize().height+25;

			int temp_y = 0;
			for (int i=0; i<titles.length; i++) {

				JLabel jl = new JLabel(titles[i]);
				insert(m_screen3, jl, x_offset, y_offset, jl.getPreferredSize().width, jl.getPreferredSize().height);
				x_offset += jl.getPreferredSize().width+10;
				temp_y = Math.max(temp_y, y_offset + jl.getPreferredSize().height + 25);

				JTextField jtf = new JTextField(5);
				jl.setLabelFor(jtf);
				insert(m_screen3, jtf, x_offset, y_offset, jtf.getPreferredSize().width, jtf.getPreferredSize().height);
				x_offset += jtf.getPreferredSize().width+25;
				temp_y = Math.max(temp_y, y_offset + jtf.getPreferredSize().height + 25);
			}

			y_offset = temp_y;
			JButton b = new JButton("Finish and Exit");
			b.addActionListener(this);
			insert(m_screen3, b, (x_offset - b.getPreferredSize().width)/2, y_offset, 
				b.getPreferredSize().width, b.getPreferredSize().height	);
			y_offset += b.getPreferredSize().height+25;
			
			m_screen3.setPreferredSize(new Dimension(x_offset, y_offset));
		}


		/** This method returns any one of the program's GUI screens
		 * @param i the number of screen to be returned
		 * @return the requested GUI screen in the form of a JPanel
		 */
		private JPanel getScreen(int i)
		{
			switch (i) {
				case 1: return m_screen1;
				case 2: return m_screen2;
				case 3: return obs_grid;
				case 4: return m_screen3;
			}
			return null;
		}


		/** This method initializes a new thread whose purpose is to display the Canvas */	
		public void run()
		{
			m_frame.repaint();
		}

	}
}


/** The Grid class is a graphical representation of a grid with a specified number of rows and columns */
class Grid extends JPanel
{
	private Point start, end, center;
	int s, x, y, r, c;
	int[][] data;

	/** The constructor creates a new Grid object
	 * @param sqr_len the length/width of each grid cell
	 * @param x_start the Grid's starting x location on its corresponding JPanel
	 * @param y_start the Grid's starting y location on its corresponding JPanel
	 * @param r_lim the number of rows in the Grid
	 * @param c_lim the number of columns in the Grid
	 * @param cent true if the grid should have a visible center marker, false otherwise
	 */
	protected Grid(int sqr_len, int x_start, int y_start, int r_lim, int c_lim, boolean cent) {
		super();
		s = sqr_len;
		x = x_start;
		y = y_start;
		r = r_lim;
		c = c_lim;
		data = new int[r+1][c+1];
		makeGrid();
		setupBoundary(x, y, x+c*s, y+r*s, cent);
	}

	/** This method sets the Grid's boundaries and center point, if one should exist
	 * @param x1 the Grid's starting x location
	 * @param y1 the Grid's starting y location
	 * @param x2 the Grid's ending x location
	 * @param y2 the Grid's ending y location
	 * @param cent true if the Grid requires a center marker, false otherwise
	 */
	private void setupBoundary(int x1, int y1, int x2, int y2, boolean cent) {
		start = new Point(x1, y1);
		end = new Point(x2, y2);
		if (cent)
			center = new Point(data.length/2, data[0].length/2);
		else
			center = new Point(-1, -1);
	}

	/** This method determines if a given JComponent is contained within the boundaries of the Grid
	 * @param jc the given JComponent
	 * @return true if the JComponent is contained within the Grid, false otherwise
	 */
	protected boolean contains(JComponent jc) {
		return ((jc.getX() >= (int)start.getX()) && ((jc.getX()+jc.getPreferredSize().width) <= (int)end.getX())
			&& (jc.getY() >= (int)start.getY()) && ((jc.getY()+jc.getPreferredSize().height) <= (int)end.getY()));
	}

	/** This method determines if a given point is contained within the boundaries of the Grid
	 * @param x_pos the given point's x value
	 * @param y_pos the given point's y value
	 * @return true if the point is contained within the Grid, false otherwise
	 */
	protected boolean containsPoint(int x_pos, int y_pos) {
		return ((x_pos >= (int)start.getX()) && (x_pos <= (int)end.getX())
			&& (y_pos >= (int)start.getY()) && (y_pos <= (int)end.getY()));
	}

	/** This method marks the cells of the observation grid if the given observation is deemed feasible
	 * @param x_pos the specified x position in the Grid
	 * @param y_pos the specified y position in the Grid
	 */
	protected void fillObs(int x_pos, int y_pos) {
		if (data[(y_pos-y)/s][(x_pos-x)/s] == 0)
			data[(y_pos-y)/s][(x_pos-x)/s] = 2;
		else
			data[(y_pos-y)/s][(x_pos-x)/s] = 0;
	}

	/** This method graws the grid in its corresponding JPanel
	 * @param g the JPanel's Graphics member variable
	 */
	private void drawGrid(Graphics g) {
		for (int rt=1; rt<=r; rt++)
		{
			g.drawLine(x+s, y+rt*s, x+c*s, y+rt*s);
		}

		for (int ct=1; ct<=c; ct++)
		{
			g.drawLine(x+ct*s, y+s, x+ct*s, y+r*s);
		}

		for (int rt=1; rt<r; rt++)
			for (int ct=1; ct<c; ct++) {
				if (data[rt][ct] == 2) {
					g.setColor(Color.CYAN);
					g.fillRect(ct*s+x+1,rt*s+y+1,s-1,s-1);
				}
				if (((int)center.getX() == rt) && ((int)center.getY() == ct)) {
					g.setColor(Color.BLACK);
					g.drawLine(rt*s+x,(ct+1)*s+y,(int)(((double)rt+0.5)*s+x),ct*s+y);
					g.drawLine((rt+1)*s+x,(ct+1)*s+y,(int)(((double)rt+0.5)*s+x),ct*s+y);
				}
			}
	}

	/** This method creates a textual representation of the graphical grid */
	private void makeGrid()
	{
		for (int i=0; i<data.length; i++)
			for (int j=0; j<data[i].length; j++)
				data[i][j] = -1;

		for (int i=1; i<data.length-1; i++)
			for (int j=1; j<data[i].length-1; j++)
				data[i][j] = 0;
	}

	/** This method prints the textual representation of the graphical grid */
	public void printGrid()
	{
		for (int i=0; i<data.length; i++) 
		{
			for (int j=0; j<data[i].length; j++)
				System.out.print(data[i][j] + " ");
			System.out.println();
		}
		System.out.println();
	}

	/** @return the textual representation of the graphical grid */
	public int[][] getData()
	{
		return data;
	}

	/** This method outputs the Grid in graphical form
	 * @param g the Grid's JPanel's Graphics member variable
	 */
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		drawGrid(g);

	}
}
