/*
 * Class PosgInputFileParser
 *
 * File structure and code adopted from 
 * ParserNew.java class by Christopher Amato
 */


import java.io.*;
import java.util.StringTokenizer;
import java.util.NoSuchElementException;


/**
 * PosgInputFileParser parses an input file and places data into appropriate data structures later to be used
 * by POSG-solving algorithms. <br>
 * @author Mark Gruman -- UMass RBR Lab <br>
 * Computer Science Department <br>
 * University of Massachusetts Amherst <br>
 * @version 1.1 <br>
 * Created on July 02, 2004 <br><br>
 */
public class PosgInputFileParser 
{
	//required pre-amble variables
	private double discount;
	private int agents;
	private int states;
	private boolean cost;

	//Observational, Transitional, and Reward Probabilities
	private double[][][] O;
	private double[][][] T;
	private double[][][][] R;

	//variables to help setup O,T,R tables
	private double[] start;
	private BufferedReader br;
	private boolean t_set;
	private boolean r_o_set;
	private int tot_actions;
	private int tot_obs;

	//names of parameters
	public String[] state;
	public String[][] action;
	public String[][] observation;

/****************************
*****	Public Methods	*****
****************************/

	/** @return the number of agents */
	public int getAgents() {
		return agents;
	}

	/** @return the number of states */
	public int getStates() {
		return states;
	}

	/** @return true if cost, false if rewards */
	public boolean getCost() {
		return cost;
	}

	/** @return the set discount */
	public double getDiscount() {
		return discount;
	}

	/** @return the number of unique action combinations */
	public int getActions() {
		return tot_actions;
	}

	/** @return the number of unique observation combinations */
	public int getObs() {
		return tot_obs;
	}

	/** @return the Transitional Probabilities Matrix */
	public double[][][] getTrnProbTable() {
		return T;
	}

	/** @return the Observational Probabilities Matrix */
	public double[][][] getObsProbTable() {
		return O;
	}

	/** @return the Reward Probabilities Matrix */
	public double[][][][] getRwdProbTable() {
		return R;
	}

	/**
	 * @param s the state name
	 * @return the corresponding state value
	 */
	public int getStateValue(String s)
	{
		if (s.equals("*"))
			return -1;
		try {
			return (Integer.parseInt(s));
		}
		catch (Exception e) {
			for (int i=0; i<getStates(); i++)
				if (s.equals(state[i]))
					return i;
			System.out.println("Inappropriate state format... please see the FORMAT.txt file for instructions");
			System.exit(1);
		}
		return -2;
	}

	/**
	 * @param v the state value
	 * @return the corresponding state name
	 */
	public String getStateName(int v)
	{
		try {
			return (state[v]);
		}
		catch (Exception e) {
			return (new Integer(v)).toString();
		}
	}

	/** This method returns a value corresponding to a unique set of
	 * actions or observations
	 * @param params a set of actions or observations
	 * @param values the matrix of all agents' actions or observations
	 * @return a value uniquely corresponding to the given set of actions
	 */
	public int getParamValue(String params, String[][] values)
	{
		String[] p = params.split("\\s");
		if (values == action)
			return getParamValueHlp(p, values, tot_actions);
		else // if (values == observation)
			return getParamValueHlp(p, values, tot_obs);
	}

	/**
	 * @param num a number representing a unique set of actions or observations
	 * @param actn true if returning name of an action, false if returning name of an observation
	 * @return a unique string of actions or observations
	 */
	public String getParamName(int num, boolean actn)
	{  //calls helper method
		if (actn)
			return getParamNameHlp(num, action, 0, "");
		else //observation
			return getParamNameHlp(num, observation, 0, "");
	}

	/**
	 * @return probabilistic distribution of starting states
	 */
	public double[] getStartValues()
	{
		return start;
	}

/***********************************
*****	Initialization Methods *****
***********************************/

	/** Constructor initializes variables and creates a BufferedReader for the specified file.
	 * @param filename The input file to be parsed
	 * @throws FileNotFoundException
	 */
	public PosgInputFileParser(String filename) throws FileNotFoundException 
	{
		agents = 0; 
		states = 0;
		tot_actions = 0;
		tot_obs = 0;
		
		start = null;
		state = null;
		action = null;
		observation = null;

		O = null;
		T = null;
		R = null;

		t_set = false;
		r_o_set = false;

		br = new BufferedReader(new FileReader(filename));
	}

