/**
 * 
 */
package dspl.backend.x86;

import dspl.SymbolTable;
import dspl.backend.AssemblyfileGenerator;
import dspl.backend.x86.operands.X86ImmediateOperand;
import dspl.backend.x86.operands.X86IndexMemoryOperand;
import dspl.backend.x86.operands.X86MemoryOperand;
import dspl.backend.x86.operands.X86Operand;
import dspl.backend.x86.operands.X86Register;
import dspl.frontend.ParserTokensTokenTypes;
import dspl.frontend.SymbolicException;
import dspl.intermediate.variable.DSPLInteger;
import dspl.intermediate.variable.Variable;

/**
 * X86VariableManager is responsible for providing registers
 * to store Variables when necessary. 
 * @author David
 *
 */
public class X86VariableManager {
	//TODO (Dave) clean up X86Variable manager (like woah)
	int GENERAL_REGISTER_EAX = 0; 
	int GENERAL_REGISTER_EBX = 1;
	int GENERAL_REGISTER_ECX = 2; 
	int GENERAL_REGISTER_EDX = 3; 
	int GENERAL_REGISTER_EDI = 4; 
	int GENERAL_REGISTER_ESI = 5; 
	int GENERAL_REGISTER_ESP = 6; 
	int GENERAL_REGISTER_EBP = 7; 
	
	private static X86Register eaxReg = new X86Register("%eax"); 
	private static X86Register ebxReg = new X86Register("%ebx"); 
	private static X86Register ecxReg = new X86Register("%ecx"); 
	private static X86Register edxReg = new X86Register("%edx"); 
	private static X86Register ediReg = new X86Register("%edi"); 
	private static X86Register esiReg = new X86Register("%esi"); 
	
	
	private static X86Register xmm0Reg = new X86Register("%xmm0"); 
	private static X86Register xmm1Reg = new X86Register("%xmm1"); 
	private static X86Register xmm2Reg = new X86Register("%xmm2"); 
	private static X86Register xmm3Reg = new X86Register("%xmm3"); 
	
	
	public static X86Register getEax() { 
		eaxReg.setRegisterContentsSize(4); //reset the contents so that it is always four, expect the few cases which use bytes to manually set it themeselves
		return eaxReg; 
	}
	public static X86Register getEbx() { 
		ebxReg.setRegisterContentsSize(4);
		return ebxReg; 
	}
	public static X86Register getEcx() { 
		ecxReg.setRegisterContentsSize(4);
		return ecxReg; 
	}
	public static X86Register getEdx() { 
		edxReg.setRegisterContentsSize(4);
		return edxReg; 
	}
	public static X86Register getEdi() { 
		return ediReg;
	}
	public static X86Register getEsi() { 
		return esiReg;
	}
	public static X86Register getXmm0() { 
		return xmm0Reg; 
	}
	public static X86Register getXmm1() { 
		return xmm1Reg; 
	}
	public static X86Register getXmm3() { 
		return xmm3Reg;
	}
	
	
	private final static String registerStrings[] = { "eax", "ebx", "ecx", "edx", "edi", "esi", "esp", "ebp" }; 
	
	private Variable registerValues[] = new Variable[8]; //keeps track of which Variables are currently stored in each register
	
	/**
	 * Postfix applied to each register's name to form the ID for the Variable used to temporarily store the value of a register in memory
	 */
	private final static String ST_TEMP_POSTFIX = "Storage"; 
	
	/**
	 * Stores the SymbolTable for the program
	 */
	private SymbolTable st = null; 
	
	/**
	 * AssemblyFileGenerator for the current program<br>
	 * Used to write additional move instructions that may be required when 
	 * providing registers
	 */
	private AssemblyfileGenerator afg = null; 
	
	public X86VariableManager(SymbolTable st, AssemblyfileGenerator afg) throws SymbolicException { 
		this.st = st; 
		this.afg = afg; 
		Variable regVar; 
		
		//add variables (assumed to be temporary) for each 
		//for example %eax has a variable called "eaxStorage"
		for (int i=0; i<registerStrings.length; i++) {
			String varID = (registerStrings[i] + ST_TEMP_POSTFIX); 
			regVar = Variable.getNewAtomic(ParserTokensTokenTypes.LITERAL_int, varID, 0, false); 
			st.addVariable(regVar); 
		}
	}
	
	public static void getVarInRegister(Variable v) { 
		if (v.isArray()) { 
			throw new Error("cannot put array into variable"); 
		}
		X86Register reg; 
		if (v.isConstant()) { 
			
		}
	}
	
	public static void getRegister() { 
		
	}
	
	public static X86Register getFreeRegister() { 
		//for (int i=0; )
		return null; 
	}
	
