from unittest import TestCase

from usbfan import Colour, Column, Message, Program


class TestColumn(TestCase):
    def test_blank(self):
        c = Column()

        self.assertEqual(len(c), 11, 'wrong number of pixels')
        for pixel in c:
            self.assertEqual(pixel, False, "pixel isn't 'False'")
        self.assertEqual(bytes(c), b'\x20\x00', 'column data is wrong')

    def test_mutation(self):
        c = Column()

        self.assertEqual(c[3], False, "4th pixel isn't 'False'")
        c[3] = True
        self.assertEqual(c[3], True, "4th pixel isn't 'True' after mutation")
        self.assertEqual(bytes(c), b'\x20\x08', 'column data is wrong')

    def test_custom(self):
        pixels = (True, False, True, False, True, False,
                  True, False, True, False, True)
        c = Column(pixels, Colour.cyan)

        self.assertSequenceEqual(c, pixels, 'column data taken from iter wrong')
        self.assertEqual(bytes(c), b'\xc5\x55', 'column data is wrong')

    def test_bad_data(self):
        with self.assertRaises(ValueError):
            Column('abcdefghijk')
        with self.assertRaises(ValueError):
            Column(colour=0x20)
        with self.assertRaises(ValueError):
            Column((True, False, True, False))


class TestMessage(TestCase):
    def test_blank(self):
        p = Message()

        self.assertEqual(len(p), 144)
        for column in p:
            self.assertIsInstance(column, Column,
                                  'Column not contained in Message iter')
        self.assertEqual(bytes(p),
                         b'\x12\xa4\xa4\xa4\xa4\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa4'
                         b'\x84\xa4\x84\xa4\x84\xa4\x84\xa4\xa4\xa4',
                         "empty program does't match reference data")

    def test_small(self):
        columns = [Column([True] + [False] * 10, Colour.red)]
        for _ in range(7):
            columns.append(Column([False] * 11, Colour.red))

        p = Message(columns)

        self.assertEqual(len(p), 8)
        self.assertEqual(bytes(p),
                         b'\x9a\xa4\xa4\xa4\xa4\xa4\x84\xa4\x84\xa4\x84\xa4\x84'
                         b'\xa4\x84\xa4\x84\xa4\x84\xa4\x84\xa3\xa4\xa4',
                         "small red dot doesn't match reference data")


