import base64
import json
import os
import tempfile

import mock

from tests.unit import base
from chirp import CHIRP_VERSION
from chirp import chirp_common
from chirp import errors


class TestUtilityFunctions(base.BaseTest):
    def test_parse_freq_whole(self):
        self.assertEqual(chirp_common.parse_freq("146.520000"), 146520000)
        self.assertEqual(chirp_common.parse_freq("146.5200"), 146520000)
        self.assertEqual(chirp_common.parse_freq("146.52"), 146520000)
        self.assertEqual(chirp_common.parse_freq("146"), 146000000)
        self.assertEqual(chirp_common.parse_freq("1250"), 1250000000)
        self.assertEqual(chirp_common.parse_freq("123456789"),
                         123456789000000)

    def test_parse_freq_decimal(self):
        self.assertEqual(chirp_common.parse_freq("1.0"), 1000000)
        self.assertEqual(chirp_common.parse_freq("1.000000"), 1000000)
        self.assertEqual(chirp_common.parse_freq("1.1"), 1100000)
        self.assertEqual(chirp_common.parse_freq("1.100"), 1100000)
        self.assertEqual(chirp_common.parse_freq("0.6"), 600000)
        self.assertEqual(chirp_common.parse_freq("0.600"), 600000)
        self.assertEqual(chirp_common.parse_freq("0.060"), 60000)
        self.assertEqual(chirp_common.parse_freq(".6"), 600000)

    def test_parse_freq_whitespace(self):
        self.assertEqual(chirp_common.parse_freq("1  "), 1000000)
        self.assertEqual(chirp_common.parse_freq("   1"), 1000000)
        self.assertEqual(chirp_common.parse_freq("   1  "), 1000000)

        self.assertEqual(chirp_common.parse_freq("1.0  "), 1000000)
        self.assertEqual(chirp_common.parse_freq("   1.0"), 1000000)
        self.assertEqual(chirp_common.parse_freq("   1.0  "), 1000000)
        self.assertEqual(chirp_common.parse_freq(""), 0)
        self.assertEqual(chirp_common.parse_freq(" "), 0)

    def test_parse_freq_bad(self):
        self.assertRaises(ValueError, chirp_common.parse_freq, "a")
        self.assertRaises(ValueError, chirp_common.parse_freq, "1.a")
        self.assertRaises(ValueError, chirp_common.parse_freq, "a.b")
        self.assertRaises(ValueError, chirp_common.parse_freq,
                          "1.0000001")

    def test_format_freq(self):
        self.assertEqual(chirp_common.format_freq(146520000), "146.520000")
        self.assertEqual(chirp_common.format_freq(54000000), "54.000000")
        self.assertEqual(chirp_common.format_freq(1800000), "1.800000")
        self.assertEqual(chirp_common.format_freq(1), "0.000001")
        self.assertEqual(chirp_common.format_freq(1250000000), "1250.000000")

    @mock.patch('chirp.CHIRP_VERSION', new='daily-20151021')
    def test_compare_version_to_current(self):
        self.assertTrue(chirp_common.is_version_newer('daily-20180101'))
        self.assertFalse(chirp_common.is_version_newer('daily-20140101'))
        self.assertFalse(chirp_common.is_version_newer('0.3.0'))
        self.assertFalse(chirp_common.is_version_newer('0.3.0dev'))

    @mock.patch('chirp.CHIRP_VERSION', new='0.3.0dev')
    def test_compare_version_to_current_dev(self):
        self.assertTrue(chirp_common.is_version_newer('daily-20180101'))

    def test_from_Hz(self):
        # FIXME: These are wrong! Adding them here purely to test the
        # python3 conversion, but they should be fixed.
        self.assertEqual(140, chirp_common.from_GHz(14000000001))
        self.assertEqual(140, chirp_common.from_MHz(14000001))
        self.assertEqual(140, chirp_common.from_kHz(14001))


