import unittest
from gradescope_utils.autograder_utils.decorators import number, weight
import subprocess

class TestDiff(unittest.TestCase):
    def setUp(self):
        pass 

    # Associated test number within Gradescope
    @number("0")
    # Associated point value within Gradescope
    @weight(5)
    def test_Compile(self):
        # Title used by Gradescope 
        """Clean Compile"""

        # 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("1")
    # Associated point value within Gradescope
    @weight(10)
    def test_InputLowercaseA(self):
        # Title used by Gradescope 
        """Check that input "a" results in correct output"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/lowercaseA.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab2.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stdout.read().strip().decode('utf-8')
        test.kill()
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True
        
        # Array of expected lines of output, line by line in expected order
        expected = ['You chose apples!']
        
        # Create array of lines of submission output by splitting at new lines
        linesPrinted = output.split("\n")
        
        # Check for correct number of lines in output (to help narrow down test failures for students)
        if len(linesPrinted) < len(expected):
            # If too few lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has fewer lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        elif len(linesPrinted) > len(expected):
            # If too many lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has more lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        else:
            # If correct number of lines, check content of each line
            currentLine = 0
            for line in linesPrinted:
                # Compare each line of expected array to current line from output
                if expected[currentLine] not in line:
                    # When output does not match expected, set test failure status
                    correct = False
                currentLine += 1
            # Update message appropriately
            msg = ['Your program\'s output does not match the expected output. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        
        # Pass test pass/fail status and message to test run output
        self.assertTrue(correct, msg)
        test.terminate()

    # Associated test number within Gradescope
    @number("2")
    # Associated point value within Gradescope
    @weight(10)
    def test_InputUppercaseA(self):
        # Title used by Gradescope 
        """Check that input "A" results in correct output"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/uppercaseA.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab2.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stdout.read().strip().decode('utf-8')
        test.kill()
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True
        
        # Array of expected lines of output, line by line in expected order
        expected = ['You chose apples!']
        
        # Create array of lines of submission output by splitting at new lines
        linesPrinted = output.split("\n")
        
        # Check for correct number of lines in output (to help narrow down test failures for students)
        if len(linesPrinted) < len(expected):
            # If too few lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has fewer lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        elif len(linesPrinted) > len(expected):
            # If too many lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has more lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        else:
            # If correct number of lines, check content of each line
            currentLine = 0
            for line in linesPrinted:
                # Compare each line of expected array to current line from output
                if expected[currentLine] not in line:
                    # When output does not match expected, set test failure status
                    correct = False
                currentLine += 1
            # Update message appropriately
            msg = ['Your program\'s output does not match the expected output. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        
        # Pass test pass/fail status and message to test run output
        self.assertTrue(correct, msg)
        test.terminate()

    # Associated test number within Gradescope
    @number("3")
    # Associated point value within Gradescope
    @weight(10)
    def test_InputLowercaseO(self):
        # Title used by Gradescope 
        """Check that input "o" results in correct output"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/lowercaseO.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab2.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stdout.read().strip().decode('utf-8')
        test.kill()
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True
        
        # Array of expected lines of output, line by line in expected order
        expected = ['You chose oranges!']
        
        # Create array of lines of submission output by splitting at new lines
        linesPrinted = output.split("\n")
        
        # Check for correct number of lines in output (to help narrow down test failures for students)
        if len(linesPrinted) < len(expected):
            # If too few lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has fewer lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        elif len(linesPrinted) > len(expected):
            # If too many lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has more lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        else:
            # If correct number of lines, check content of each line
            currentLine = 0
            for line in linesPrinted:
                # Compare each line of expected array to current line from output
                if expected[currentLine] not in line:
                    # When output does not match expected, set test failure status
                    correct = False
                currentLine += 1
            # Update message appropriately
            msg = ['Your program\'s output does not match the expected output. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        
        # Pass test pass/fail status and message to test run output
        self.assertTrue(correct, msg)
        test.terminate()

    # Associated test number within Gradescope
    @number("4")
    # Associated point value within Gradescope
    @weight(10)
    def test_InputUppercaseO(self):
        # Title used by Gradescope 
        """Check that input "O" results in correct output"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/uppercaseO.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab2.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stdout.read().strip().decode('utf-8')
        test.kill()
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True
        
        # Array of expected lines of output, line by line in expected order
        expected = ['You chose oranges!']
        
        # Create array of lines of submission output by splitting at new lines
        linesPrinted = output.split("\n")
        
        # Check for correct number of lines in output (to help narrow down test failures for students)
        if len(linesPrinted) < len(expected):
            # If too few lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has fewer lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        elif len(linesPrinted) > len(expected):
            # If too many lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has more lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        else:
            # If correct number of lines, check content of each line
            currentLine = 0
            for line in linesPrinted:
                # Compare each line of expected array to current line from output
                if expected[currentLine] not in line:
                    # When output does not match expected, set test failure status
                    correct = False
                currentLine += 1
            # Update message appropriately
            msg = ['Your program\'s output does not match the expected output. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        
        # Pass test pass/fail status and message to test run output
        self.assertTrue(correct, msg)
        test.terminate()

    # Associated test number within Gradescope
    @number("5")
    # Associated point value within Gradescope
    @weight(10)
    def test_InputInvalid(self):
        # Title used by Gradescope 
        """Check that an invalid input results in correct output"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/invalid.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab2.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stdout.read().strip().decode('utf-8')
        test.kill()
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True
        
        # Array of expected lines of output, line by line in expected order
        expected = ['You entered an invalid letter.']
        
        # Create array of lines of submission output by splitting at new lines
        linesPrinted = output.split("\n")
        
        # Check for correct number of lines in output (to help narrow down test failures for students)
        if len(linesPrinted) < len(expected):
            # If too few lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has fewer lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        elif len(linesPrinted) > len(expected):
            # If too many lines, set test failure status and update message appropriately
            correct = False
            msg = ['Your program\'s output has more lines than expected. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        else:
            # If correct number of lines, check content of each line
            currentLine = 0
            for line in linesPrinted:
                # Compare each line of expected array to current line from output
                if expected[currentLine] not in line:
                    # When output does not match expected, set test failure status
                    correct = False
                currentLine += 1
            # Update message appropriately
            msg = ['Your program\'s output does not match the expected output. Check the lab instructions and try again.', f'Your output: {output}', f'Expected output: {expected}']
        
        # Pass test pass/fail status and message to test run output
        self.assertTrue(correct, msg)
        test.terminate()