	/** 
	 * This method initializes the probability matricies 
	 * @param actns true if initializing actions, false if observations
	 */
	private void initProbs(boolean actns)
	{
		if (actns) 
		{
			tot_actions = 1;
			for (int i=0; i<action.length; i++) 
				tot_actions *= action[i].length;

			if (!t_set) 
			{
				T = new double[tot_actions][getStates()][getStates()];
				t_set = true;
			}

		} 

		else // if (!actns) 
		{
			tot_obs = 1;
			for (int i=0; i<observation.length; i++)
				tot_obs *= observation[i].length;
		
			O = new double[tot_actions][getStates()][tot_obs];
			R = new double[tot_actions][getStates()][getStates()][tot_obs];
			r_o_set = true;
		}
	}

/****************************
*****	Set Methods	*****
****************************/

	/**
	 * This method sets the number of agents and all other corresponding variables.
	 * @param line the string containing the number of agents
	 */
	private void setAgent(String line)
	{
		StringTokenizer st = new StringTokenizer(line, " :");
		//advance past agents...
		st.nextToken();
		String agnt = st.nextToken();
		try
		{
			agents = Integer.parseInt(agnt);
			action = new String[agents][];
			observation = new String[agents][];
		}
		catch (NumberFormatException nfe) 
		{
			System.out.println("discount not formated correctly: " + nfe.getMessage());
			System.exit(1);
		}
	}

	/** This method parses a set of actions or observations
	 * @param val the matrix of all agents' actions or observations
	 * @param line the current line in the text file to be parsed
	 * @return the next line in the text file to be parsed
	 * @throws IOException
	 */
	private String setParam(String[][] val, String line) throws IOException
	{
		int action_line = 0;
		int actions;
		StringTokenizer actionTokenize;
		String current = "";

		do {
			if (action_line == 0) {
				actionTokenize = new StringTokenizer(line, " :");
				//skip "actions:"
				actionTokenize.nextToken();
			}
			else
				actionTokenize = new StringTokenizer(line);	
			
		
			//number of actions/obs
			try { 
				current = actionTokenize.nextToken();
				val[action_line] = new String[Integer.parseInt(current)];
				for (int i=0; i<val[action_line].length; i++) 
					val[action_line][i] = (new Integer(i)).toString();
			}

			//line contained only the word "actions"
			catch (NoSuchElementException nsee) { 
				line = br.readLine();
				StringTokenizer str = new StringTokenizer(line);
				String first = str.nextToken();
				String temp = "actions: ";

				do {
					if (line.startsWith("#"))
						continue;
					str = new StringTokenizer(line);
					for (int i=0; i<str.countTokens(); i++)
						temp += str.nextToken() + " ";
				} while (!(line = br.readLine()).equals(""));

				str = new StringTokenizer(temp, " :");
				catchParamNFE(str, val, action_line, first);
			}

			//actions have names
			catch (NumberFormatException nfe) { 
				catchParamNFE(actionTokenize, val, action_line, current);
			}

			while((line = br.readLine()) != null && line.equals("")) { }
			action_line++;
//	System.out.println(line + " " + action_line + " " + getAgents());
		}
		while (action_line < getAgents());
		return line;
	}

