from multiprocessing.sharedctypes import Value
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(7.5)
    def test_Header(self):
        # Title used by Gradescope 
        """Check that PPM header information is correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/15.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab7.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        try:
            output = test.stdout.read().strip().decode('utf-8')
            test.kill()

            # Set msg to blank string, in case test passes
            msg = ''
            
            # Array of expected lines of output, line by line in expected order
            expected = ['P3', '15', '7', '255']
            
            # Check if header information is correct
            header = output.split()
            try:
                if header[0] != expected[0]:
                    correct = False
                    msg = [f'Your PPM image\'s header label is incorrect. Check the Lab 6 instructions for the correct file format label. Your header laber: {header[0]}, expected header label: {expected[0]}']
                elif header[1] != expected[1]:
                    correct = False
                    msg = [f'Your PPM image\'s width is incorrect. Make sure your program uses the width entered by the user for your PPM image. Your width: {header[1]}, expected width: {expected[1]}']
                elif header[2] != expected[2]:
                    correct = False
                    msg = [f'Your PPM image\'s height is incorrect. Check the Lab 6 instructions for where to find the correct height. Your height: {header[2]}, expected height: {expected[2]}']
                elif header[3] != expected[3]:
                    correct = False
                    msg = [f'Your PPM image\'s maximum pixel value is incorrect. Check the Lab 6 instructions for the maximum pixel value of a PPM image. Your value: {header[3]}, expected value: {expected[3]}']
            except (IndexError):
                correct = False
                msg = ['Your PPM image\'s header is too short, and cannot be used in autograder comparisons. Ensure your program prints the correct header information.']
        except (UnicodeDecodeError):
            correct = False
            msg = ['Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.']
        
        # 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(7.5)
    def test_FirstPixel(self):
        # Title used by Gradescope 
        """Check that first pixel is correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/15.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab7.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        try:
            output = test.stdout.read().strip().decode('utf-8')
            test.kill()

            # Set msg to blank string, in case test passes
            msg = ''
            
            # Array of expected lines of output, line by line in expected order
            expected = [('0', '128', '0')]
            
            try:
                # Split program output from header information to pixels
                encoding, height, width, max, *values = output.split()
                # Create array of PPM pixels
                rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]
                
                try:
                    # Check that first pixel in image is correct
                    if rgb[0] != expected[0]:
                        correct = False
                        msg = [f'The first pixel in your PPM image is incorrect. Your pixel: {rgb[0]}, expected pixel: {expected[0]}']
                except (IndexError):
                    correct = False
                    msg = ['Your PPM image doesn\'t print any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.']
            except (ValueError):
                correct = False
                msg = ['Your PPM image doesn\'t print any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.']
        except (UnicodeDecodeError):
            correct = False
            msg = ['Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.']
        
        # 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(7.5)
    def test_LastPixel(self):
        # Title used by Gradescope 
        """Check that last pixel is correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/15.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab7.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        try:
            output = test.stdout.read().strip().decode('utf-8')
            test.kill()

            # Set msg to blank string, in case test passes
            msg = ''
            
            # Array of expected lines of output, line by line in expected order
            expected = [('255', '165', '0')]
            
            try:
                # Split program output from header information to pixels
                encoding, height, width, max, *values = output.split()
                # Create array of PPM pixels
                rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]
            
                try:
                    # Check that last pixel in image is correct
                    if rgb[len(rgb) - 1] != expected[0]:
                        correct = False
                        msg = [f'The last pixel in your PPM image is incorrect. Your pixel: {rgb[len(rgb) - 1]}, expected pixel: {expected[0]}']
                except (IndexError):
                    correct = False
                    msg = ['Your PPM image doesn\'t print any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.']
            except (ValueError):
                correct = False
                msg = ['Your PPM image doesn\'t print any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.']
        except (UnicodeDecodeError):
            correct = False
            msg = ['Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.']
        
        # 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(7.5)
    def test_NumPixels(self):
        # Title used by Gradescope 
        """Check that PPM image has correct number of pixels"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/15.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab7.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        try:
            output = test.stdout.read().strip().decode('utf-8')
            test.kill()

            # Set msg to blank string, in case test passes
            msg = ''
            
            # Check that length of rgb array is correct, if not then fail test and set appropriate error
            try:
                # Split program output from header information to pixels
                encoding, height, width, max, *values = output.split()
                # Create array of PPM pixels
                rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]
                
                if len(rgb) < 105:
                    correct = False
                    msg = [f'Your PPM image contains fewer pixels than expected. Your length: {len(rgb)} pixels, expected length: 105 pixels']
                elif len(rgb) > 105:
                    correct = False
                    msg = [f'Your PPM image contains more pixels than expected. Your length: {len(rgb)} pixels, expected length: 105 pixels']
            except (ValueError):
                correct = False
                msg = ['Your PPM image doesn\'t print any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.']
        except (UnicodeDecodeError):
            correct = False
            msg = ['Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.']
        
        # 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(15)
    def test_Width15(self):
        # Title used by Gradescope 
        """Check that PPM image with width 15 is created"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/15.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["./lab7.out"], stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        try:
            output = test.stdout.read().strip().decode('utf-8')
            test.kill()

            # Set msg to blank string, in case test passes
            msg = ''
            
            # Array of expected pixels in PPM output
            expected = [('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('0', '128', '0'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '255', '255'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0'), ('255', '165', '0')]
            
            try:
                # Split program output from header information to pixels
                encoding, height, width, max, *values = output.split()
                # Create array of PPM pixels
                rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]

                # Check that length of rgb array is correct, if yes then loop through pixels until a wrong one is found
                if len(rgb) < 105:
                    correct = False
                    msg = [f'Your PPM image contains fewer pixels than expected. Your length: {len(rgb)} pixels, expected length: 105 pixels']
                elif len(rgb) > 105:
                    correct = False
                    msg = [f'Your PPM image contains more pixels than expected. Your length: {len(rgb)} pixels, expected length: 105 pixels']
                else:
                    index = 0
                    for pixel in rgb:
                        if pixel != expected[index]:
                            correct = False
                            index = index + 1
                            msg = [f'Pixel #{index} in your PPM image is incorrect. Fix this pixel and try again. Your pixel: {pixel}, expected pixel: {expected[index - 1]}']
                            break
                        index = index + 1
            except (ValueError):
                correct = False
                msg = ['Your PPM image doesn\'t print any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.']
        except (UnicodeDecodeError):
            correct = False
            msg = ['Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.']
        
        # Pass test pass/fail status and message to test run output
        self.assertTrue(correct, msg)
        test.terminate()