	public static void getRegister(int registerNum) { 
		
	}
	
	
	public static void writeMoveInstruction(X86Operand src, X86Operand dest, AssemblyfileGenerator afg) { 
		if (src.getOperandSize()==1 && dest.getOperandSize()==4) { 
			afg.writeAsmInstr(//using min handles (sometimes but not foolproof) case where one of htem is a register, but other isn't (a byte for example)
					"movzbl", //convert byte (zero extend) to long and move it 
					src.toString(),
					dest.toString());
			return;
		}
		else if (src.getOperandSize()==dest.getOperandSize()) { //if equal, all good
			afg.writeAsmInstr(//using min handles (sometimes but not foolproof) case where one of htem is a register, but other isn't (a byte for example)
					X86AsmFormatter.getMoveInstruction(Math.min(src.getOperandSize(), dest.getOperandSize())), 
					src.toString(),
					dest.toString());
			return;
		}
		throw new Error("invalid op size combos");
	}
	
	public static void writeVectorMoveInstruction(X86Operand src, X86Operand dest, AssemblyfileGenerator afg, boolean isAligned) { 
		if (isAligned) { 
			afg.writeAsmInstr(
					"movdqa", //aligned 128bit move  
					src.toString(),
					dest.toString());
		}
		else { 
			afg.writeAsmInstr(
					"movdqu", //aligned 128bit move  
					src.toString(),
					dest.toString());
		}
	}
	
	//TODO: (Dave) Comment X86 Variable Manager code/conventions
	/**
	 * Very important note: Arg1, when retrieved in memory, is stored in eax. this is very important since some x86 operations can only operate on eax
	 */
	public static X86Operand prepareArg1Variable(Variable var, int allowableOpTypes, AssemblyfileGenerator afg) { 
		return prepareVariable(var, ebxReg, eaxReg, allowableOpTypes, afg); 
	}
	
	/**
	 * Very important note: see note above, same holds for ecx
	 * @param var
	 * @param allowableOpTypes
	 * @param afg
	 * @return
	 */
	public static X86Operand prepareArg2Variable(Variable var, int allowableOpTypes, AssemblyfileGenerator afg) { 
		return prepareVariable(var, edxReg, ecxReg, allowableOpTypes, afg); 
	}
	
	public static X86Operand prepareDestVariable(Variable var, int allowableOpTypes, AssemblyfileGenerator afg) { 
		return prepareVariable(var, ediReg, esiReg, allowableOpTypes, afg); 
	}
	/**
	 * intended specifically when given an array ("iA", for example),
	 * do: mov $iA, %(specified reg)
	 * and return the specified reg
	 * @param var
	 * @param afg
	 * @return
	 */
	public static X86Register prepareArrayLocAsReg(Variable var, X86Register reg, AssemblyfileGenerator afg) { 
		if (var==null) { 
			throw new Error("var is null");
		}
		if (!var.isArray()) {
			throw new Error("var is not an array"); 
		}
		if (reg==null) { 
			throw new Error("reg is null"); 
		}
		X86Operand arrayOperand = new X86ImmediateOperand(var, true); 
		//writeMoveInstruction(arrayOperand, reg, afg); 
		
		//must explicitly write move, instead of using writeMoveInstructions, since it will use movzbl on byte arrays, since the size of the byte array variable is 1, instead of knowing its actually a pointer (and therefore four). 
		afg.writeAsmInstr(//using min handles (sometimes but not foolproof) case where one of htem is a register, but other isn't (a byte for example)
				"movl", //convert byte (zero extend) to long and move it 
				arrayOperand.toString(),
				reg.toString());
		
		return reg; 
	}
	
