import unittest
from gradescope_utils.autograder_utils.decorators import number, weight
import subprocess
from time import sleep
from re import sub
from utils import *
import signal

def nonEmptyLine(s):
        return len(s.strip()) != 0
    
def stripstr(s):
    return s.strip()

def removeEmptyLines(text):
    lst = filter(nonEmptyLine, list(map(stripstr, text.split("\n"))))
    return "\n".join(list(lst))

class RuntimeAbort(Exception):
    pass

class RuntimeSegFault(Exception):
    pass

class RuntimeFPE(Exception):
    pass

class RuntimeBusError(Exception):
    pass

class RuntimeIllegalInstruction(Exception):
    pass

def checkRuntimeErrors(proc):
    if (proc.returncode == -signal.SIGABRT):
        raise RuntimeAbort
    elif (proc.returncode == -signal.SIGSEGV):
        raise RuntimeSegFault
    elif (proc.returncode == -signal.SIGFPE):
        raise RuntimeFPE
    elif (proc.returncode == -signal.SIGBUS):
        raise RuntimeBusError
    elif (proc.returncode == -signal.SIGILL):
        raise RuntimeIllegalInstruction

class TestDiff(unittest.TestCase):
    maxDiff = None
    
    # Array of all the expected file names
    files = ['makefile']
    
    def setUp(self):
        pass

    # Associated test number within Gradescope
    @number("0")
    # Associated point value within Gradescope
    @weight(0)
    def test_checkFiles(self):
        """Ensure all required files are present"""
        
        checkFiles(self.files)
        sleep(1)
    
    # Associated test number within Gradescope
    @number("1")
    # Associated point value within Gradescope
    @weight(15)
    def test_Make(self):
        # Title used by Gradescope 
        """Test makefile "make" command"""

        # Create a subprocess to run the students make file to ensure it compiles
        test = subprocess.Popen(["make"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stderr.read().strip().decode('utf-8')
        test.kill()

        # Standard unit test case with an associated error message
        self.assertTrue(output == "", msg=output)
        test.terminate()
    
    # Associated test number within Gradescope
    @number("2")
    # Associated point value within Gradescope
    @weight(15)
    def test_MakeLab9(self):
        # Title used by Gradescope 
        """Test makefile "make lab9" command"""

        # Create a subprocess to run the student's make file to ensure it compiles
        test = subprocess.Popen(["make", "lab9"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stderr.read().strip().decode('utf-8')
        test.kill()

        # Standard unit test case with an associated error message
        self.assertTrue(output == "", msg=output)
        test.terminate()
    
    # Associated test number within Gradescope
    @number("3")
    # Associated point value within Gradescope
    @weight(7.5)
    def test_ExecutableMake(self):
        # Title used by Gradescope 
        """Check that "lab9" executable is created by "make" command"""

        # Create a subprocess to run the student's make file to ensure it compiles
        test = subprocess.Popen(["make"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test.kill()
        
        ls = subprocess.Popen(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        lsOut = ls.stdout.read().strip().decode('utf-8').split()
        
        correct = True
        if "lab9" not in lsOut:
            correct = False

        # Standard unit test case with an associated error message
        self.assertTrue(correct, msg="Executable \"lab9\" not created by \"make\"")
        test.terminate()
    
    # Associated test number within Gradescope
    @number("4")
    # Associated point value within Gradescope
    @weight(7.5)
    def test_ExecutableMakeLab9(self):
        # Title used by Gradescope 
        """Check that "lab9" executable is created by "make lab9" command"""

        # Create a subprocess to run the student's make file to ensure it compiles
        test = subprocess.Popen(["make", "lab9"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test.kill()
        
        ls = subprocess.Popen(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        lsOut = ls.stdout.read().strip().decode('utf-8').split()
        
        correct = True
        if "lab9" not in lsOut:
            correct = False

        # Standard unit test case with an associated error message
        self.assertTrue(correct, msg="Executable \"lab9\" not created by \"make lab9\"")
        test.terminate()
    
    # Associated test number within Gradescope
    @number("5")
    # Associated point value within Gradescope
    @weight(15)
    def test_MakeRun(self):
        # Title used by Gradescope 
        """Test makefile "make run" command"""

        # Create a subprocess to run the student's make file to ensure it compiles
        cat = subprocess.Popen(["cat", "input/20.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make", "run"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        
        output = test.stdout.read().strip().decode('utf-8')
        test.kill()
        
        # Open reference output and decode
        reference = open('reference/makeRun.txt', 'rb').read().strip().decode('utf-8')
        
        # Remove empty lines from both output and reference
        output = removeEmptyLines(output)
        reference = removeEmptyLines(reference)
        
        correct = True
        if output != reference:
            correct = False
        
        # Check that output and reference are equal
        self.assertTrue(correct, msg="Executable \"lab9\" not run by \"make run\"")
        test.terminate()
    
    # Associated test number within Gradescope
    @number("6")
    # Associated point value within Gradescope
    @weight(15)
    def test_MakeClean(self):
        # Title used by Gradescope 
        """Test makefile "make clean" command"""

        # Create a subprocess to run the student's make file to ensure it compiles
        lsMake = subprocess.Popen(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        lsMakeOut = lsMake.stdout.read().strip().decode('utf-8').split()
        
        test = subprocess.Popen(["make", "clean"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stdout.read().strip().decode().split()
        test.kill()
        
        lsClean = subprocess.Popen(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        lsCleanOut = lsClean.stdout.read().strip().decode('utf-8').split()
        
        correct = True
        msg = ''
        
        if "lab9" not in lsMakeOut:
            correct = False
            msg = ['Executable \"lab9\" not created by \"make\"']
        
        if correct is True:
            if "lab9" in lsCleanOut:
                correct = False
                msg = ['Executable \"lab9\" not removed by \"make clean\"']

        # Standard unit test case with an associated error message
        self.assertTrue(correct, msg)
        test.terminate()