	/** This method set some or all values of a given probability matrix
	 * It deals mostly with parameters with a "*" value, as defined in the specifications<br>
	 * @param m the probability matrix to be updated
	 * @param a the value corresponding to a unique set of actions
	 * @param p for T and R matricies: the start state, for the O matrix: the end state
	 * @param p2 for the R matrix: the end state (the parameter is passed a 0 value otherwise)
	 * @param pr the corresponding probability
	 * @param o_flag true if updating the O or R matrix, false otherwise
	 * @param r_flag true if updating the R matrix, false otherwise
	 */
	private void setValues(Object m, int a, int p, int p2, int o, double pr, boolean o_flag, boolean r_flag)
	{
	  if (a == -1) 
	    for (int i=0; i<tot_actions; i++) {
	      if (p == -1)
	        for (int j=0; j<getStates(); j++) {
		  if (o == -1) {
		    if (p2 == -1)
		      for (int k=0; k<getStates(); k++)
			for (int l=0; l<tot_obs; l++)
			  ((double[][][][])m)[i][j][k][l] = pr; //a=*, p=*, p2=*, o=* (R)
		    else if (o_flag)
		      for (int k=0; k<tot_obs; k++) {
			if (!r_flag)
			  ((double[][][])m)[i][j][k] = pr; //a=*, p=*, o=* (O)
			else
			  ((double[][][][])m)[i][j][p2][k] = pr; //a=*, p=*, o=* (R)
		      }
		    else
		      for (int k=0; k<getStates(); k++)
			((double[][][])m)[i][j][k] = pr; //a=*, p=*, o=* (T)
		  }
		  else if (p2 == -1)
		    for (int k=0; k<getStates(); k++)
		      ((double[][][][])m)[i][j][k][o] = pr; //a=*, p=*, p2=* (R)
		  else if (!r_flag)
		    ((double[][][])m)[i][j][o] = pr; //a=*, p=* (O/T)

		  else
		    ((double[][][][])m)[i][j][p2][o] = pr; //a=*, p=* (R)
		}
	      else if (o == -1) {
		if (p2 == -1)
		  for (int k=0; k<getStates(); k++)
		    for (int l=0; l<tot_obs; l++)
		      ((double[][][][])m)[i][p][k][l] = pr; //a=*, p2=*, o=* (R)
		else if (o_flag)
		  for (int k=0; k<tot_obs; k++) {
		    if (!r_flag)
		      ((double[][][])m)[i][p][k] = pr; //a=*, o=* (O)
		    else
		      ((double[][][][])m)[i][p][p2][k] = pr; //a=*, o=* (R)
		  }
		else
		  for (int k=0; k<getStates(); k++)
		    ((double[][][])m)[i][p][k] = pr; //a=*, o=* (T)
	      }
	      else if (p2 == -1)
		for (int k=0; k<getStates(); k++)
		  ((double[][][][])m)[i][p][k][o] = pr; //a=*, p2=* (R)		  
	      else if (!r_flag)
		((double[][][])m)[i][p][o] = pr; //a=* (O/T)
	      else
		((double[][][][])m)[i][p][p2][o] = pr; //a=* (R)
	    }
	  else if (p == -1)
	    for (int j=0; j<getStates(); j++) {
	      if (o == -1) {
		if (p2 == -1)
		  for (int k=0; k<getStates(); k++)
		    for (int l=0; l<tot_obs; l++)
		      ((double[][][][])m)[a][j][k][l] = pr; //p=*, p2=*, o=* (R)
		else if (o_flag)
		  for (int k=0; k<tot_obs; k++) {
		    if (!r_flag)
		      ((double[][][])m)[a][j][k] = pr; //p=*, o=* (O)
		    else
		      ((double[][][][])m)[a][j][p2][k] = pr; //p=*, o=* (R)
		  }
		else
		  for (int k=0; k<getStates(); k++)
		    ((double[][][])m)[a][j][k] = pr; //p=*, o=* (T)
	      }
	      else if (p2 == -1)
		for (int k=0; k<getStates(); k++)
		  ((double[][][][])m)[a][j][k][o] = pr; //p=*, p2=* (R)
	      else if (!r_flag)
		((double[][][])m)[a][j][o] = pr; //p=* (O/T)
	      else
		((double[][][][])m)[a][j][p2][o] = pr; //p=* (R)
	    }
	  else if (o == -1) {
	    if (p2 == -1)
	      for (int k=0; k<getStates(); k++)
		for (int l=0; l<tot_obs; l++)
		  ((double[][][][])m)[a][p][k][l] = pr; //p2=*, o=* (R)
	    else if (o_flag)
	      for (int k=0; k<tot_obs; k++) {
		if (!r_flag)
		  ((double[][][])m)[a][p][k] = pr; //o=* (O)
		else
		  ((double[][][][])m)[a][p][p2][k] = pr; //o=* (R)
	      }
	    else
	      for (int k=0; k<getStates(); k++)
		((double[][][])m)[a][p][k] = pr; //o=* (T)
	  }
	  else if (p2 == -1) 
	    for (int k=0; k<getStates(); k++)
	      ((double[][][][])m)[a][p][k][o] = pr; //p2=* (R)	      
	  else if (!r_flag)
	    ((double[][][])m)[a][p][o] = pr; //no '*' (O/T)
	  else
	    ((double[][][][])m)[a][p][p2][o] = pr; //no '*' (R)
	}