class TestSplitTone(base.BaseTest):
    def _test_split_tone_decode(self, tx, rx, **vals):
        mem = chirp_common.Memory()
        chirp_common.split_tone_decode(mem, tx, rx)
        for key, value in list(vals.items()):
            self.assertEqual(getattr(mem, key), value)

    def test_split_tone_decode_none(self):
        self._test_split_tone_decode((None, None, None),
                                     (None, None, None),
                                     tmode='')

    def test_split_tone_decode_tone(self):
        self._test_split_tone_decode(('Tone', 100.0, None),
                                     ('', 0, None),
                                     tmode='Tone',
                                     rtone=100.0)

    def test_split_tone_decode_tsql(self):
        self._test_split_tone_decode(('Tone', 100.0, None),
                                     ('Tone', 100.0, None),
                                     tmode='TSQL',
                                     ctone=100.0)

    def test_split_tone_decode_dtcs(self):
        self._test_split_tone_decode(('DTCS', 23, None),
                                     ('DTCS', 23, None),
                                     tmode='DTCS',
                                     dtcs=23)

    def test_split_tone_decode_cross_tone_tone(self):
        self._test_split_tone_decode(('Tone', 100.0, None),
                                     ('Tone', 123.0, None),
                                     tmode='Cross',
                                     cross_mode='Tone->Tone',
                                     rtone=100.0,
                                     ctone=123.0)

    def test_split_tone_decode_cross_tone_dtcs(self):
        self._test_split_tone_decode(('Tone', 100.0, None),
                                     ('DTCS', 32, 'R'),
                                     tmode='Cross',
                                     cross_mode='Tone->DTCS',
                                     rtone=100.0,
                                     rx_dtcs=32,
                                     dtcs_polarity='NR')

    def test_split_tone_decode_cross_dtcs_tone(self):
        self._test_split_tone_decode(('DTCS', 32, 'R'),
                                     ('Tone', 100.0, None),
                                     tmode='Cross',
                                     cross_mode='DTCS->Tone',
                                     ctone=100.0,
                                     dtcs=32,
                                     dtcs_polarity='RN')

    def test_split_tone_decode_cross_dtcs_dtcs(self):
        self._test_split_tone_decode(('DTCS', 32, 'R'),
                                     ('DTCS', 25, 'R'),
                                     tmode='Cross',
                                     cross_mode='DTCS->DTCS',
                                     dtcs=32,
                                     rx_dtcs=25,
                                     dtcs_polarity='RR')

    def test_split_tone_decode_cross_none_dtcs(self):
        self._test_split_tone_decode((None, None, None),
                                     ('DTCS', 25, 'R'),
                                     tmode='Cross',
                                     cross_mode='->DTCS',
                                     rx_dtcs=25,
                                     dtcs_polarity='NR')

    def test_split_tone_decode_cross_none_tone(self):
        self._test_split_tone_decode((None, None, None),
                                     ('Tone', 100.0, None),
                                     tmode='Cross',
                                     cross_mode='->Tone',
                                     ctone=100.0)

    def _set_mem(self, **vals):
        mem = chirp_common.Memory()
        for key, value in list(vals.items()):
            setattr(mem, key, value)
        return chirp_common.split_tone_encode(mem)

    def split_tone_encode_test_none(self):
        self.assertEqual(self._set_mem(tmode=''),
                         (('', None, None),
                          ('', None, None)))

    def split_tone_encode_test_tone(self):
        self.assertEqual(self._set_mem(tmode='Tone', rtone=100.0),
                         (('Tone', 100.0, None),
                          ('', None, None)))

    def split_tone_encode_test_tsql(self):
        self.assertEqual(self._set_mem(tmode='TSQL', ctone=100.0),
                         (('Tone', 100.0, None),
                          ('Tone', 100.0, None)))

    def split_tone_encode_test_dtcs(self):
        self.assertEqual(self._set_mem(tmode='DTCS', dtcs=23,
                                       dtcs_polarity='RN'),
                         (('DTCS', 23, 'R'),
                          ('DTCS', 23, 'N')))

    def split_tone_encode_test_cross_tone_tone(self):
        self.assertEqual(self._set_mem(tmode='Cross', cross_mode='Tone->Tone',
                                       rtone=100.0, ctone=123.0),
                         (('Tone', 100.0, None),
                          ('Tone', 123.0, None)))

    def split_tone_encode_test_cross_tone_dtcs(self):
        self.assertEqual(self._set_mem(tmode='Cross', cross_mode='Tone->DTCS',
                                       rtone=100.0, rx_dtcs=25),
                         (('Tone', 100.0, None),
                          ('DTCS', 25, 'N')))

    def split_tone_encode_test_cross_dtcs_tone(self):
        self.assertEqual(self._set_mem(tmode='Cross', cross_mode='DTCS->Tone',
                                       ctone=100.0, dtcs=25),
                         (('DTCS', 25, 'N'),
                          ('Tone', 100.0, None)))

    def split_tone_encode_test_cross_none_dtcs(self):
        self.assertEqual(self._set_mem(tmode='Cross', cross_mode='->DTCS',
                                       rx_dtcs=25),
                         (('', None, None),
                          ('DTCS', 25, 'N')))

    def split_tone_encode_test_cross_none_tone(self):
        self.assertEqual(self._set_mem(tmode='Cross', cross_mode='->Tone',
                                       ctone=100.0),
                         (('', None, None),
                          ('Tone', 100.0, None)))


