"""Pruebas de detect.py: lo que dicen los apuntes sobre cada método de detección."""
import random
import unittest
import zlib

import detect


class Methods(unittest.TestCase):
    def test_checksum_known_value(self):
        # Ejemplo de los apuntes: 0x4500 + 0x0054 = 0x4554; complemento = 0xBAAB.
        self.assertEqual(detect.internet_checksum(bytes.fromhex("45000054")), 0xBAAB)

    def test_checksum_carry_wraps_around(self):
        # 0xFFFF + 0x0001 = 0x10000, que con acarreo circular es 0x0001.
        self.assertEqual(detect.internet_checksum(bytes.fromhex("ffff0001")), 0xFFFE)

    def test_parity_misses_even_errors(self):
        data = bytes(range(64))
        self.assertEqual(detect.parity(data), detect.parity(detect.flip_bits(data, [3, 77])))
        self.assertNotEqual(detect.parity(data), detect.parity(detect.flip_bits(data, [3])))


class Comparison(unittest.TestCase):
    TRIALS = 3000

    def misses(self, damage):
        return detect.undetected(damage, self.TRIALS, random.Random(7))

    def test_one_bit_is_detected_by_everyone(self):
        self.assertEqual(self.misses(detect.one_bit),
                         {"paridad": 0, "checksum": 0, "crc32": 0})

    def test_two_bits_escape_parity_always_and_checksum_sometimes(self):
        m = self.misses(detect.two_bits)
        self.assertEqual(m["paridad"], self.TRIALS)
        # Pasa si caen en la misma columna de 16 bits y en sentidos opuestos: cerca de 1/32.
        self.assertGreater(m["checksum"], self.TRIALS / 64)
        self.assertLess(m["checksum"], self.TRIALS / 16)
        self.assertEqual(m["crc32"], 0)

    def test_swapped_words_only_caught_by_crc(self):
        m = self.misses(detect.swap_words)
        self.assertEqual(m["paridad"], self.TRIALS)
        self.assertEqual(m["checksum"], self.TRIALS)
        self.assertEqual(m["crc32"], 0)

    def test_crc32_catches_all_bursts_up_to_32_bits(self):
        self.assertEqual(self.misses(detect.burst(32))["crc32"], 0)
        self.assertEqual(self.misses(detect.burst(16))["crc32"], 0)

    def test_crc32_catches_every_two_bit_error_in_a_short_frame(self):
        data = bytes(range(32))
        base = zlib.crc32(data)
        for i in range(0, len(data) * 8):
            for j in range(i + 1, len(data) * 8):
                self.assertNotEqual(zlib.crc32(detect.flip_bits(data, [i, j])), base)


if __name__ == "__main__":
    unittest.main()