	/** This parses the given text file's probabilities data and updates 
	 * the corresponding probability matrix
	 * @param prob the probability matrix to be updated
	 * @param line the current line in the text file to be parsed
	 * @param obsr true if updating the O or R matrix, false otherwise
	 * @param rwrd true if updating the R matrix, false otherwise
	 * @return the next line in the text file to be parsed
	 * @throws IOException
	 */
	private String setProb(Object prob, String line, boolean obsr, boolean rwrd) throws IOException
	{
		StringTokenizer st;
		String[] acs = null;
		String[] obs = null;
		int params = getAgents();
		int a_value = 0;
		int o_value = 0;
		int cur_line = 0;
		boolean one_line = false;
		int start_s = 0;
		int end_s = 0;
		int r_end_s = 0;
		int tokens = 0;

		do {
			if (cur_line == 0) {
				StringTokenizer star_elim = new StringTokenizer(line, " :");
				tokens = getTokenCount(star_elim, obsr, rwrd);

				st = new StringTokenizer(line, " :");
				st.nextToken(); //bypass the "T:" token

				String temp = st.nextToken();
					//to account for the * instead of multiple actions
				if (temp.equals("*")) {
					a_value = -1;
				}
				else {
					acs = new String[params];
					acs[0] = temp;
					for (int a=1; a<acs.length; a++)
						acs[a] = st.nextToken(); 
					a_value = getParamValueHlp(acs, action, tot_actions);
				}
			}

			else {
				st = new StringTokenizer(line);	
				tokens = st.countTokens();
				params = getStates();
			}

		/* line =
		 *      " T : <a1 a2 ... an> : <start-state> : <end-state> : %f" 
		 *  or  " T : <a1 a2 ... an> : <start-state> : <end-state> /r/n %f"
		 *  or  " O : <a1 a2 ... an> : <end-state> : <o1 o2 ... on> : %f"
		 *  or  " O : <a1 a2 ... an> : <end-state> : <o1 o2 ... on> /r/n %f"
		 *  or  " R : <a1 a2 ... an> : <start-state> : <end-state> : <o1 o2 ... on> : %f"
		 *  or  " R : <a1 a2 ... an> : <start-state> : <end-state> : <o1 o2 ... on> /r/n %f"		 
		 */
			if ( (tokens >= (params*2 + 3) && tokens <= (params*2 + 4) && rwrd) || 
			     (tokens >= (params*2 + 2) && tokens <= (params*2 + 3) && obsr && !rwrd) || 
			     (tokens >= (params+3) && tokens <= (params+4) && !obsr && !rwrd)) {
				start_s = getStateValue(st.nextToken());
				if (rwrd)
					r_end_s = getStateValue(st.nextToken());

				if ( (tokens >= (params*2 + 3) && tokens <= (params*2 + 4) && rwrd) || 
				     (tokens >= (params*2 + 2) && tokens <= (params*2 + 3) && obsr && !rwrd)) {
					String temp2 = st.nextToken();
					if (temp2.equals("*"))
						end_s = -1;
					else {
						obs = new String[params];
						obs[0] = temp2;
						for (int o=1; o<obs.length; o++)
							obs[o] = st.nextToken();
						end_s = getParamValueHlp(obs, observation, tot_obs);
					}
				}
				else
					end_s = getStateValue(st.nextToken());
				
				double pr;

				if ((tokens == (params+4) && !rwrd) || tokens == (params*2+4) || (tokens == (params*2+3) && !rwrd))
					pr = Double.parseDouble(st.nextToken());
				else {
					line = br.readLine();
					pr = Double.parseDouble(new StringTokenizer(line).nextToken());
				}

				if (rwrd)
					setValues(prob,a_value,start_s,r_end_s,end_s,pr, true, true);
				else if (obsr)
					setValues(prob,a_value,start_s,0,end_s,pr,true,false);
				else
					setValues(prob,a_value,start_s,0,end_s,pr,false,false);
				return br.readLine();
			}

		/* line =
		 *     " T : <a1 a2 ... an> : <start-state>" 
		 * or  " O : <a1 a2 ... an> : <end-state>"
		 * or  " R : <a1 a2 ... an> : <start-state> : <end-state>"
		 */
			else if (tokens == (params+3) || (tokens == (params+2) && !rwrd)) {
				start_s = getStateValue(st.nextToken());
				if (rwrd)
					r_end_s = getStateValue(st.nextToken());
				one_line = true;
				cur_line = getStates()-1;
			}

		/* line =
		 *    " T : <a1 a2 ... an>"
		 * or " O : <a1 a2 ... an>"
		 * or " R : <a1 a2 ... an> : <start_state>"
		 */
			else if (tokens == (params+2) || (tokens == (params+1) && !rwrd)) {
				if (rwrd)
					start_s = getStateValue(st.nextToken());
			}

		/* line =
		 * " %f %f ... %f " | identity | uniform
		 */
			else if (tokens == getStates() || tokens == tot_obs || tokens == 1) { 
				StringTokenizer tk = new StringTokenizer(line);
				String temp = tk.nextToken();

				if (temp.equalsIgnoreCase("IDENTITY") || temp.equalsIgnoreCase("UNIFORM")) {
					cur_line = getStates();
					if (!one_line)
						start_s = -1;
				}

				if (!one_line && start_s != -1)
					start_s = cur_line-1;

			/* line =
			 * identity
			 */
				if (temp.equalsIgnoreCase("IDENTITY") && !obsr && !rwrd)
				  for (int s=0; s<getStates(); s++)
				    for (int e=0; e<getStates(); e++) {
				      if (s == e)
				        setValues(prob,a_value,s,0,e,1.0,false,false);
				      else
				        setValues(prob,a_value,s,0,e,0.0,false,false);
				    }

			/* line =
			 * uniform
			 */
				else if (temp.equalsIgnoreCase("UNIFORM")) {
					if (!one_line)
						for (int s=0; s<getStates(); s++)
							uniformHlp(prob,a_value,start_s,s,1.0/getStates(),obsr,rwrd);
					else
						uniformHlp(prob,a_value,start_s,r_end_s,1.0/getStates(),obsr,rwrd);
				}


				else if (tokens == getStates())
					for (int e=0; e<getStates(); e++)	// set T values
						setValues(prob,a_value,start_s,0,e,Double.parseDouble(st.nextToken()),false,false);
				else if (!rwrd)
					for (int e=0; e<getObs(); e++)		// set O values
						setValues(prob,a_value,start_s,0,e,Double.parseDouble(st.nextToken()),true,false);
				else
					for (int e=0; e<getObs(); e++)		// set R values
						setValues(prob,a_value,start_s,r_end_s,e,Double.parseDouble(st.nextToken()),true,true);
			}


			else {
				System.out.println("Incorrect Formatting in line " + line);
				System.out.println("Please see the INPUT FILE SPECIFICATIONS.txt file for instructions");
				System.exit(1);
			}

			line = br.readLine();
			cur_line++;
		}
		while (cur_line <= getStates());
		return line;
	}

