package lab05;

import static org.junit.jupiter.api.Assertions.*;

import java.util.Random;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class InstructionTest {
	private CPU cpu;
	private Memory mem;
	private int[] memCpy;
	private Random rand;
	private Program prog;

	@BeforeEach
	void setUp() throws Exception {
		mem = new Memory(100);
		rand = new Random();
		cpu = new CPU(mem);
		memCpy = new int[100];
		for (int i=0;i<100;i++) {
			int r = rand.nextInt();
			mem.set(i, r);
			memCpy[i]=r;
		}
		prog = new Program();
	}

	@Test
	void testADDIMM() { 
		prog.add(Instruction.factory("ADD","IMM",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(23,cpu.getAccumulator(),"Add imm 11+12=23");
		checkMem();
	}
	
	@Test
	void testADDDIR() { 
		prog.add(Instruction.factory("ADD","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		int sum=cpu.getData(12)+11;
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(sum,cpu.getAccumulator(),"Add dir 11+@12");
		checkMem();
	}
	
	@Test
	void testADDNOM() {
		Instruction inst=Instruction.factory("ADD","NOM",12);
		assertFalse(inst.isValid(),"ADD test mode check");
	}
	
	@Test
	void testSUBIMM() { 
		prog.add(Instruction.factory("SUB","IMM",12));
		prog.load(cpu);
		cpu.setAccumulator(26);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(14,cpu.getAccumulator(),"SUB imm 26-12=14");
		checkMem();
	}
	
	@Test
	void testSUBDIR() { 
		prog.add(Instruction.factory("SUB","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		int sum=11-cpu.getData(12);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(sum,cpu.getAccumulator(),"SUB dir 11-@12");
		checkMem();
	}
	
	@Test
	void testSUBNOM() {
		Instruction inst=Instruction.factory("SUB","NOM",12);
		assertFalse(inst.isValid(),"SUB test mode check");
	}
	
	@Test
	void testMULIMM() { 
		prog.add(Instruction.factory("MUL","IMM",3));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(33,cpu.getAccumulator(),"MUL imm 3*11=33");
		checkMem();
	}
	
	@Test
	void testMULDIR() { 
		prog.add(Instruction.factory("MUL","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(2);
		int sum=cpu.getData(12)*2;
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(sum,cpu.getAccumulator(),"MUL dir 2*@12");
		checkMem();
	}
	
	@Test
	void testMULNOM() {
		Instruction inst=Instruction.factory("MUL","NOM",12);
		assertFalse(inst.isValid(),"MUL test mode check");
	}
	
	@Test
	void testDIVIMM() { 
		prog.add(Instruction.factory("DIV","IMM",3));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(3,cpu.getAccumulator(),"DIV imm 11/3=3");
		checkMem();
	}
	
	@Test
	void testDIVDIR() { 
		prog.add(Instruction.factory("DIV","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(cpu.getData(12)*5);
		int sum=cpu.getAccumulator()/cpu.getData(12);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(sum,cpu.getAccumulator(),"DIV dir @12/4");
		checkMem();
	}
	
	@Test
	void testDIVNOM() {
		Instruction inst=Instruction.factory("DIV","NOM",12);
		assertFalse(inst.isValid(),"DIV test mode check");
	}
	
	@Test
	void testANDIMM_T() { 
		prog.add(Instruction.factory("AND","IMM",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(1,cpu.getAccumulator(),"AND imm 11 & 12= True");
		checkMem();
	}
	
	@Test
	void testANDIMM_F1() { 
		prog.add(Instruction.factory("AND","IMM",0));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(0,cpu.getAccumulator(),"AND imm 0 & 12= False");
		checkMem();
	}
	
	@Test
	void testANDIMM_F2() { 
		prog.add(Instruction.factory("AND","IMM",12));
		prog.load(cpu);
		cpu.setAccumulator(0);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(0,cpu.getAccumulator(),"AND imm 0 & 12= True");
		checkMem();
	}
	
	@Test
	void testANDDIR() { 
		prog.add(Instruction.factory("AND","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(1,cpu.getAccumulator(),"AND DIR 11 & @12= True");
		checkMem();
	}
	
	@Test
	void testANDNOM() {
		Instruction inst=Instruction.factory("AND","NOM",12);
		assertFalse(inst.isValid(),"AND test mode check");
	}
	
	@Test
	void testCMLIMM() {
		Instruction inst=Instruction.factory("CML","IMM",12);
		assertFalse(inst.isValid(),"CMZ IMM test mode check");
	}
	
	@Test
	void testCMLDIR_T() {
		prog.add(Instruction.factory("CML","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setData(12, -37);
		memCpy[13]=-37;
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(1,cpu.getAccumulator(),"CML DIR (@12=-37)<0 = True");
		checkMem();
	}
	
	@Test
	void testCMLDIR_F() {
		prog.add(Instruction.factory("CML","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setData(12,0);
		memCpy[13]=0;
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(0,cpu.getAccumulator(),"CML DIR (@12=0)<0 = False");
		checkMem();
	}
	
	@Test
	void testCMLNOM() {
		Instruction inst=Instruction.factory("CML","NOM",12);
		assertFalse(inst.isValid(),"CML test mode check");
	}
	
	@Test
	void testCMZIMM() {
		Instruction inst=Instruction.factory("CMZ","IMM",12);
		assertFalse(inst.isValid(),"CMZ IMM test mode check");
	}
	
	@Test
	void testCMZDIR_T() {
		prog.add(Instruction.factory("CMZ","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setData(12,0);
		memCpy[13]=0;
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(1,cpu.getAccumulator(),"CMZ DIR (@12=0)==0 = True");
		checkMem();
	}
	
	@Test
	void testCMZDIR_F() {
		prog.add(Instruction.factory("CMZ","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(11);
		cpu.setData(12,344);
		memCpy[13]=344;
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(0,cpu.getAccumulator(),"CMZ DIR (@12=344)==0 = False");
		checkMem();
	}
	
	@Test
	void testCMZNOM() {
		Instruction inst=Instruction.factory("CMZ","NOM",12);
		assertFalse(inst.isValid(),"CMZ test mode check");
	}
	
	@Test
	void testNOPIMM() {
		Instruction inst=Instruction.factory("NOP","IMM",12);
		assertFalse(inst.isValid(),"NOP test mode check");
	}
	
	@Test 
	void testNOPNOM() {
		prog.add(Instruction.factory("NOP","NOM",12));
		prog.load(cpu);
		cpu.setAccumulator(235);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(235,cpu.getAccumulator(),"NOP NOM");
		checkMem();
	}
	
	@Test
	void testLODIMM() {
		prog.add(Instruction.factory("LOD","IMM",12));
		prog.load(cpu);
		cpu.setAccumulator(235);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(12,cpu.getAccumulator(),"LOD IMM");
		checkMem();
	}
	
	@Test
	void testLODDIR() {
		prog.add(Instruction.factory("LOD","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(235);
		cpu.setHalted(false);
		cpu.execute();
		assertEquals(cpu.getData(12),cpu.getAccumulator(),"LOD DIR");
		checkMem();
	}
	
	@Test
	void testLODNOM() {
		Instruction inst=Instruction.factory("LOD","NOM",12);
		assertFalse(inst.isValid(),"LOD test mode check");
	}
	
	@Test
	void testSTOIMM() {
		Instruction inst=Instruction.factory("STO","IMM",12);
		assertFalse(inst.isValid(),"STO IMM test mode check");
	}
	
	@Test
	void testSTODIR() {
		prog.add(Instruction.factory("STO","DIR",12));
		prog.load(cpu);
		cpu.setAccumulator(235);
		cpu.setHalted(false);
		cpu.execute();
		memCpy[13]=235;
		assertEquals(235,cpu.getData(12),"STO DIR");
		checkMem();
	}
	
	@Test
	void testSTONOM() {
		Instruction inst=Instruction.factory("STO","NOM",12);
		assertFalse(inst.isValid(),"STO NOM test mode check");
	}

	@Test
	void testHLTIMM() {
		Instruction inst=Instruction.factory("HLT","IMM",12);
		assertFalse(inst.isValid(),"HLT test mode check");
	}
	
	@Test
	void testHLTNOM() {
		prog.add(Instruction.factory("HLT","NOM",12));
		prog.add(Instruction.factory("ADD","IMM",300));
		prog.load(cpu);
		memCpy[1]=mem.get(1);
		cpu.setAccumulator(235);
		cpu.setHalted(false);
		cpu.execute();
		cpu.execute();
		assertEquals(235,cpu.getAccumulator(),"HLT NOM");
		checkMem();
	}
	
	void checkMem() {
		// mem[0] contains instruction
		for(int i=1;i<100;i++) {
			assertEquals(memCpy[i],mem.get(i),"Memory cell " + i + " incorrect.");
		}
	}

}
