package lab06;

/**
 * Model a single Pippin Instruction.
 * 
 * A Pippin Instruction consists of:
 * <ol>
 * <li>An operation - the kind of operation that the CPU should perform
 * <li>A mode - How the instruction argument should be interpreted
 * <li>An argument - an integer value
 * </ol>
 * 
 * @author cs140
 */
public class Instruction {
	
	private static final String[] opNames= { "NOP", "LOD", "STO", "ADD", "SUB", "MUL", "DIV", "AND", "NOT", "CML", "CMZ","HLT" } ;
	private static final String[] modeNames = { "NOM", "IMM", "DIR" };
	
	// Fields
	private int opcode;
	private int mode;
	private int argument;
	
	/**
	 * Generate an InstructionXXX object from the parameters.
	 * XXX is one of the valid operation names.
	 * If the operation is not recognized, returns an InstructionNOP.  
	 * @param opName operation name
	 * @param modeName mode name
	 * @param argument argument value
	 * @return a reference to an InstructionXXX object
	 */
	static public Instruction factory(String opName,String modeName,int argument) {
		return factory(findOpcode(opName),findMode(modeName),argument);
		
	}
	
	/**
	 * Generate an InstructionXXX object from the parameters.
	 * XXX is one of the valid operation names.
	 * If the operation is not recognized, returns an InstructionNOP
	 * @param binInstr binary encoded instruction
	 * @return a reference to an InstructionXXX object
	 */
	static public Instruction factory(int binInstr) { 
		return factory(getEncodedOpcode(binInstr),getEncodedMode(binInstr),getEncodedArg(binInstr));
	}

		
	/**
	 * Generate an InstructionXXX object from the parameters.
	 * XXX is one of the valid operation names.
	 * If the operation is not recognized, returns an InstructionNOP.
	 * @param opcode index into the opNames array
	 * @param mode index into the modeNames array
	 * @param argument argument value
	 * @return a reference to an InstructionXXX object
	 */
	static public Instruction factory(int opcode,int mode,int argument) {
		switch(opcode) {
		case 0: return new InstructionNOP(opcode,mode,argument); 
		case 1: return new InstructionLOD(opcode,mode,argument); 
		case 2: return new InstructionSTO(opcode,mode,argument); 
		case 3: return new InstructionADD(opcode,mode,argument);
		case 4: return new InstructionSUB(opcode,mode,argument); 
		case 5: return new InstructionMUL(opcode,mode,argument); 
		case 6: return new InstructionDIV(opcode,mode,argument); 
		case 7: return new InstructionAND(opcode,mode,argument);
		case 8: return new InstructionNOT(opcode,mode,argument);
		case 9: return new InstructionCML(opcode,mode,argument);
		case 10: return new InstructionCMZ(opcode,mode,argument); 
		case 11: return new InstructionHLT(opcode,mode,argument);
		default:
			// System.out.println("Instruction not recognized");
			return new InstructionNOP(0,mode,argument); 
		}
	}
	
	/**
	 * Construct an instruction object using the opcode, mode, and argument parameters.
	 * @param opcode index into the opNames array for this operation
	 * @param mode index into the modeNames array to specify the mode for this instruction
	 * @param argument the value of the argument to be used.
	 */
	public Instruction(int opcode,int mode,int argument) {
		this.opcode = opcode;
		this.mode = mode;
		this.argument = argument;
	}
	
	/**
	 * Store this instruction in the specified memory at the specified location.
	 * 
	 * The store method will write over whatever used to be in memory with the 
	 * binary encoded instruction.
	 * 
	 * @param mem The memory in which to store the instruction
	 * @param loc the location (index or address) where the instruction will be stored.
	 */
	public void store(Memory mem,int loc) {
		mem.set(loc,encodeOpModeArg(opcode,mode,argument));
	}
	
	/**
	 * get the three character operation name (mnemonic) for this instruction.
	 * @return the operation name
	 */
	public String getOpName() { 
		if (opcode<0 || opcode>=opNames.length) return "???";
		return opNames[opcode]; }

	/**
	 * get the opcode for this instruction
	 * @return opcode
	 */
	public int getOpcode() { return opcode; }

	/**
	 * get the three character mode mnemonic for this instruction.
	 * @return the mode name (mnemonic)
	 */
	public String getModeName() {
		if (mode<0 || mode>=modeNames.length) return "???";
		return modeNames[mode]; 
	}

	/**
	 * get the mode value from this instruction.
	 * @return the mode value
	 */
	public int getMode() { return mode; }