class TestProgram(TestCase):
    def test_checksum(self):
        self.assertEqual(Program.checksum(b'\x00\x40\x40\x01\xA2\x00\x00\x00'),
                         b'\x00\x40\x40\x01\xA2\x00\x00\x00\x23',
                         "checksum doesn't match reference data")
        self.assertEqual(Program.checksum(b'\x00\x40\x23\x80\x83\x80\x83\x80'),
                         b'\x00\x40\x23\x80\x83\x80\x83\x80\xE9',
                         "checksum doesn't match reference data")
        with self.assertRaises(ValueError):
            Program.checksum('abcdefgh')
        with self.assertRaises(ValueError):
            Program.checksum(b'aoeu')

    def test_small(self):
        columns = [Column([True] + [False] * 10, Colour.red)]
        for _ in range(7):
            columns.append(Column([False] * 11, Colour.red))
        f = Program((Message(columns),))

        self.assertEqual(len(f), 1)
        self.assertIsInstance(f[0], Message)

        reference_message = (
            b'\x00\x40\x40\x01\x22\x00\x00\x00\xA3',
            b'\x00\x40\x23\xA4\x23\x9A\xA4\xA4\x0C',
            b'\x00\x40\x23\xA4\xA4\xA4\x84\xA4\x77',
            b'\x00\x40\x23\x84\xA4\x84\xA4\x84\x37',
            b'\x00\x40\x23\xA4\x84\xA4\x84\xA4\x57',
            b'\x00\x40\x23\x84\xA4\x84\xA3\xA4\x56',
            b'\x00\x40\x23\xA4\xA4\xA4\xA4\xA4\x97',
        )

        for data, reference in zip(f, reference_message):
            self.assertEqual(data, reference)

    def test_large(self):
        programs = list()
        for colour in (Colour.red, Colour.green, Colour.blue, Colour.yellow,
                       Colour.magenta, Colour.cyan, Colour.white):
            columns = [Column([True] + [False] * 10, colour)]
            for _ in range(14):
                columns.append(Column([False] * 11, colour))
            columns.append(Column([False] * 10 + [True], colour))
            programs.append(Message(columns))
        f = Program(programs)

        self.assertEqual(len(f), 7)
        self.assertIsInstance(f[6], Message)

        reference_message = (
            b'\x00\x40\x40\x02\x22\x01\x00\x00\xA5',
            b'\x00\x40\x23\xA4\x1D\x92\xA4\xA4\xFE',
            b'\x00\x40\x23\xA4\xA4\xA4\x80\xA4\x73',
            b'\x00\x40\x23\x84\xA4\x84\xA4\x84\x37',
            b'\x00\x40\x23\xA4\x84\xA4\x84\xA4\x57',
            b'\x00\x40\x23\x84\xA4\x84\xA4\x84\x37',
            b'\x00\x40\x23\xA4\x84\xA4\x84\xA4\x57',
            b'\x00\x40\x23\x84\xA4\x84\xA4\x84\x37',
            b'\x00\x40\x23\xA4\x84\xA4\x84\xA3\x56',
            b'\x00\x40\x23\xA4\xA4\x92\xA4\xA4\x85',
            b'\x00\x40\x23\xA4\xA4\xA4\x20\xA4\x13',
            b'\x00\x40\x23\x24\xA4\x24\xA4\x24\x17',
            b'\x00\x40\x23\xA4\x24\xA4\x24\xA4\x97',
            b'\x00\x40\x23\x24\xA4\x24\xA4\x24\x17',
            b'\x00\x40\x23\xA4\x24\xA4\x24\xA4\x97',
            b'\x00\x40\x23\x24\xA4\x24\xA4\x24\x17',
            b'\x00\x40\x23\xA4\x24\xA4\x24\xA3\x96',
            b'\x00\x40\x23\xA4\xA4\x92\xA4\xA4\x85',
            b'\x00\x40\x23\xA4\xA4\xA4\x60\xA4\x53',
            b'\x00\x40\x23\x64\xA4\x64\xA4\x64\xD7',
            b'\x00\x40\x23\xA4\x64\xA4\x64\xA4\x17',
            b'\x00\x40\x23\x64\xA4\x64\xA4\x64\xD7',
            b'\x00\x40\x23\xA4\x64\xA4\x64\xA4\x17',
            b'\x00\x40\x23\x64\xA4\x64\xA4\x64\xD7',
            b'\x00\x40\x23\xA4\x64\xA4\x64\xA3\x16',
            b'\x00\x40\x23\xA4\xA4\x92\xA4\xA4\x85',
            b'\x00\x40\x23\xA4\xA4\xA4\x00\xA4\xF3',
            b'\x00\x40\x23\x04\xA4\x04\xA4\x04\xB7',
            b'\x00\x40\x23\xA4\x04\xA4\x04\xA4\x57',
            b'\x00\x40\x23\x04\xA4\x04\xA4\x04\xB7',
            b'\x00\x40\x23\xA4\x04\xA4\x04\xA4\x57',
            b'\x00\x40\x23\x04\xA4\x04\xA4\x04\xB7',
            b'\x00\x40\x23\xA4\x04\xA4\x04\xA3\x56',
            b'\x00\x40\x23\xA4\xA4\x92\xA4\xA4\x85',
            b'\x00\x40\x23\xA4\xA4\xA4\x40\xA4\x33',
            b'\x00\x40\x23\x44\xA4\x44\xA4\x44\x77',
            b'\x00\x40\x23\xA4\x44\xA4\x44\xA4\xD7',
            b'\x00\x40\x23\x44\xA4\x44\xA4\x44\x77',
            b'\x00\x40\x23\xA4\x44\xA4\x44\xA4\xD7',
            b'\x00\x40\x23\x44\xA4\x44\xA4\x44\x77',
            b'\x00\x40\x23\xA4\x44\xA4\x44\xA3\xD6',
            b'\x00\x40\x23\xA4\xA4\x92\xA4\xA4\x85',
            b'\x00\x40\x23\xA4\xA4\xA4\xE0\xA4\xD3',
            b'\x00\x40\x23\xE4\xA4\xE4\xA4\xE4\x57',
            b'\x00\x40\x23\xA4\xE4\xA4\xE4\xA4\x17',
            b'\x00\x40\x23\xE4\xA4\xE4\xA4\xE4\x57',
            b'\x00\x40\x23\xA4\xE4\xA4\xE4\xA4\x17',
            b'\x00\x40\x23\xE4\xA4\xE4\xA4\xE4\x57',
            b'\x00\x40\x23\xA4\xE4\xA4\xE4\xA3\x16',
            b'\x00\x40\x23\xA4\xA4\x92\xA4\xA4\x85',
            b'\x00\x40\x23\xA4\xA4\xA4\xC0\xA4\xB3',
            b'\x00\x40\x23\xC4\xA4\xC4\xA4\xC4\xF7',
            b'\x00\x40\x23\xA4\xC4\xA4\xC4\xA4\xD7',
            b'\x00\x40\x23\xC4\xA4\xC4\xA4\xC4\xF7',
            b'\x00\x40\x23\xA4\xC4\xA4\xC4\xA4\xD7',
            b'\x00\x40\x23\xC4\xA4\xC4\xA4\xC4\xF7',
            b'\x00\x40\x23\xA4\xC4\xA4\xC4\xA3\xD6',
            b'\x00\x40\x23\xA4\xA4\xA4\xA4\xA4\x97',
            b'\x00\x40\x23\xA4\xA4\xA4\xA4\xA4\x97',
            b'\x00\x40\x23\x64\xA4\xA4\xA4\xA4\x57',
            b'\x00\x40\x23\xA4\xA4\xA4\xA4\xA4\x97'
        )

        for data, reference in zip(f, reference_message):
            self.assertEqual(data, reference)