class TestStepFunctions(base.BaseTest):
    _625 = [145856250,
            445856250,
            862731250,
            146118750,
            ]
    _125 = [145862500,
            445862500,
            862737500,
            ]
    _005 = [145005000,
            445005000,
            850005000,
            ]
    _025 = [145002500,
            445002500,
            850002500,
            ]

    def test_is_fractional_step(self):
        for freq in self._125 + self._625:
            print(freq)
            self.assertTrue(chirp_common.is_fractional_step(freq))

    def test_is_6_25(self):
        for freq in self._625:
            self.assertTrue(chirp_common.is_6_25(freq))

    def test_is_12_5(self):
        for freq in self._125:
            self.assertTrue(chirp_common.is_12_5(freq))

    def test_is_5_0(self):
        for freq in self._005:
            self.assertTrue(chirp_common.is_5_0(freq))

    def test_is_2_5(self):
        for freq in self._025:
            self.assertTrue(chirp_common.is_2_5(freq))

    def test_required_step(self):
        steps = {2.5: self._025,
                 5.0: self._005,
                 6.25: self._625,
                 12.5: self._125,
                 }
        for step, freqs in list(steps.items()):
            for freq in freqs:
                self.assertEqual(step, chirp_common.required_step(freq))

    def test_required_step_fail(self):
        self.assertRaises(errors.InvalidDataError,
                          chirp_common.required_step,
                          146520500)

    def test_fix_rounded_step_250(self):
        self.assertEqual(146106250,
                         chirp_common.fix_rounded_step(146106000))

    def test_fix_rounded_step_500(self):
        self.assertEqual(146112500,
                         chirp_common.fix_rounded_step(146112000))

    def test_fix_rounded_step_750(self):
        self.assertEqual(146118750,
                         chirp_common.fix_rounded_step(146118000))