	/** This method is equivalent to setProb and should be used to update a row of any matrix
	 * after a text file has already been parsed
	 * @param prob the probability matrix to be updated
	 * @param line the current line in the text file to be parsed
	 */
	public void updateProbRow(Object prob, String line) throws IOException
	{
		if (prob == getTrnProbTable())
			setProb(prob, line, false, false);
		else if (prob == getObsProbTable())
			setProb(prob, line, true, false);
		else
			setProb(prob, line, true, true);
	}

/****************************
*****	Helper Methods	*****
****************************/

	/** This method is a tail-recursive helper to the getParamName method
	 * @param value a number representing a unique set of actions or observations
	 * @param param the matrix of all agents' actions or observations
	 * @param param_col the next column to be checked
	 * @param res the resulting string of unique actions
	 * @return a unique string of actions or observations
	 */
	private String getParamNameHlp(int value, String[][] param, int param_col, String res)
	{
		int i;
		int num = 1;
		int temp;

		if (value == 0) {
			for (int f=param_col; f<param.length; f++)
				res += param[f][0] + " "; 
			return res;
		}

		for (i=param.length-1; i>=0; num=temp, i--) {
			temp = num * param[i].length;
			if (value < temp)
				break;
		}

		if (i > param_col)
			for (int j=param_col; j<i; j++)
				res += param[j][0] + " ";

		int row;
		for (row=0; row<param[i].length; row++)
			if (value < row*num)
				break;
			
		return getParamNameHlp(value - num*(row-1), param, i+1, res + param[i][row-1] + " ");		
	}

