import logging

import pytest

from chirp import chirp_common
from chirp import errors
from tests import base

LOG = logging.getLogger(__name__)


class TestCaseBruteForce(base.DriverTest):
    def set_and_compare(self, m, **kwargs):
        msgs = self.radio.validate_memory(m)
        if msgs:
            # If the radio correctly refuses memories it can't
            # store, don't fail
            return

        self.radio.set_memory(chirp_common.FrozenMemory(m))
        ret_m = self.radio.get_memory(m.number)

        # Damned Baofeng radios don't seem to properly store
        # shift and direction, so be gracious here
        if m.duplex == "split" and ret_m.duplex in ["-", "+"]:
            ret_m.offset = ret_m.freq + \
                (ret_m.offset * int(ret_m.duplex + "1"))
            ret_m.duplex = "split"

        self.assertEqualMem(m, ret_m, **kwargs)

    def test_tone(self):
        m = self.get_mem()
        for tone in chirp_common.TONES:
            for tmode in self.rf.valid_tmodes:
                if tmode not in chirp_common.TONE_MODES:
                    continue
                elif tmode in ["DTCS", "DTCS-R", "Cross"]:
                    continue  # We'll test DCS and Cross tones separately

                m.tmode = tmode
                if tmode == "":
                    pass
                elif tmode == "Tone":
                    m.rtone = tone
                elif tmode in ["TSQL", "TSQL-R"]:
                    if self.rf.has_ctone:
                        m.ctone = tone
                    else:
                        m.rtone = tone
                else:
                    self.fail("Unknown tone mode `%s'" % tmode)

                try:
                    self.set_and_compare(m)
                except errors.UnsupportedToneError:
                    # If a radio doesn't support a particular tone value,
                    # don't punish it
                    pass

    @base.requires_feature('has_dtcs')
    def test_dtcs(self):
        m = self.get_mem()
        m.tmode = "DTCS"
        for code in self.rf.valid_dtcs_codes:
            m.dtcs = code
            self.set_and_compare(m)

        if not self.rf.has_dtcs_polarity:
            return

        for pol in self.rf.valid_dtcs_pols:
            m.dtcs_polarity = pol
            self.set_and_compare(m)

    @base.requires_feature('has_cross')
    def test_cross(self):
        m = self.get_mem()
        m.tmode = "Cross"
        # No fair asking a radio to detect two identical tones as Cross instead
        # of TSQL
        m.rtone = 100.0
        m.ctone = 107.2
        m.dtcs = 506
        m.rx_dtcs = 516
        for cross_mode in self.rf.valid_cross_modes:
            m.cross_mode = cross_mode
            self.set_and_compare(m)

    @base.requires_feature('valid_duplexes')
    def test_duplex(self):
        m = self.get_mem()
        if 'duplex' in m.immutable:
            self.skipTest('Test memory has immutable duplex')
        for duplex in self.rf.valid_duplexes:
            assert duplex in ["", "-", "+", "split", "off"]
            if duplex == 'split':
                self.assertTrue(self.rf.can_odd_split,
                                'Radio supports split but does not set '
                                'can_odd_split=True in features')
                m.offset = self.rf.valid_bands[0][1] - 100000
            else:
                m.offset = chirp_common.to_kHz(int(m.tuning_step) * 2)
            m.duplex = duplex
            # Ignore the offset because we do some fudging on this and we
            # don't necessarily know the best step to use. What we care about
            # is duplex here.
            self.set_and_compare(m, ignore=['offset'])

        if self.rf.can_odd_split:
            self.assertIn('split', self.rf.valid_duplexes,
                          'Radio claims can_odd_split but split not in '
                          'valid_duplexes')

    @base.requires_feature('valid_skips')
    def test_skip(self):
        mem = self.get_mem()
        lo, hi = self.rf.memory_bounds
        # Walk through several memories, specifically across 8 and 16,
        # as many radio use bitfields for skip flags.
        for i in range(max(5, lo), min(25, hi)):
            # Walk through the skip flags twice each, to make sure we can
            # toggle them on..off..on
            for skip in self.rf.valid_skips * 2:
                m = mem.dupe()
                if 'empty' not in m.immutable:
                    m.empty = False
                m.number = i
                m.skip = skip
                self.set_and_compare(m)
                # Delete the memory each time because some radios are
                # dynamically allocated and will run out of space here.
                self.radio.erase_memory(m.number)

    @base.requires_feature('valid_modes')
    def test_mode(self):
        m = self.get_mem()
        if 'mode' in m.immutable:
            self.skipTest('Test memory has immutable duplex')

        def ensure_urcall(call):
            lst = self.radio.get_urcall_list()
            lst[0] = call
            self.radio.set_urcall_list(lst)

        def ensure_rptcall(call):
            lst = self.radio.get_repeater_call_list()
            lst[0] = call
            self.radio.set_repeater_call_list(lst)

        def freq_is_ok(freq):
            for lo, hi in self.rf.valid_bands:
                if freq > lo and freq < hi:
                    return True
            return False

        successes = 0
        for mode in self.rf.valid_modes:
            self.assertIn(mode, chirp_common.MODES,
                          'Radio exposes non-standard mode')
            tmp = m.dupe()
            if mode == "DV" and \
                isinstance(self.radio,
                           chirp_common.IcomDstarSupport):
                tmp = chirp_common.DVMemory()
                try:
                    ensure_urcall(tmp.dv_urcall)
                    ensure_rptcall(tmp.dv_rpt1call)
                    ensure_rptcall(tmp.dv_rpt2call)
                except IndexError:
                    if self.rf.requires_call_lists:
                        raise
                    else:
                        # This radio may not do call lists at all,
                        # so let it slide
                        pass
            if mode == "FM" and freq_is_ok(tmp.freq + 100000000):
                # Some radios don't support FM below approximately 30MHz,
                # so jump up by 100MHz, if they support that
                tmp.freq += 100000000

            tmp.mode = mode

            if self.rf.validate_memory(tmp):
                # A result (of error messages) from validate means the radio
                # thinks this is invalid, so don't fail the test
                LOG.warning('Failed to validate %s: %s' % (
                    tmp, self.rf.validate_memory(tmp)))
                continue

            # Ignore tuning_step because changing modes may cause step changes
            # in some radios
            self.set_and_compare(tmp, ignore=['tuning_step'])
            successes += 1

    def test_validate_all(self):
        lo, hi = self.rf.memory_bounds
        for i in range(lo, hi + 1):
            m1 = self.radio.get_memory(i)
            if m1.empty:
                continue
            errs, warns = chirp_common.split_validation_msgs(
                self.radio.validate_memory(m1))
            self.assertEqual([], errs,
                             ('Radio has validation errors for memory %i '
                              'stored in sample image: %s') % (i, m1))

    @base.requires_feature('has_nostep_tuning', equal=False)
    def test_validate_all_steps(self):
        lo, hi = self.rf.memory_bounds
        for i in range(lo, hi + 1):
            m1 = self.radio.get_memory(i)
            try:
                chirp_common.required_step(m1.freq, self.rf.valid_tuning_steps)
            except Exception as e:
                self.fail('Memory %i: %s' % (i, e))

    @pytest.mark.skip(reason='not ready yet')
    def test_get_set_all(self):
        lo, hi = self.rf.memory_bounds
        for i in range(lo, hi + 1):
            m1 = self.radio.get_memory(i)
            self.radio.set_memory(m1)
            m2 = self.radio.get_memory(i)
            self.assertEqualMem(m1, m2)