	public static X86Operand prepareVariable(Variable var, X86Register indexRegister, X86Register variableRegister, int allowableOpTypes, AssemblyfileGenerator afg) { 
		X86Operand op = null; 
		
		if (var.isConstant() && (allowableOpTypes & X86Operand.OPERAND_TYPE_IMMEDIATE)!=0) { //if the variable is a constant, see if returning a constant is acceptable
			op = new X86ImmediateOperand(var); 
			return op; 
		}
		if (!var.isElement()) { //an atomic non-element can be operand_type_memory, but not operand_type_indexed_memory (indexing only used for elements)
			if ((allowableOpTypes & X86Operand.OPERAND_TYPE_MEMORY)!=0 && !var.isConstant()) { //if using a memory operand is acceptable
				op = new X86MemoryOperand(var); 
				return op; 
			}
		}
		if (var.isElement()) { //only consider OPERAND_TYPE_INDEXED_MEMORY if the var is an element, else doen't make sense
			if ((allowableOpTypes & X86Operand.OPERAND_TYPE_INDEXED_MEMORY)!=0) {
				X86Operand indexOperand; //holds the index, which will be moved into the indexRegister
				indexOperand = prepareVariable(var.getIndexVariable(), indexRegister, variableRegister, X86Operand.OPERAND_TYPE_IMMEDIATE | X86Operand.OPERAND_TYPE_MEMORY, afg); 
				writeMoveInstruction(indexOperand, indexRegister, afg); 
				//move index into indexRegister; 
				
				//writeMoveInstruction(prepareArg1Variable(var.getParentVariable(), X86Operand.OPERAND_TYPE_IMMEDIATE | X86Operand.OPERAND_TYPE_MEMORY, afg));
				op = new X86IndexMemoryOperand(var.getParentVariable(), null, indexRegister, var.getDataTypeByteSize()); 
				return op; 
			}
		}
		if ((allowableOpTypes & X86Operand.OPERAND_TYPE_REGISTER)!=0) {
			variableRegister.setRegisterContentsSize(var); //set register content size (should be either 4 or 1)
						
			X86Operand variableOperand; //holds the operand, which will be moved into the operandRegister
			variableOperand = prepareVariable(var, indexRegister, variableRegister, X86Operand.OPERAND_TYPE_IMMEDIATE | X86Operand.OPERAND_TYPE_MEMORY | X86Operand.OPERAND_TYPE_INDEXED_MEMORY, afg); 
			writeMoveInstruction(variableOperand, variableRegister, afg); 
			op = variableRegister; 
			
			return op; 
		}
		throw new Error("unable to acquire variable in allowable operand type"); 
	}
	
	/**
	 * Used when the exact mov instruction for putting the variable into a register is known <br>
	 * Added for type conversions <br>
	 * Always retuns reg in %eax<br>
	 * NOTE: here the register content's lenght IS NOT SET, this must be done after calling the function to get the desired moves for conversion (since length is based on the destination not the src which is the var we have here)
	 * @param var
	 * @param explicitMovInstruction
	 * @param afg
	 * @return eax reg containing the contents which was moved using the supplied move instruction
	 */
	public static X86Register prepareRegVariableWithExplicitMove(Variable var,  String explicitMovInstruction, AssemblyfileGenerator afg) { 
		X86Register variableRegister = getEax(); //store in eax
		X86Register indexRegister = getEbx(); 
		//variableRegister.setRegisterContentsSize(var); //set register content size (should be either 4 or 1)
		
		X86Operand variableOperand; //holds the operand, which will be moved into the operandRegister
		variableOperand = prepareVariable(var, indexRegister, variableRegister, X86Operand.OPERAND_TYPE_IMMEDIATE | X86Operand.OPERAND_TYPE_MEMORY | X86Operand.OPERAND_TYPE_INDEXED_MEMORY, afg); 
		afg.writeAsmInstr(explicitMovInstruction,variableOperand.toString(), variableRegister.toString()); 
		
		return variableRegister; 
	}
	
	/**
	 * Pushes the floating point variable onto the FPU stack (@ ST(0)); 
	 * @param var
	 * @param indexRegister
	 * @param variableRegister
	 * @param allowableOpTypes
	 * @param afg
	 */
	public static void pushFloatingPointVariable(Variable var, AssemblyfileGenerator afg) {
		if (var.getDataType()!=Variable.DATA_TYPE_FLOAT) { 
			throw new Error("invalid: non float"); 
		}
		else if (var.isArray()) {
			throw new Error("arrays invalid allowed"); 
		}
		X86Operand op; 
		if (var.isElement()) {
			X86Register eax = getEax();
			X86Register ebx = getEbx(); 
			op = prepareArg1Variable(var, X86Operand.OPERAND_TYPE_INDEXED_MEMORY, afg); //gets (%eax, %ebx, 4); 
		}
		else {
			op = prepareArg1Variable(var, X86Operand.OPERAND_TYPE_MEMORY, afg); //gets (%eax, %ebx, 4); 
		}
		writeFPLoad(var, op, afg); //now write the appriopriate load instruction
	}
	
	/**
	 * Pushes op (which represents var) onto the stack
	 * @param var
	 * @param op
	 */
	public static void writeFPLoad(Variable var, X86Operand op, AssemblyfileGenerator afg) {
		//if the value is set, then this is a floating point constant, which is stored as a double, so use fldl
		if (var.isValueSet()) { 
			afg.writeAsmInstr("fldl", op.toString()); //floating point load double
		}
		else { //else it is an actually declared float, in which case it is 32 bits, so use flds
			afg.writeAsmInstr("flds", op.toString()); 
		}
	}
	
	/**
	 * 
	 * @param var the destination variable to write to
	 * @param afg
	 */
	public static void writeTopOfFPStack(Variable var, AssemblyfileGenerator afg) { 
		X86Operand op = prepareArg1Variable(var, X86Operand.OPERAND_TYPE_MEMORY | X86Operand.OPERAND_TYPE_INDEXED_MEMORY, afg); 
		afg.writeAsmInstr("fstps", op.toString()); 
	}
}