	/** This method uses a hashing formula to assign a particular set of
	 * action or observation a unique value
	 * @param params a set of actions or observations
	 * @param values the matrix of all agents' actions or observations
	 * @param data the total number of action combinations for all agents
	 * @return a value uniquely corresponding to the given set of actions
	 */
	private int getParamValueHlp(String[] params, String[][] values, int data)
	{
		int tot = data;
		int value = 0;
		for (int i=0; i<params.length; i++)
			for (int j=0; j<values[i].length; j++)
				if (params[i].equals(values[i][j]))
				{
					value += j * (tot/values[i].length);
					tot /= values[i].length;
				}
		return value;
	}

	/** This method is called when the actions or observations are specified on a separate line(s)
	 * @param st a StringTokenizer that has tokenized the current text line to be processed
	 * @param val the matrix of all agents' actions or observations
	 * @param action_line the agent corresponding to the current action or observation
	 * @param current the first action or observation or number of actions/observations
	 */
	private void catchParamNFE(StringTokenizer st, String[][] val, int action_line, String current)
	{
		int actions = st.countTokens();

		val[action_line] = new String[actions+1];
		val[action_line][0] = current;

		for (int count = 1; st.hasMoreTokens(); count++)
			val[action_line][count] = st.nextToken();
	}

	/** This method is a helper to the setProb method and deals with the UNIFORM tag
	 * @param m the probability matrix to be updated
	 * @param a the value corresponding to a unique set of actions
	 * @param p for T and R matricies: the start state, for the O matrix: the end state
	 * @param p2 for the R matrix: the end state (the parameter is passed a 0 value otherwise)
	 * @param pr the corresponding probability
	 * @param o_flag true if updating the O or R matrix, false otherwise
	 * @param r_flag true if updating the R matrix, false otherwise
	 */
	private void uniformHlp(Object m, int a, int p, int p2, double pr, boolean o_flag, boolean r_flag)
	{
		if (!o_flag && !r_flag)
			for (int e=0; e<getStates(); e++)
				setValues(m,a,p,0,e,1.0/getStates(),o_flag,r_flag);
		else if (!r_flag)
			for (int e=0; e<tot_obs; e++) 
				setValues(m,a,p,0,e,1.0/getObs(),o_flag,r_flag);
		else
			for (int e=0; e<tot_obs; e++)
				setValues(m,a,p,p2,e,1.0/getObs(),o_flag,r_flag);
	}

	/** This method is called when the number of states is specified on a separate line(s)
	 * @param line the current line in the text file to be processed
	 */
	private void catchStatesNFE (String line)
	{
		StringTokenizer stateTokenize = new StringTokenizer(line);
		states = stateTokenize.countTokens() - 1;
		start = new double[states];
	
		state = new String[states];
		stateTokenize.nextToken();
		int count;
		for (count = 0; stateTokenize.hasMoreTokens(); count++)
			state[count] = stateTokenize.nextToken();
	}

	/** This method counts the number of tokens in a given line of probabilities and
	 * accounts for "*" values in actions or observations.
	 * @param s the StringTokenizer containing all the data tokens
	 * @param obs true if observations or rewards tokens, false otherwise
	 * @param rwd true if rewards tokens, false otherwise
	 * @return the number of tokens, with "*" values substituted with number of agents
	 */
	private int getTokenCount(StringTokenizer s, boolean obs, boolean rwd)
	{
		int r = s.countTokens();
		try {		
			s.nextToken(); //Bypass 'header' token (e.g. "T:")

			String temp = s.nextToken(); //get action token(s)
			if (temp.equals("*"))
				r += getAgents() - 1;

			s.nextToken(); //Bypass start-state token
			if (rwd) //Bypass end-state token
				s.nextToken();

			temp = s.nextToken(); //get observation token(s)
			if (obs && temp.equals("*"))
				r += getAgents() - 1;
			return r;
			
		}
		catch (NoSuchElementException nsee) {
			return r;
		}
	}


/********************************
*****	Main Parse Method   *****
********************************/

