package swen90006.machine;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.file.FileSystems;

import org.junit.*;

import static org.junit.Assert.*;


public class PartitioningTests {
    /*
     * Any method annotated with "@Before" will be executed before each test,
     * allowing the tester to set up some shared resources.
     **/
    @Before
    public void setUp() {
    }

    /*
     * Any method annotated with "@After" will be executed after each test,
     * allowing the tester to release any shared resources used in the setup.
     **/
    @After
    public void tearDown() {
    }

    /*
     * EC1
     * test for no RET instruction
     * if there is no RET instruction
     * throw the NoReturnValueException
     * */
    @Test(expected = NoReturnValueException.class)
    public void noRetTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 10");
        machineExeLines(lines);
    }

    /*
     * EC2
     * test for more than one RET instructions
     * return the value of first RET
     * */
    @Test
    public void multiRetTest() {
        List<String> lines = new ArrayList<String>();
        final int expected = -10;
        lines.add("MOV R0 " + expected);
        lines.add("RET R0");
        lines.add("MOV R1 " + expected * 10);
        lines.add("RET R1");
        assertEquals(expected, machineExeLines(lines));
    }

    /*
     * EC3
     * test for the index i of a register less than 0
     * if i < 0
     * throw the InvalidInstructionException
     * */
    @Test(expected = InvalidInstructionException.class)
    public void smallRegIndexTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R-1 0");
        machineExeLines(lines);
    }

    /*
     * EC4
     * test for the index i of a register bigger than 31
     * if i > 31
     * throw the InvalidInstructionException
     * */
    @Test(expected = InvalidInstructionException.class)
    public void bigRegIndexTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R32 0");
        machineExeLines(lines);
    }

    /*
     * EC5
     * test for the value val
     * use MOV for an example
     * if val < -65535
     * throw the InvalidInstructionException
     * */
    @Test(expected = InvalidInstructionException.class)
    public void smallValTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R1 -65536");
        lines.add("RET R1");
        machineExeLines(lines);
    }

    /*
     * EC6
     * test for the value val
     * use MOV for an example
     * if val > 65535
     * throw the InvalidInstructionException
     * */
    @Test(expected = InvalidInstructionException.class)
    public void bigValTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R1 65536");
        lines.add("RET R1");
        machineExeLines(lines);
    }

    /*
    * EC7
    * test for the value of a register
    * if regV < -(2^31) + 1
    * stop the function, show the error
    * EC7 cannot give a test
    *
    @Test
    public void smallRegVTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 -2");
        lines.add("MOV R1 2");
        int minRegV = 2;

        for (int i = 0; i < 30; i++) {
            lines.add("MUL R0 R0 R1");
            minRegV *= 2;
        }
        //regV_of_R0 = -(2^31), minRegV = -(2^31)
        minRegV += 1;
        lines.add("MUL R0 R0 R1");
        lines.add("RET R0");
        assertEquals("The value of register if overflow.", minRegV, machineExeLines(lines));
        //System.exit(1);
    }*/

    /*
     * EC8
     * test for the value of a register
     * if regV > 2^31
     * stop the function, show the error
     *
    @Test
    public void bigRegVTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 2");
        lines.add("MOV R1 2");
        int maxRegV = 2;

        for (int i = 0; i < 30; i++) {
            lines.add("MUL R0 R0 R1");
            maxRegV *= 2;
        }
        //regV_of_R0 = (2^31), minRegV = (2^31)

        lines.add("MUL R0 R0 R1");
        lines.add("RET R0");
        assertEquals("The value of register if overflow.", maxRegV, machineExeLines(lines));
        //System.exit(1);
    }*/

    /*
     * EC9
     * test for ADD
     * return a normal value
     * */
    @Test
    public void addNormalTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 1");
        lines.add("ADD R0 R0 R0");
        lines.add("RET R0");
        assertEquals("Wrong ADD result.", 2, machineExeLines(lines));
    }

    /*
     * EC10
     * test for SUB
     * return a normal value
     * */
    @Test
    public void subNormalTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 1");
        lines.add("SUB R0 R0 R0");
        lines.add("RET R0");
        assertEquals("Wrong SUB result.", 0, machineExeLines(lines));
    }

    /*
     * EC11
     * test for MUL
     * return a normal value
     * */
    @Test
    public void mulNoramlTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 1");
        lines.add("MUL R0 R0 R0");
        lines.add("RET R0");
        assertEquals("Wrong MUL result.", 1, machineExeLines(lines));
    }

    /*
     * EC12
     * test for DIV
     * if divisor = 0
     * return a normal value (still the value of the dividend)
     * */
    @Test
    public void divByZeroTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R1 0");
        lines.add("MOV R0 -3");
        lines.add("DIV R0 R0 R1");
        lines.add("RET R0");
        assertEquals(-3, machineExeLines(lines));
    }

    /*
     * EC13
     * test for DIV
     * if divisor != 0
     * return a normal value
     * */
    @Test
    public void divByNonZeroTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R1 1");
        lines.add("MOV R0 3");
        lines.add("DIV R0 R0 R1");
        lines.add("RET R0");
        assertEquals(3, machineExeLines(lines));
    }

    /*
     * EC14
     * test for MOV
     * return a normal value
     * */
    @Test
    public void movNormalTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 1");
        lines.add("RET R0");
        assertEquals("Wrong ADD result.", 1, machineExeLines(lines));
    }

    /*
     * EC15
     * test for RET
     * if the register is undefined
     * return a normal value
     * */
    @Test
    public void retUndefinedTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("RET R0");
        assertEquals(0, machineExeLines(lines));
    }

    /*
     * EC16
     * test for RET
     * if the register is defined
     * return a normal value
     * */
    @Test
    public void retNormalTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 1");
        lines.add("RET R0");
        assertEquals("Wrong RET result.", 1, machineExeLines(lines));
    }

    /*
     * EC17
     * test for JMP
     * if val = 0
     * cause an infinite loop
     * but can not have a test case since it will loop forever
     * */

    /*
     * EC18
     * test for JMP
     * if v < 0 && |v| > the amount of previous pc count
     * throw the NoReturnValueException
     * */
    @Test(expected = NoReturnValueException.class)
    public void jmpMinusPCNoReturnTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("JMP -1");
        lines.add("RET R0");
        machineExeLines(lines);
    }

    /*
     * EC19
     * test for JMP
     * if v > 0 && v > the amount of pc count above
     * throw the NoReturnValueException
     * */
    @Test(expected = NoReturnValueException.class)
    public void jmpPositivePCNoReturnTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("JMP 2");
        lines.add("RET R0");
        machineExeLines(lines);
    }

    /*
     * EC20
     * test for JMP
     * if _pc <= v <= pc_ && v != 0
     * return a normal value
     * */
    @Test
    public void jmpNormalTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("JMP 2");
        lines.add("MOV R0 1");
        lines.add("MOV R0 2");
        lines.add("RET R0");
        assertEquals("Wrong JMP result.", 2, machineExeLines(lines));
    }

    /*
     * EC21
     * test for JZ
     * if val = 0
     * cause an infinite loop
     * but can not have a test case since it will loop forever
     * */

    /*
     * EC22
     * test for JZ
     * if v < 0 && |v| > the amount of previous pc count
     * throw the NoReturnValueException
     * */
    @Test(expected = NoReturnValueException.class)
    public void jzMinusPCNoReturnTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("JZ R0 -1");
        lines.add("RET R0");
        machineExeLines(lines);
    }

    /*
     * EC23
     * test for JZ
     * if v > 0 && v > the amount of pc count above
     * throw the NoReturnValueException
     * */
    @Test(expected = NoReturnValueException.class)
    public void jzPositivePCNoReturnTest()
            throws Throwable {
        List<String> lines = new ArrayList<String>();
        lines.add("JZ R0 2");
        lines.add("RET R0");
        machineExeLines(lines);
    }

    /*
     * EC24
     * test for JZ
     * if _pc <= v <= pc_ && v != 0
     * return a normal value
     * */
    @Test
    public void jzNormalTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R1 1");
        lines.add("JZ R1 3");
        lines.add("SUB R1 R1 R1");
        lines.add("MOV R1 2");
        lines.add("RET R1");
        assertEquals("Wrong JMP result.", 2, machineExeLines(lines));
    }

    /*
     * EC25
     * test for LDR
     * if b + v <= -(2^31)
     * stop the programme and print out the error
     *
    @Test
    public void ldrSmallAddressTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 2");
        lines.add("MOV R1 2");
        lines.add("MOV R2 2");
        int m = 2;

        for (int i = 0; i < 29; i++) {
            lines.add("MUL R0 R0 R2");
            lines.add("MUL R1 R1 R2");
            m *= 2;
        }
        System.out.println(m);
        lines.add("LDR R0 R1 -65535");
        lines.add("RET R0");
        machineExeLines(lines);
    }*/

    /*
     * EC26
     * test for LDR
     * if b + v in -(2^31-1) ... (2^31-1)
     * return a normal value
     * */
    @Test
    public void ldrNormalAddressTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 1");
        lines.add("MOV R1 2");
        lines.add("LDR R0 R1 -2");
        lines.add("RET R0");
        assertEquals("Wrong LDR result.", 0, machineExeLines(lines));
    }

    /*
     * EC27
     * test for LDR
     * if b + v >= 2^31
     * stop the programme and print out the error
     */

    /*
     * EC28
     * test for STR
     * if b + v <= -(2^31)
     * stop the programme and print out the error
     */

    /*
     * EC29
     * test for STR
     * if b + v in -(2^31-1) ... (2^31-1)
     * return a normal value
     * */
    @Test
    public void strNormalAddressTest() {
        List<String> lines = new ArrayList<String>();
        lines.add("MOV R0 1");
        lines.add("MOV R1 2");
        lines.add("STR R0 -1 R1");
        lines.add("RET R0");
        machineExeLines(lines);
    }

    /*
     * EC30
     * test for STR
     * if b + v >= 2^31
     * stop the programme and print out the error
     */


    /*
     *  Machine executes input lines
     * */
    private int machineExeLines(List<String> lines) {
        Machine m = new Machine();
        return m.execute(lines);
    }
}