	/**
	 * get the argument value from this instruction.
	 * @return the argument value
	 */
	public int getArgument() { return argument; }
	
	/**
	 * Fetch the value of the operand, based on the mode, for this instruction
	 * @param cpu The Pippin CPU to use when resolving the operand
	 * @return the resolved operand.
	 */
	public int fetchOperand(CPU cpu) {
		// Note... override this method for special opcodes (like STO)
		if (mode==findMode("IMM")) return argument;
		if (mode==findMode("DIR")) return cpu.getData(argument);
		System.out.println("fetchOperand encounterred something unexpected.");
		return 0;
	}
	
	/**
	 * Determine if everything in this instruction is valid.
	 * 
	 * Checks include:
	 * <ul>
	 * <li>Does the instruction have a valid op-code
	 * <li>Does the instruction have a valid mode, and is that mode consistent with the opcode
	 * </ul>
	 * @return true if this instruction is valid, false otherwise.
	 */
	public boolean isValid() {
		if (opcode<0 || opcode>opNames.length) {
			System.out.println("Invalid instruction, Invalid opcode: " + this);
			return false;
		}
		if (!isModeValid()) {
			System.out.println("Invalid instruction, mode: " + this);
			return false;
		}
		return true;
	}
	
	/**
	 * Determine if the mode for this instruction is valid for the current operation.
	 * 
	 * Actual implementation is in the InstructionXXX sub-classes.
	 * @return true if the mode is valid, false otherwise
	 */
	public boolean isModeValid() {
		throw new UnsupportedOperationException("Dont know about modes for a general instruction");
	}
	
	/**
	 * Determine if this mode is either IMM or DIR.
	 * @return true if mode is either IMM or DIR, false otherwise
	 */
	public boolean isModeIMMorDIR() { 
		if (mode==findMode("IMM")) return true;
		if (mode==findMode("DIR")) return true;
		return false;
	}
	
	/**
	 * execute this instruction.
	 * 
	 * The actual implementation is in each InstructionXXX sub-class
	 * @param cpu A Pippin CPU to use when executing this instruction
	 */
	public void execute(CPU cpu) {
		throw new UnsupportedOperationException("Dont know how to execute a general instruction");
	}

	/**
	 * Find the index of the parameter in the opNames array.
	 * @param name the operation name
	 * @return the index of the operation in the opNames array. 
	 * Returns -1 if the operation is not in the opNames array.
	 */
	public static int findOpcode(String name) {
		for(int i=0;i<opNames.length;i++) {
			if (name.equals(opNames[i])) return i;
		}
		return -1;
	}
	
	/**
	 * Find the index of the parameter in the modeNames array.
	 * @param name the mode name
	 * @return the index of the mode in the modeNames array. 
	 * Returns -1 if the mode is not in the modeNames array.
	 */
	public static int findMode(String name) {
		for(int i=0;i<modeNames.length;i++) {
			if (name.equals(modeNames[i])) return i;
		}
		return -1;
	}
	
	/**
	 * Generate binary encoded instruction from the parameters.
	 * @param opcode index into the opNames array
	 * @param mode index into the modeNames array
	 * @param arg argument value
	 * @return binar encoded instruction
	 */
	public static int encodeOpModeArg(int opcode,int mode,int arg) {
		int opMode= (opcode*10 + mode) << 24;
		int binInst = opMode | (arg & 0xFFFFFF);
		return binInst;
	}
	
	/**
	 * Extract and return the opcode from the binary encoded instruction.
	 * @param binInstr binary encoded instruction
	 * @return index into the opNames array extracted from the parameter
	 */
	public static int getEncodedOpcode(int binInstr) {
		int opMode=(binInstr>>24) & 0xFF; // Get last 8 bits
		return opMode/10;
	}
	
	/**
	 * Extract and return the mode from the binary encoded instruction.
	 * @param binInstr binary encoded instruction
	 * @return index into the modeNames array extracted from the parameter
	 */
	public static int getEncodedMode(int binInstr) {
		int opMode=(binInstr>>24) & 0xFF;
		return opMode%10; // Remainder operation discards the opcode
	}
	
	/**
	 * Extract and return the argument from the binary encoded instruction.
	 * @param binInstr binary encoded instruction
	 * @return argument value extracted from the parameter
	 */
	public static int getEncodedArg(int binInstr) {
		int arg = binInstr<<8; // Shift off leftmost 8 bits
		arg = arg>>8; // And shift back, propagating sign bit
		return arg;
	}

	@Override
	public String toString() {
		return "[" + getOpName() + ", mode=" + getModeName()
				+ ", argument=" + argument + "]";
	}
	
}