	/**
	 * This method parses an input data file
	 * @throws IOException
	 */
	public void parse() throws IOException {
		String line = null;
		//loop until the end of the file
		for (line = br.readLine(); line != null;) {
//			System.out.println(line);

			/**
			 * header:
			 *   agents, discount, values, states, actions, observations
			 * 
			 * lists the discount, whether this is max or min problem
			 * and states how many states, actions and observations there
			 * are.  These can also be named.
			 * 
			 */

			//comment
			if (line.startsWith("#")) {
				//System.out.println("comment");
				line = br.readLine();
			}

			//set the discount
			else if (line.toLowerCase().startsWith("discount:")) 
			{
				StringTokenizer st = new StringTokenizer(line, " :");
				st.nextToken();	//advance past "discount:"

				String discTemp = st.nextToken();
				try 
				{
					discount = Double.parseDouble(discTemp);
				} 
				
				catch (NumberFormatException nfe) 
				{
					System.out.println("discount not formated correctly: " + nfe.getMessage());
					System.exit(1);
				}
				line = br.readLine();
			}

			//sets the number of agents
			else if (line.toLowerCase().startsWith("agents:"))
			{
				setAgent(line);
				line = br.readLine();
			}

			//values -- set maximization or minimization
			else if (line.toLowerCase().startsWith("values:")) {
//				System.out.println("values...");
				StringTokenizer st = new StringTokenizer(line, " :");

				st.nextToken(); //advace past values...

				if (st.countTokens() != 1) {
					System.out.println("wrong number of params in values: def");
					System.exit(1);
				}
				
				String values = st.nextToken();
				if (values.equals("reward")) {
					cost = false;
				} else if (values.equals("cost")) {
					cost = true;
				} else {
					System.out.println(
						"values: must be specified as 'cost' or 'reward'");
					System.exit(1);
				}
				line = br.readLine();

			}

			//set the states
			else if (line.toLowerCase().startsWith("states:")) {
//				System.out.println("states...");
				StringTokenizer st = new StringTokenizer(line, " :");
					
				//skip "states:"
				st.nextToken(); 
					
				//set number of states
				try 
				{
					states = Integer.parseInt(st.nextToken());
					start = new double[states];
					line = "states:";
					for (int i=0; i<states; i++)
						line += " state" + (i+1);
					catchStatesNFE(line);
					line = br.readLine();
				}
					//line only contained "states:"
				catch (NoSuchElementException nsee)
				{
					String temp = "states: ";
					while (!(line = br.readLine()).equals(""))  {						
						StringTokenizer str = new StringTokenizer(line);
						for (int i=0; i<str.countTokens(); i++)
							temp += str.nextToken() + " ";
					}
					catchStatesNFE(temp);
				}
					//states have names
				catch (NumberFormatException nfe) 
				{
					catchStatesNFE(line);
					line = br.readLine();
				}
			}

			//actions
			else if (line.toLowerCase().startsWith("actions:")) 
			{ 
				if (getAgents() == 0)
					setAgent("agents: 1");
//				System.out.println("actions...");
				line = setParam(action, line);
				initProbs(true);
			}


			//observations
			else if (line.toLowerCase().startsWith("observations:")) 
			{
				if (getAgents() == 0)
					setAgent("agents: 1");
//				System.out.println("observations...");
				line = setParam(observation, line);
				initProbs(false);
			}

			//transition probabilities
			else if (line.toLowerCase().startsWith("t"))
				line = setProb(T, line, false, false);

			else if (line.toLowerCase().startsWith("o")) 
				line = setProb(O, line, true, false); 

			else if (line.toLowerCase().startsWith("r")) 
				line = setProb(R, line, true, true); 


			// start
			else if (line.toLowerCase().startsWith("start:")) 
			{
//				System.out.println("start...");
				StringTokenizer st = new StringTokenizer(line, " :");
				int tokens = st.countTokens() - 1;

				if (tokens == 0) { // "start: \r\n %f %f ... %f"
					line = br.readLine();
					st = new StringTokenizer(line, " :");
					tokens = st.countTokens();
				}

				if (tokens != start.length) {
					System.out.println("wrong number of start probabilities");
					System.out.println("given: " + tokens + ", expected: " + start.length);
					System.exit(1);
				}				

				if (st.countTokens() == start.length+1)  //advance past "start:"
					st.nextToken();

				String temp;
				
				try {
					for (int i=0; i<tokens; i++) {
						temp = st.nextToken();
						start[i] = Double.parseDouble(temp);
					}
				}
				catch (NumberFormatException nfe) {
					System.out.println("start probs not formatted correctly: " + nfe.getMessage());
					System.exit(1);
				}
				line = br.readLine();
			}				

			if (line != null && line.equals(""))
				while((line = br.readLine()) != null && line.equals("")) { }
		}
		System.out.println("done parsing...");
	}
}