class TestImageMetadata(base.BaseTest):
    def test_make_metadata(self):
        class TestRadio(chirp_common.FileBackedRadio):
            VENDOR = 'Dan'
            MODEL = 'Foomaster 9000'
            VARIANT = 'R'

        raw_metadata = TestRadio._make_metadata()
        metadata = json.loads(base64.b64decode(raw_metadata).decode())
        expected = {
            'vendor': 'Dan',
            'model': 'Foomaster 9000',
            'variant': 'R',
            'rclass': 'TestRadio',
            'chirp_version': CHIRP_VERSION,
        }
        self.assertEqual(expected, metadata)

    def test_strip_metadata(self):
        class TestRadio(chirp_common.FileBackedRadio):
            VENDOR = 'Dan'
            MODEL = 'Foomaster 9000'
            VARIANT = 'R'

        raw_metadata = TestRadio._make_metadata()
        raw_data = (b'foooooooooooooooooooooo' + TestRadio.MAGIC +
                    TestRadio._make_metadata())
        data, metadata = chirp_common.FileBackedRadio._strip_metadata(raw_data)
        self.assertEqual(b'foooooooooooooooooooooo', data)
        expected = {
            'vendor': 'Dan',
            'model': 'Foomaster 9000',
            'variant': 'R',
            'rclass': 'TestRadio',
            'chirp_version': CHIRP_VERSION,
        }
        self.assertEqual(expected, metadata)

    def test_load_mmap_no_metadata(self):
        f = tempfile.NamedTemporaryFile()
        f.write(b'thisisrawdata')
        f.flush()

        with mock.patch('chirp.memmap.MemoryMap') as mock_mmap:
            chirp_common.FileBackedRadio(None).load_mmap(f.name)
            mock_mmap.assert_called_once_with(b'thisisrawdata')

    def test_load_mmap_bad_metadata(self):
        f = tempfile.NamedTemporaryFile()
        f.write(b'thisisrawdata')
        f.write(chirp_common.FileBackedRadio.MAGIC + b'bad')
        f.flush()

        with mock.patch('chirp.memmap.MemoryMap') as mock_mmap:
            chirp_common.FileBackedRadio(None).load_mmap(f.name)
            mock_mmap.assert_called_once_with(b'thisisrawdata')

    def test_save_mmap_includes_metadata(self):
        # Make sure that a file saved with a .img extension includes
        # the metadata blob
        class TestRadio(chirp_common.FileBackedRadio):
            VENDOR = 'Dan'
            MODEL = 'Foomaster 9000'
            VARIANT = 'R'

        with tempfile.NamedTemporaryFile(suffix='.Img') as f:
            fn = f.name
        r = TestRadio(None)
        r._mmap = mock.Mock()
        r._mmap.get_byte_compatible.return_value.get_packed.return_value = (
            b'thisisrawdata')
        r.save_mmap(fn)
        with open(fn, 'rb') as f:
            filedata = f.read()
        os.remove(fn)
        data, metadata = chirp_common.FileBackedRadio._strip_metadata(filedata)
        self.assertEqual(b'thisisrawdata', data)
        expected = {
            'vendor': 'Dan',
            'model': 'Foomaster 9000',
            'variant': 'R',
            'rclass': 'TestRadio',
            'chirp_version': CHIRP_VERSION,
        }
        self.assertEqual(expected, metadata)

    def test_save_mmap_no_metadata_not_img_file(self):
        # Make sure that if we save without a .img extension we do
        # not include the metadata blob
        class TestRadio(chirp_common.FileBackedRadio):
            VENDOR = 'Dan'
            MODEL = 'Foomaster 9000'
            VARIANT = 'R'

        with tempfile.NamedTemporaryFile(suffix='.txt') as f:
            fn = f.name
        r = TestRadio(None)
        r._mmap = mock.Mock()
        r._mmap.get_byte_compatible.return_value.get_packed.return_value = (
            b'thisisrawdata')
        r.save_mmap(fn)
        with open(fn, 'rb') as f:
            filedata = f.read()
        os.remove(fn)
        data, metadata = chirp_common.FileBackedRadio._strip_metadata(filedata)
        self.assertEqual(b'thisisrawdata', data)
        self.assertEqual({}, metadata)

    def test_load_mmap_saves_metadata_on_radio(self):
        class TestRadio(chirp_common.FileBackedRadio):
            VENDOR = 'Dan'
            MODEL = 'Foomaster 9000'
            VARIANT = 'R'

        with tempfile.NamedTemporaryFile(suffix='.img') as f:
            fn = f.name
        r = TestRadio(None)
        r._mmap = mock.Mock()
        r._mmap.get_byte_compatible.return_value.get_packed.return_value = (
            b'thisisrawdata')
        r.save_mmap(fn)

        newr = TestRadio(None)
        newr.load_mmap(fn)
        expected = {
            'vendor': 'Dan',
            'model': 'Foomaster 9000',
            'variant': 'R',
            'rclass': 'TestRadio',
            'chirp_version': CHIRP_VERSION,
        }
        self.assertEqual(expected, newr.metadata)
