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(10)
    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(20)
    def test_Stderr(self):
        # Title used by Gradescope 
        """Check that program prints correct output to stderr"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/Lp972.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab4.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stderr.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 = ['Enter a capital letter: Enter a lowercase letter: Enter an integer (33 - 126): Enter an integer:']
        
        # 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_InputSequenceLp972(self):
        # Title used by Gradescope 
        """Check that input sequence "L, p, 97, 2" results in correct stdout"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/Lp972.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab4.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 = ['The lowercase of L is: l', 'The ASCII values for l are:', 'DECIMAL         OCTAL           HEX', '108             0154            0x6c', 'The uppercase of p is: P', 'The ASCII values for P are:', 'DECIMAL         OCTAL           HEX', '80              0120            0x50', 'The ASCII character of 97 is: a', '2 cubed is 8.0']
        
        # 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_InputSequenceTt44171(self):
        # Title used by Gradescope 
        """Check that input sequence "T, t, 44, 171" results in correct stdout"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/Tt44171.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab4.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 = ['The lowercase of T is: t', 'The ASCII values for t are:', 'DECIMAL         OCTAL           HEX', '116             0164            0x74', 'The uppercase of t is: T', 'The ASCII values for T are:', 'DECIMAL         OCTAL           HEX', '84              0124            0x54', 'The ASCII character of 44 is: ,', '171 cubed is 5000211.0']
        
        # 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_InputSequenceGh3314(self):
        # Title used by Gradescope 
        """Check that input sequence "G, h, 33, 14" results in correct stdout"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/Gh3314.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab4.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 = ['The lowercase of G is: g', 'The ASCII values for g are:', 'DECIMAL         OCTAL           HEX', '103             0147            0x67', 'The uppercase of h is: H', 'The ASCII values for H are:', 'DECIMAL         OCTAL           HEX', '72              0110            0x48', 'The ASCII character of 33 is: !', '14 cubed is 2744.0']
        
        # 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_InputSequenceAg12632(self):
        # Title used by Gradescope 
        """Check that input sequence "A, g, 126, 32" results in correct stdout"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/Ag12632.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab4.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 = ['The lowercase of A is: a', 'The ASCII values for a are:', 'DECIMAL         OCTAL           HEX', '97              0141            0x61', 'The uppercase of g is: G', 'The ASCII values for G are:', 'DECIMAL         OCTAL           HEX', '71              0107            0x47', 'The ASCII character of 126 is: ~', '32 cubed is 32768.0']
        
        # 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()