 /**
 * 
 */
package dspl.backend;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import dspl.SymbolTable;
import dspl.intermediate.variable.Variable;

/**
 * Utility used to actually write the provided assembly file and instructions
 * @author dlariviere
 *
 */
public class AssemblyfileGenerator {
	/**
	 * Used to generate unique labels for branching/control flow. 
	 * 
	 */
	private int nextBranchLabelNum = 0; 
	
	/**
	 * Used to generate labels for string constants 
	 */
	private int nextStringID = 0; 
	
	/**
	 * PrintWriter responsible for writing instructions directly to the asm file
	 */
	private PrintWriter writer;
	
	/**
	 * If true, all data written to asm file will also be written to std out 
	 */
	private boolean writeStdOut = false; 
	
	/**
	 * 
	 */
	private HashMap stringLabelsHashMap = null;
	
	/**
	 * Create an AssemblyfileGenerator to write a basic assembly program to the target file
	 * @param outputFilename: name of ASM file to write to
	 * @param writeStdOut if true, all data written to the file will also be printed to standard out
	 * @throws FileNotFoundException: thrown when file cannot be opened 
	 */
	public AssemblyfileGenerator(String outputFilename, boolean writeStdOut) throws FileNotFoundException { 
		writer = new PrintWriter(new File(outputFilename)); 
		this.writeStdOut = writeStdOut; 
		stringLabelsHashMap = new HashMap(); 
	}
	
	/**
	 * Called after the program is done writing instructions to the generator. <br>
	 * Responsible for flushing and closing the file.
	 *
	 */
	public void closeFile() { 
		writer.flush();
		writer.close(); 
	}
	
	/**
	 * Returns a unique branch label to be used for branch instructions
	 * @return 
	 */
	public String getBranchLabel() {
		String label = new String("L" + Integer.toString(nextBranchLabelNum)); 
		nextBranchLabelNum++; 
		return label; 
	}
	
	/**
	 * Output line
	 * @param line: line to write to file
	 */
	private void outputLine(String line) { 
		writer.println(line); 
		if (writeStdOut) { 
			System.out.println(line); 
		}
	}
	
	/**
	 * Output tabbed (indented) line
	 * @param line: line to write to file, prepended by tab
	 */
	private void outputILine(String line) { 
		writer.println("\t" + line); 
		if (writeStdOut) { 
			System.out.println("\t" + line); 
		}
	}
	
	/**
	 * Called internally by Data section when it encounters a string
	 * will return a unique label to refer to that string.<p>
	 * The stringLabelHashMap will also be updated mapping the 
	 * Variable stringVar to the generator String label;
	 * @param string
	 * @deprecated This functionality is now in the SymbolTable
	 * @return
	 */
	/*
	private String getNextStringLabel(Variable stringVar) { 
		String label = new String("stringLabel" + Integer.toString(nextStringID)); 
		nextStringID++; 
		stringLabelsHashMap.put(stringVar, label); 
		return label; 
	}
	*/
	
	/**
	 * Returns the label used to refer to a particular String that should've
	 * been added to the data section of the ASM file
	 * @param stringVar
	 * @return
	 */
	public String getStringLabel(Variable stringVar) { 
		String label = (String) stringLabelsHashMap.get(stringVar);
		if (label==null) { 
			throw new Error("Error: can not find string (" + stringVar.getIdentifier() + ") in Assembly Label Hashmap"); 
		}
		return label; 
	}
	
	/**
	 * Generates the initial Assembly file given it's variables<br>
	 * Should be called before all other write commands are called on it
	 * @param variables
	 */
	public void writeSkeletonASM(ArrayList variables) {
		writeDataSection(variables); 
		writeBSSSection(variables); 
		/*
		outputLine(".section data");
		outputLine(".section bss");
		*/ 
		outputLine(".section text,");
		outputLine(".globl _main");
		outputLine("_main:"); 
		outputILine("nop"); //start all programs with NOP to allow for debugging
	}
	
	
	/**
	 * Generate the data section for the program based on its variables. <p>
	 * All atomic elements and initially declared variables go in the symbol table.<p>
	 * The only elements that do not are arrays whose defintion is unknown ("i.e.: int_array iA[5];"), which should instead be put in the .bss section<br>
	 * @param variables
	 */
	private void writeDataSection(ArrayList variables) { 
		outputLine(".section data,"); 
		//outputILine(".align 16"); //16 byte alignment of all variables 
		
		Iterator it = variables.iterator();
		// TODO: (Dave) handle writing preset known values when allocating memory, besides with strings
		
		while (it.hasNext()) { 
			Variable v = (Variable) it.next(); 
			if (v.getDataType()==Variable.DATA_TYPE_STRING) {
				//outputLine(v.getIdentifier() + ": ");
				//outputILine(".asciz" + "\"" + v.value + "\"");
				outputLine(v.getIdentifier() + ":"); 
				//outputLine(getNextStringLabel(v) + ":"); 
				outputILine(".asciz " + v.value); //for now, the frontend people are adding the string directly from source code with quotes
			}
			if (v.getDataType()==Variable.DATA_TYPE_FLOAT && v.isValueSet()) {
				outputLine(v.getIdentifier() + ":"); 
				/*
				outputILine(".long 0");
				Float f = (Float) v.getValue(); 
				outputILine(".long " + new String(Integer.toString(Float.floatToIntBits(f.floatValue()))));
				outputILine(".align 8,");
				*/
				Double d = (Double) v.getValue(); 
				outputILine(".double " + d.toString());
				//System.out.println("DAVETEST: " + new Double(200000000000.0)); 
			}
		}
	}
	
	/**
	 * Writes the BSS section, which includes all variables that are not explicitly defined
	 * before hand. <p>
	 * BSS declared variables differ from those in the Data segment in that variables created in the 
	 * BSS section to not allocate space within the actual executable, unlike in the Data section
	 * where allocating a 1000 element array would actually make the final executable 1000 * sizeof(array element) bigger<p>
	 * @param variables
	 */
	private void writeBSSSection(ArrayList variables) { 
		outputLine(".section bss,");
		//outputILine(".align 16"); //16 byte alignment of all variables 
		
		Iterator it = variables.iterator();
		while (it.hasNext()) { 
			Variable v = (Variable) it.next(); 
			if (v.getDataType()!=Variable.DATA_TYPE_STRING && !v.isValueSet()) { //only values that aren't set go in the bss
				outputILine(".comm " + v.getIdentifier() + ", " + v.getByteSize());
			}
		}
	}
	
	/**
	 * Writes standard entry sequence for a function according to Intel ABI<br>
	 * Note: does not handle allocating additional space on the stack for local variables<br>
	 *
	 */
	public void writeStdEntrySeq() { 
		outputILine("push %ebp");
		outputILine("mov %esp, %ebp");
	}
	
	/**
	 * Writes standard exit sequence for a function
	 */
	public void writeStdOutputSeq() { 
		outputILine("mov %ebp, %esp"); 
		outputILine("pop %ebp"); 
		outputILine("ret"); 
	}
	
	
	/**
	 * Writes a standard two operand ASM instruction
	 */
	public void writeAsmInstr(String op, String src, String dest) { 
		outputILine(op + " " + src + ", " + dest); 
	}
	
	/**
	 * Writes one operand ASM instruction
	 * @param op
	 * @param arg
	 */
	public void writeAsmInstr(String op, String arg)  {
		outputILine(op + " " + arg); 
	}
	
		
	/**
	 * Writes an ASM label<br>
	 * Example: "L10:"
	 * @param label
	 */
	public void writeLabel(String label)  {
		outputLine(label + ":"); 
	}
}
