package lab06;

/**
 * Simulate a Pippin CPU. 
 * 
 * A Pippin CPU consists of:
 * <ul>
 * <li>a reference to a Memory object, 
 * <li>three internal integer registers: an instructionPointer, an accumulator, and a dataMemoryBase,
 * <li>a single flag called "halted",
 * <li>the logic to execute Pippin instructions in an ALU to manipulate the registers, flags, and memory
 * </ul> 
 * @author cs140
 */
public class CPU {
	
	private int accumulator;
	private int instructionPointer;
	private int dataMemoryBase;
	private Memory memory;
	private boolean halted;

	/**
	 * Constructor to initialize CPU
	 * @param memory the memory to be used by the CPU
	 */
	public CPU(Memory memory) {
		accumulator=0; // Not required, but included for clarity
		dataMemoryBase=0; 
		instructionPointer=2; // Start programs at memory location 2
		this.memory=memory;
		halted=true;
	}
	
	/**
	 * Run the current program by executing instructions until halted
	 */
	public void run() {
		halted=false;
		while(!halted) execute();
	}
	
	/**
	 * Execute a single Pippin Instruction
	 */
	public void execute() {
		
		if (halted) return; 
		
		// Fetch instruction from memory
		Instruction inst = Instruction.factory(memory.get(instructionPointer));
		instructionPointer++;
		
		if (inst.isValid()) inst.execute(this);
		else { 
			halted=true;
			System.out.println("Invalid instruction... execution halted");
		}
		
		/*------------------------------------------------------------------
		 * I have included the previous implementation of the CPU execute
		 * method for reference purposes.  All of the logic that was in 
		 * this method (and is now commented) belongs in the individual
		 * execute methods in the InstructionXXX classes.
		 * 
		// Execute instruction		
		switch(inst.getOpcode()) {
		
		case(0): //NOP
			break;
		
		case(1): //LOD
			accumulator=input;
			Trace.message(inst + " acc=" + accumulator);
			break;
			
		case(2):	//STO
			if (inst.getModeName().equals("DIR")) {
				setData(inst.getArgument(),accumulator);
				Trace.message(inst + " data[" + inst.getArgument() + "]=" + accumulator);
			} 
			else System.out.println("Error... " + inst.getOpName() + " has unrecognized inst.getModeName(): " + inst.getModeName());
			break;
			
		case(3): //ADD
			accumulator = accumulator + input;
			Trace.message(inst + " acc=" + accumulator);
			break;
			
		case(4): //SUB
			accumulator = accumulator - input;
			Trace.message(inst + " acc=" + accumulator);
			break;
			
		case(5): //MUL
			accumulator = accumulator * input;
			Trace.message(inst + " acc=" + accumulator);
			break;
			
		case(6): //DIV
			if (input==0) 
				System.out.println("Attempt to divide by zero ignored");
			else {
				accumulator = accumulator / input;
				Trace.message(inst + " acc=" + accumulator);
			}
			break;
			
		case(7): //HLT
			halted=true;
			Trace.message(inst + " Program halted");
			break;
		 
		 */
	}
	
	/**
	 * Get the current value of the accumulator register.
	 * @return The current value of the accumulator register
	 */
	public int getAccumulator() { return accumulator; }
	/**
	 * Set the value of the accumulator register.
	 * @param accumulator new value
	 */
	public void setAccumulator(int accumulator) { this.accumulator = accumulator; }

	/**
	 * Set the value of the dataMemoryBase register.
	 * @param dataMemoryBase new value
	 */
	public void setDataMemoryBase(int dataMemoryBase) {
		this.dataMemoryBase = dataMemoryBase;
	}
	
	/**
	 * Get the memory object this CPU is using.
	 * @return A reference to the Memory object this CPU is using
	 */
	public Memory getMemory() { return memory; }

	/**
	 * Set the instructionPointer register.
	 * @param instructionPointer new value
	 */
	public void setInstructionPointer(int instructionPointer) {
		this.instructionPointer = instructionPointer;
	}

	/**
	 * Get the value in the data part of the memory at the specified location.
	 * @param loc index into the data part of the memory (offset from the dataMemoryBase)
	 * @return the value in memory at the specified location
	 */
	public int getData(int loc) {
		return memory.get(loc+dataMemoryBase);
	}
	
	/**
	 * Set the value in the data part of the memory at the specified location.
	 * @param loc index into the data part of the memory (offset from the dataMemoryBase)
	 * @param value new value to write into the specified location in data memory
	 */
	public void setData(int loc,int value) {
		memory.set(loc+dataMemoryBase, value);
	}

	/**
	 * Set the halted flag to the specified value.
	 * @param halted new value for the halted flag
	 */
	public void setHalted(boolean halted) { this.halted = halted; }

}
