package lab05;

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
	int opcode;
	int mode;
	int argument;
	
	static public Instruction factory(String opName,String modeName,int argument) {
		return factory(findOpcode(opName),findMode(modeName),argument);
		
	}
	
	static public Instruction factory(int binInstr) { 
		return factory(getEncodedOpcode(binInstr),getEncodedMode(binInstr),getEncodedArg(binInstr));
	}

		
	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); 
		}
	}
	
	public Instruction(int opcode,int mode,int argument) {
		this.opcode = opcode;
		this.mode = mode;
		this.argument = argument;
	}
	
	public void store(Memory mem,int loc) {
		mem.set(loc,encodeOpModeArg(opcode,mode,argument));
	}
	
	public String getOpName() { 
		if (opcode<0 || opcode>=opNames.length) return "???";
		return opNames[opcode]; }

	public int getOpcode() { return opcode; }

	public String getModeName() {
		if (mode<0 || mode>=modeNames.length) return "???";
		return modeNames[mode]; 
	}

	public int getMode() { return mode; }

	public int getArgument() { return argument; }
	
	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;
	}
	
	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;
	}
	
	public boolean isModeValid() {
		throw new UnsupportedOperationException("Dont know about modes for a general instruction");
	}
	
	public boolean isModeIMMorDIR() { 
		if (mode==findMode("IMM")) return true;
		if (mode==findMode("DIR")) return true;
		return false;
	}
	
	public void execute(CPU cpu) {
		throw new UnsupportedOperationException("Dont know how to execute a general instruction");
	}

	public static int findOpcode(String name) {
		for(int i=0;i<opNames.length;i++) {
			if (name.equals(opNames[i])) return i;
		}
		return -1;
	}
	
	public static int findMode(String name) {
		for(int i=0;i<modeNames.length;i++) {
			if (name.equals(modeNames[i])) return i;
		}
		return -1;
	}
	
	public static int encodeOpModeArg(int opcode,int mode,int arg) {
		int opMode= (opcode*10 + mode) << 24;
		int binInst = opMode | (arg & 0xFFFFFF);
		return binInst;
	}
	
	public static int getEncodedOpcode(int binInstr) {
		int opMode=(binInstr>>24) & 0xFF; // Get last 8 bits
		return opMode/10;
	}
	
	public static int getEncodedMode(int binInstr) {
		int opMode=(binInstr>>24) & 0xFF;
		return opMode%10; // Remainder operation discards the opcode
	}
	
	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 + "]";
	}
	
}