import unittest
import onetimepad as OT


class TestMod25(unittest.TestCase):
    msg = ('HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFA'
           'WOODCHUCKCOULDCHUCKWOOD' * 50)
    _padsize = 2000
    PADLINES = (1, 4, 7, 10, 13, 16, 19, 22, 25)
    TEXTLINES = (2, 5, 8, 11, 14, 17, 20, 23, 26)
    CIPHERLINES = (3, 6, 9, 12, 15, 18, 21, 24, 27)

    TEST_PADLINE = 'HUCHUGGHOBUXADDHOHPGQBKXQKAOLRL'
    TEST_TEXTLINE = 'THISCAKEISUNSATISFACTORYTOMEYES'
    TEST_CIPHERLINE = 'BCMAXHRNXUPLTEXRGOQKKQBWKYNTKWD'

    TEST_MSG = \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \
        'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOO'

    TEST_CIPHER = \
        'FMHZRGHRITAMHVAOLBANWMNYPBYDCFCAVXAEEKXUTYXLUCYQVYPWZFSKYI' \
        'LWKCHZSXLFCMZIEFHXKVUXVNBIQVSNYPTFVIZHSVTDCKWUSZHEHUPLZSUY' \
        'BTPSMEISGTYDWCXVUCPGHHKXDOUHGCVDTDUQVKFDRDCHFIKTNFGDFODCQS' \
        'RIWGZBLPTCSXLMPQODYLHQNGWNSCTHNDOZEUXWCEQAZMRGZIQUTUYSKOGW' \
        'YGYPEDNUZDWBEPUHLWWBHLGADYTCNGEUFKPLQSAMMXCTXWQEOYBNKQAHBV' \
        'PDKTMQSHDFCFBCVKXVTRSXRTBHAGKNLFASNVNENHHRNGCKXALMIBNZKOTL' \
        'BZMWGSNMTLDNPXKVQZZUAZCSPWFNRKAMTNMCPHWGLGBVSNFDXSKVUFBZBW' \
        'WICBUPCWZDUXCZTXHKQBMQNTBHAAGMBPKUIDTHITUEWEPPMACPXERXRYND' \
        'RWQSBQQMSQRVWZMAQQKUGHLELKFLKHGNFFDHIWUZPDIMIOELSIHMUIZQCW' \
        'KLPZQIFAXNDRRIIIHOYBGSCIYBIVSRTECEOZSBLYTNCXGQXEWMGIXHKBIL' \
        'TOKWHGABPMXNAHEWFKIMOQPHVHSYZABWLMLSSDEHQIBNEMEMHCWOUBIIZS' \
        'XPFWXMQNOTEANYGQBVMZAXNPAMWSUOGFAFFYUTKDZYHFTPYGOMDHESKOZI' \
        'IPDXAMSAQOTGAQKYPYWWUYGHSBVODAHDSKDVPMLWRCXQXGAXQZBFNKXFKU' \
        'WUDRWNRKECIQIUEEMQNCAZDDDOQOEGUVFVCTKLTISNOQILEKUYXCYZNHHZ' \
        'OFHCSLOPPQAFLHHDZANGTXNQSDZSAFLLSGWULVHIDERMTZZHTDHTZPHEFN' \
        'ORDEIYOVTTAWVTOOXSLZBOPMRCCKSMHQOVFERCOVUMWXFSNEMFALNWGRQC' \
        'NLWELLXNCHZGXQEUGEVNCWMHRNGUTBKTVEXGASBRCEBSFITOEFSNIBTLME' \
        'MERMOFVBNTFCTTWEGAIGRZQBBFOKOPXNCWXFBK'

    TEST_PAD = """\
    XXLNWDZOAYXBEGFCGADYHHKQUYOAULZQMRZHQVTRLDUARODERVGBWVVVKECHNQNWKUCLZBWU


    KUDWNGFTSEGFFSKSVEKZUMLTOSLIZZTFXODBZZMACDFUTESFRBAPYYVTTOCKQBSSTDGPILKE


    YHSTKXTTGVBAIIZXCUPHICYICDGOBOIUZUEYCMLHPNHXUEKCBWTMKYBKHZLNKTETDXIHYBHF


    WBOSEXMRLZVHNZSSQSBCKAERRITRBAZWGVZNTGDSIVIZEMBKWDOOBDWIDCZIUHVTKVTSGFDT


    NRGPNGRNKEVLZVYOAYTUWCDTOLGEQDBSHVRMMYYQIEZWKWZVCPGIAGKPNZEGTLPKMPEILQAC


    MIPKMYCFMVZKUTVKIPXBKGLFATSDCMYLPYLSTPBARVELNSOUFPZMUTRIRNZLYMDITNXMKLGE


    QXYRYEAOHGETEQMKTUMARPYMPKONUKYZIHTFGNHIKVOLTLRPMPNFSDHWQGVHBNDCWZCLUHQW


    GIFBFZKZOFZRRYCBOSBWSNVFXXPSAGOUOXDNBNSOZADYYSKWQUTYNCDNGVLSZNDBCTSIYOUX


    NNUGLZNKNDSYGRUCXTKLBIMXZMMZAEHVRFYMBFKVDPAEHOYCBXKADZOTRRMULOPAIKCIHKFY


    BQKKMEXUPLMTKGFIMPMTDVRZEBFEFARDEVQADVKIVNBHNZLEAAGLFIKXHTQWXBPNLXZHFUDZ


    XYLLVFETIDCYAXGTIHUFUSFMMWTLKZARVQOFGEBKIGWHFFFFKTHPQOMVAVILFLWMRLWPBWVW


    PFKSLFFWKYQVPHVPQTTVFRLQXHFMGVXVHTNSVZQSETKHXAPPSLHAIAVXWGDFVKOBQLEWPAZY


    WELQRIFCGSOVFSLYXMSETCTROLNKMDWZSGKREFEPEHCOKSMRTNCDSTHCSQKMKCBYEWZSQHPK


    UNWWUBKICDYYOSIZWKWRLGGIMYWKMDXOUKYHCUYCACKSFRWWXADQUZTCNYEYCSQEBTCZMSCV


    NTGCDGFUUCTQWINV........................................................

    """

    def setUp(self):
        self.m25 = OT.Mod25()

    def test_mk_as_alpha_excludes_j(self):
        self.assertTrue('J' not in self.m25._as_alpha.values())

    def test_mk_as_alpha_dict_has_25_members(self):
        self.assertEqual(25, len(self.m25._as_alpha.items()))

    def test_creates_pad_of_desired_length(self):
        for width in (72, 33, 99, 111):
            pad = self.m25.create_pad(self._padsize, width=width)
            lines = [line.strip('.') for line in pad.split()]
            actual = len(''.join(lines))
            self.assertEqual(self._padsize, len(''.join(lines)),
                             'pad of {0} chars created at width '
                             '{1}, actual={2}'.format(self._padsize,
                                                      width, actual))

    def test_is_padline(self):
        for lineno in self.PADLINES:
            self.assertTrue(self.m25._is_padline(lineno),
                            'line {0} is padline'.format(lineno))
        for lineno in self.TEXTLINES:
            self.assertFalse(self.m25._is_padline(lineno),
                             'line {0} is not padline'.format(lineno))
        for lineno in self.CIPHERLINES:
            self.assertFalse(self.m25._is_padline(lineno),
                             'line {0} is not padline'.format(lineno))

    def test_is_textline(self):
        for lineno in self.TEXTLINES:
            self.assertTrue(self.m25._is_textline(lineno),
                            'line {0} is textline'.format(lineno))
        for lineno in self.PADLINES:
            self.assertFalse(self.m25._is_textline(lineno),
                             'line {0} is not textline'.format(lineno))
        for lineno in self.CIPHERLINES:
            self.assertFalse(self.m25._is_textline(lineno),
                             'line {0} is not textline'.format(lineno))

    def test_is_cipherline(self):
        for lineno in self.CIPHERLINES:
            self.assertTrue(self.m25._is_cipherline(lineno),
                            'line {0} is cipherline'.format(lineno))
        for lineno in self.PADLINES:
            self.assertFalse(self.m25._is_cipherline(lineno),
                             'line {0} is not cipherline'.format(lineno))
        for lineno in self.TEXTLINES:
            self.assertFalse(self.m25._is_cipherline(lineno),
                             'line {0} is not cipherline'.format(lineno))

    def test_make_cipherline_from_padline_and_textline(self):
        actual = \
            self.m25._cipherline_from_padline_and_textline(self.TEST_PADLINE,
                                                           self.TEST_TEXTLINE)
        self.assertEqual(self.TEST_CIPHERLINE, actual)

    def test_make_textline_from_cipherline_and_padline(self):
        actual = self.m25._textline_from_cipherline_and_padline(
                         self.TEST_CIPHERLINE, self.TEST_PADLINE)
        self.assertEqual(self.TEST_TEXTLINE, actual)

    def test_encode_equals_expected(self):
        ciphertext = OT.mod25encode(self.TEST_MSG, self.TEST_PAD)
        self.assertEqual(self.TEST_CIPHER, ciphertext)

    def test_decode_equals_expected(self):
        text = OT.mod25decode(self.TEST_CIPHER, self.TEST_PAD)
        self.assertEqual(self.TEST_MSG, text)


class TestOneTimePad(unittest.TestCase):

    def test_get_randint_on_nonexistent_randomness_file_fails_ioerror(self):
        OT._DEV_URANDOM['fp'] = None
        self.assertRaises(IOError, OT._get_randint,
                          25, randomness_file='/foo/bar/busted/borken',
                          fallback_to_fake=False)

    def test_get_randint_on_nonexistent_randomness_file_uses_fake(self):
        OT._DEV_URANDOM['fp'] = None
        random_int = OT._get_randint(32, randomness_file='/broke/as/joke')
        self.assertTrue(random_int in range(0, 255))


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