import java.io.*;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Set;

/**
 * The SamAssembler is responsible for reading in
 * a file and creating a Program object that can
 * then be sent to the Processor for execution
 */
public class SamAssembler {
	private final static int EOF = SamTokenizer.EOF, OPERATOR = SamTokenizer.OPERATOR, 
				 INTEGER = SamTokenizer.INTEGER, WORD = SamTokenizer.WORD;

	/**
	 * Assembles a new program from the file provided
	 * @param filename The filename of the file to load
	 * @throws FileNotFoundException if the file could not be found
	 * @throws AssemblerException if there is an error with the instructions
	 * @throws TokenizerException if there was an error parsing the input
	 * @return a Program object that can be executed
	 */
	public static Program assemble(String filename) throws AssemblerException, 
					FileNotFoundException, TokenizerException, IOException {
		return assemble(new SamTokenizer(filename));
	}

	/**
	 * Assembles a new program from the provided Reader input
	 * The returned Program can then be sent to a Processor
	 * for execution
	 * @param input The reader to use for input.
	 * @throws AssemblerException if there is an error with the instructions
	 * @throws TokenizerException if there was an error parsing the input
	 * @return a Program object that can be executed
	 */
	public static Program assemble(Reader input) throws AssemblerException, TokenizerException, IOException {
		return assemble(new SamTokenizer(input));
	}

	/**
	 * Assembles a new program from the tokenizer object provided.
	 * @param in The tokenizer to use for input.
	 * @throws AssemblerException if there is an error with the instructions
	 * @throws TokenizerException if there was an error parsing the input
	 * @return a Program object that can be executed.
	 */

	public static Program assemble(Tokenizer in) throws AssemblerException, TokenizerException {
		Program prog = new SamProgram();
		SymbolTable ST = prog.getTable();
		Hashtable labelReferences = new Hashtable();

		do {
			String str = null;

			/*====================LABELS==================================*/
			while (in.peekAtKind() != EOF && (str = in.getWord()).endsWith(":")) {

				/* Extract the label */
				String label = str.substring(0, str.length() - 1);

				/* Catch Duplicates */
				if (ST.resolveAddress(label) >= 0)
					throw new AssemblerException("Duplicate Label", in.lineNo());

				/* Add to the symbol table */
				ST.add(label, prog.getLength());
			}
	
			/*==================== INSTRUCTIONS: NAME =======================*/
			/* At this point, s should contain the instruction name */
			Instruction i;

			try  { i = (Instruction) (Class.forName(str)).newInstance(); }
			catch (Throwable e)  
				{ throw new AssemblerException("Unknown Instruction: " + str, in.lineNo()); }

			/*==================== INTEGER INSTRUCTIONS: OPERAND ============*/
			/*__________Operand could be either a label or an integer._______*/

			if (i instanceof SamIntInstruction) {
				if (in.peekAtKind() == WORD) {		
					String label = in.getWord();
					Vector references = (Vector) labelReferences.get(label);		
					if (references == null)	
						labelReferences.put(label, references = new Vector());

					references.add (new Integer(prog.getLength()));
				}

				else	
					 ((SamIntInstruction)i).setOperand(in.getInt());
			}

			/*===================== FLOAT INSTRUCTIONS: OPERAND =============*/
			else if (i instanceof SamFloatInstruction)
				 ((SamFloatInstruction)i).setOperand(in.getFloat());

			/*===================== CHAR INSTRUCTIONS: OPERAND  =============*/
			else if (i instanceof SamCharInstruction)
				 ((SamCharInstruction)i).setOperand(SamAssembler.readChar(in));

			/*===================== STRING INSTRUCTIONS: OPERAND ============*/
			else if (i instanceof SamStringInstruction)
				 ((SamStringInstruction)i).setOperand(in.getWord());

			/*===================== ADD INSTRUCTION TO PROGRAM ===============*/
			prog.addInst(i);

		} while (in.peekAtKind() != EOF);

		/* Close Input strem */
		in.close();

		/* Resolve all undefined labels */
		Object [] labels = labelReferences.keySet().toArray();
		for (int a=0; a < labels.length; a++) {
			int address = ST.resolveAddress((String) labels[a]);
			if (address < 0) 
				throw new AssemblerException ("Unresolved label: " + labels[a]);
			Vector references = (Vector) labelReferences.get(labels[a]);
			for (int i=0; i < references.size(); i++) {
				int location = ((Integer) references.get(i)).intValue();
				((SamIntInstruction) prog.getInst(location)).setOperand(address);
			}
		}

		/* If the generated program was null, throw an exception */
		if (prog.getLength() == 0)
			throw new AssemblerException("Cannot assemble null program.");

		/* Return the program object */
		return prog;
	}

	// Reads in a character
	private static char readChar(Tokenizer in) throws TokenizerException, AssemblerException{
		if(in.peekAtKind() != Tokenizer.WORD)
			throw new AssemblerException("Expected Character", in.lineNo());
		String s = in.getWord();
		if(s.length() > 1) throw new AssemblerException("Expected Character", in.lineNo());
		return s.charAt(0);
	}
}
