/**
 * 
 */
package dspl.intermediate.instructions;

import dspl.intermediate.variable.Variable;

/**
 * ArithmaticInstruction includes assignment, conversion, and all arithmatic instructions on both atomics and arrays<br>
 * It covers all common arithmatic operations (addition, subtraction, multiplication, divison, and convolusion)
 * as applied (depending on args/dest) to both atomic and sometimes array Variables. <p>
 * It also includes assignment (i.e. "x=5;") and conversion operations (converting a signed->unsigned integer)
 * 
 * @author David
 *
 */
public class ArithmaticInstruction extends Instruction {
	/**
	 * Assignment refers to setting the value of a Variable
	 * Example: x = 5; would set the value of Variable x to 5
	 */
	public final static int OPERATION_ASSIGNMENT 	= 0; 
	
	/**
	 * Mathematical Addition:<br>
	 * The table below lists the possible combinations of data types for arguments for each destination.<br>
	 * dest = arg1 + arg2; <br>
	 * <table>
	 * <tr>
	 * <td><b><u>dest</b></u></td><td><b><u>possible types for arg1</b></u></td><td><b><u>possible types for arg2</b></u></td></tr>
	 * <tr><td>byte</td><td>byte/ubyte</td><td>byte/ubyte</td></tr>
	 * <tr><td>ubyte</td><td>byte/ubyte</td><td>byte/ubyte</td></tr>
	 * <tr><td>int</td><td>int/uint</td><td>int/uint</td></tr>
	 * <tr><td>uint</td><td>int/uint</td><td>int/uint</td></tr>
	 * <tr><td>float</td><td>float</td><td>float</td></tr>
	 * <tr><td>complex</td><td>complex</td><td>complex</td></tr>
	 *  
	 * <tr><td>byte_array</td><td>byte_array/ubyte_array</td><td>byte_array/ubyte_array/byte/ubyte</td></tr>
	 * <tr><td>ubyte_array</td><td>byte_array/ubyte_array</td><td>byte_array/ubyte_array/byte/ubyte</td></tr>
	 * <tr><td>int_array</td><td>int_array/uint_array</td><td>int_array/uint_array/int/uint</td></tr>
	 * <tr><td>uint_array</td><td>int_array/uint_array</td><td>int_array/uint_array/int/uint</td></tr>
	 * <tr><td>complex_array</td><td>complex_array</td><td>complex_array/complex</td></tr>
	 * </table>
	 * <br>
	 * Note that in general, arrays can be operated on by atomics (i.e. "int_array arrayA; arrayA = arrayA + 1;", which implies adding 1 to every element in arrayA). <br>
	 * but the opposite does not hold true; i.e. "int x; x = arrayA + arrayB;" does not make sense. <br>
	 */
	public final static int OPERATION_ADDITION   	= 1; 
	
	//TODO: (Varun) fill in comments in style similar to OPERATION_ADDITION
	public final static int OPERATION_SUBTRACTION 	= 2;
	//	TODO: (Varun) fill in comments in style similar to OPERATION_ADDITION
	public final static int OPERATION_MULTIPLICATION= 3; 
	//	TODO: (Varun) fill in comments in style similar to OPERATION_ADDITION
	public final static int OPERATION_DIVISION 		= 4; 
	//	TODO: (Varun) fill in comments in style similar to OPERATION_ADDITION
	public final static int OPERATION_CONVOLUTION 	= 5;
	//	TODO: (Varun) fill in comments in style similar to OPERATION_ADDITION
	public final static int OPERATION_CONVERSION    = 6; 
	
	/**
	 * contains the operation type, must be one of the
	 * OPERATION_xxx values listed above
	 */
	public int operationType = -1;
	
	/**
	 * First arguement 
	 */
	public Variable arg1; 
	
	/**
	 * Second argument, will be null in the case of OPERATION_ASSIGNMENT and OPERATION_CONVERSION
	 */
	public Variable arg2; 
	
	/**
	 * Destination variable: its value is determined by...
	 * dest = arg1 *operationType* arg2; (in the case of +,-,*,/, and ~)
	 * dest = (dest data type) arg1 (in the case of assignment)
	 */
	public Variable dest; 
	
	/**
	 * Constructor for creating ArithmaticInstruction
	 * @param operationType
	 * @param arg1
	 * @param arg2
	 * @param dest
	 */
	public ArithmaticInstruction(int operationType, Variable arg1, Variable arg2, Variable dest) { 
		//arg1/dest are never null, check it
		if (arg1==null || dest==null) { 
			throw new Error("Error constructing ArithmaticInstruction, arg1 or dest is null"); 
		}
		
		//arg2 can only be null for assignment/conversion
		if (arg2==null && operationType!=ArithmaticInstruction.OPERATION_ASSIGNMENT && operationType!=ArithmaticInstruction.OPERATION_CONVERSION) {
			throw new Error("Error constructing ArithmaticInstruction, arg2 is null"); 
		}
		
		//except in the case of conversion, ensure that the data types of the variables is the same
		if (operationType != ArithmaticInstruction.OPERATION_CONVERSION) {
			if (!arg1.isSameDataType(dest) || (arg2!=null && !arg1.isSameDataType(arg2))) {
				throw new Error("Error constructing ArithmaticInstruction: data types of args/dest not the same data type"); 
			}
		}
		
		//ensure that unless the operation is Assignment, that all args/dest are numeric types
		if (operationType!=OPERATION_ASSIGNMENT) { 
			if (!arg1.isNumeric() || !dest.isNumeric() || (arg2!=null && !arg2.isNumeric())) {
				throw new Error("Error constructing ArithmaticInstruction: non-numeric datatypes supplied"); 
			}
		}
		
		//if no errors have been thrown, proceed to assign all of the proper values
		//this.instructionType = Instruction.INST_TYPE_ARITHMATIC; 
		this.operationType = operationType;
		this.arg1 = arg1;
		this.arg2 = arg2;
		this.dest = dest; 
		
		//ensure that given two args, one an array, and one an atomic, that the array is in arg1
		//thus helps simplify code in the InstructionProcessors
		if (arg2!=null && !arg1.isArray() && arg2.isArray()) {
			Variable temp = this.arg1;
			this.arg1 = this.arg2;
			this.arg2 = this.arg1; 
		}
	}
	
	/* (non-Javadoc)
	 * @see dspl.intermediate.instructions.Instruction#usesVariable(dspl.intermediate.variable.Variable)
	 */
	public boolean usesVariable(Variable v) {
		// TODO Auto-generated method stub
		return false;
	}
	
	/**
	 * Returns the Arithmatic Operation Type
	 * @return
	 */
	public int getArithmaticOperationType() { 
		if (operationType==-1) { 
			throw new Error("getArithmaticOperationType called when operation type has not been set"); 
		}
		return this.operationType; 
	}

	public int getInstType() {
		return Instruction.INST_TYPE_ARITHMATIC;
	}
}
