# Copyright 2011 Dan Smith <dsmith@danplanet.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Wouxun radios management module"""

import time
import logging
from chirp import util, chirp_common, bitwise, memmap, errors, directory
from chirp.settings import RadioSetting, RadioSettingGroup, \
                RadioSettingValueBoolean, RadioSettingValueList, \
                RadioSettingValueInteger, RadioSettingValueString, \
                RadioSettingValueFloat, RadioSettings
from chirp.drivers.wouxun_common import wipe_memory, do_download, do_upload

LOG = logging.getLogger(__name__)

FREQ_ENCODE_TABLE = [0x7, 0xa, 0x0, 0x9, 0xb, 0x2, 0xe, 0x1, 0x3, 0xf]


def encode_freq(freq):
    """Convert frequency (4 decimal digits) to Wouxun format (2 bytes)"""
    enc = 0
    div = 1000
    for i in range(0, 4):
        enc <<= 4
        enc |= FREQ_ENCODE_TABLE[int((freq/div) % 10)]
        div /= 10
    return enc


def decode_freq(data):
    """Convert from Wouxun format (2 bytes) to frequency (4 decimal digits)"""
    freq = 0
    shift = 12
    for i in range(0, 4):
        freq *= 10
        freq += FREQ_ENCODE_TABLE.index((data >> shift) & 0xf)
        shift -= 4
        # LOG.debug("data %04x freq %d shift %d" % (data, freq, shift))
    return freq


@directory.register
class KGUVD1PRadio(chirp_common.CloneModeRadio,
                   chirp_common.ExperimentalRadio):
    """Wouxun KG-UVD1P,UV2,UV3"""
    VENDOR = "Wouxun"
    MODEL = "KG-UVD1P"
    _model = b"KG669V"
    NEEDS_COMPAT_SERIAL = False

    _querymodel = (b"HiWOUXUN\x02", b"PROGUV6X\x02")

    CHARSET = list("0123456789") + \
        [chr(x + ord("A")) for x in range(0, 26)] + list("?+-")

    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
                    chirp_common.PowerLevel("Low", watts=1.00)]

    valid_freq = [(136000000, 175000000), (216000000, 520000000)]

    _MEM_FORMAT = """
        #seekto 0x0010;
        struct {
          lbcd rx_freq[4];
          lbcd tx_freq[4];
          ul16 rx_tone;
          ul16 tx_tone;
          u8 _3_unknown_1:4,
             bcl:1,
             _3_unknown_2:3;
          u8 splitdup:1,
             skip:1,
             power_high:1,
             iswide:1,
             _2_unknown_2:4;
          u8 unknown;
          u8 _0_unknown_1:3,
             iswidex:1,
             _0_unknown_2:4;
        } memory[199];

        #seekto 0x0842;
        u16 fm_presets_0[9];

        #seekto 0x0882;
        u16 fm_presets_1[9];

        #seekto 0x0970;
        struct {
            u16 vhf_rx_start;
            u16 vhf_rx_stop;
            u16 uhf_rx_start;
            u16 uhf_rx_stop;
            u16 vhf_tx_start;
            u16 vhf_tx_stop;
            u16 uhf_tx_start;
            u16 uhf_tx_stop;
        } freq_ranges;

        #seekto 0x0E00;
        struct {
          char welcome1[6];
          char welcome2[6];
          char single_band[6];
        } strings;

        #seekto 0x0E20;
        struct {
          u8 unknown_flag_01:6,
             vfo_b_ch_disp:2;
          u8 unknown_flag_02:5,
             vfo_a_fr_step:3;
          u8 unknown_flag_03:4,
             vfo_a_squelch:4;
          u8 unknown_flag_04:7,
             power_save:1;
          u8 unknown_flag_05:8;
          u8 unknown_flag_06:6,
             roger_beep:2;
          u8 unknown_flag_07:2,
             transmit_time_out:6;
          u8 unknown_flag_08:4,
             vox:4;
          u8 unknown_1[4];
          u8 unknown_flag_09:6,
             voice:2;
          u8 unknown_flag_10:7,
             beep:1;
          u8 unknown_flag_11:7,
             ani_id_enable:1;
          u8 unknown_2[2];
          u8 unknown_flag_12:5,
             vfo_b_fr_step:3;
          u8 unknown_3[1];
          u8 unknown_flag_13:3,
             ani_id_tx_delay:5;
          u8 unknown_4[1];
          u8 unknown_flag_14:6,
             ani_id_sidetone:2;
          u8 unknown_flag_15:4,
             tx_time_out_alert:4;
          u8 unknown_flag_16:6,
             vfo_a_ch_disp:2;
          u8 unknown_flag_15:6,
             scan_mode:2;
          u8 unknown_flag_16:7,
             kbd_lock:1;
          u8 unknown_flag_17:6,
             ponmsg:2;
          u8 unknown_flag_18:5,
             pf1_function:3;
          u8 unknown_5[1];
          u8 unknown_flag_19:7,
             auto_backlight:1;
          u8 unknown_flag_20:7,
             sos_ch:1;
          u8 unknown_6;
          u8 sd_available;
          u8 unknown_flag_21:7,
             auto_lock_kbd:1;
          u8 unknown_flag_22:4,
             vfo_b_squelch:4;
          u8 unknown_7[1];
          u8 unknown_flag_23:7,
             stopwatch:1;
          u8 vfo_a_cur_chan;
          u8 unknown_flag_24:7,
             dual_band_receive:1;
          u8 current_vfo:1,
             unknown_flag_26:7;
          u8 unknown_8[2];
          u8 mode_password[6];
          u8 reset_password[6];
          u8 ani_id_content[6];
          u8 unknown_flag_25:7,
             menu_available:1;
          u8 unknown_9[1];
          u8 priority_chan;
          u8 vfo_b_cur_chan;
        } settings;

        #seekto 0x1008;
        struct {
          u8 unknown[8];
          u8 name[6];
          u8 pad[2];
        } names[199];
    """

    @classmethod
    def get_prompts(cls):
        rp = chirp_common.RadioPrompts()
        rp.experimental = \
            ('This version of the Wouxun driver allows you to modify the '
             'frequency range settings of your radio. This has been tested '
             'and reports from other users indicate that it is a safe '
             'thing to do. However, modifications to this value may have '
             'unintended consequences, including damage to your device. '
             'You have been warned. Proceed at your own risk!')
        rp.pre_download = _(
            "1. Turn radio off.\n"
            "2. Connect cable to mic/spkr connector.\n"
            "3. Make sure connector is firmly connected.\n"
            "4. Turn radio on.\n"
            "5. Ensure that the radio is tuned to channel with no"
            " activity.\n"
            "6. Click OK to download image from device.\n")
        rp.pre_upload = _(
            "1. Turn radio off.\n"
            "2. Connect cable to mic/spkr connector.\n"
            "3. Make sure connector is firmly connected.\n"
            "4. Turn radio on.\n"
            "5. Ensure that the radio is tuned to channel with no"
            " activity.\n"
            "6. Click OK to upload image to device.\n")
        return rp

    @classmethod
    def _get_querymodel(cls):
        if isinstance(cls._querymodel, str):
            while True:
                yield cls._querymodel
        else:
            i = 0
            while True:
                yield cls._querymodel[i % len(cls._querymodel)]
                i += 1

    def _identify(self):
        """Do the original Wouxun identification dance"""
        query = self._get_querymodel()
        for _i in range(0, 10):
            self.pipe.write(next(query))
            resp = self.pipe.read(9)
            if len(resp) != 9:
                LOG.debug("Got:\n%s" % util.hexprint(resp))
                LOG.info("Retrying identification...")
                time.sleep(1)
                continue
            if resp[2:8] != self._model:
                raise Exception("I can't talk to this model (%s)" %
                                util.hexprint(resp))
            return
        if len(resp) == 0:
            raise Exception("Radio not responding")
        else:
            raise Exception("Unable to identify radio")

    def _start_transfer(self):
        """Tell the radio to go into transfer mode"""
        self.pipe.write(b"\x02\x06")
        time.sleep(0.05)
        ack = self.pipe.read(1)
        if ack != b"\x06":
            raise Exception("Radio refused transfer mode")

    def _download(self):
        """Talk to an original Wouxun and do a download"""
        try:
            self._identify()
            self._start_transfer()
            return do_download(self, 0x0000, 0x2000, 0x0040)
        except errors.RadioError:
            raise
        except Exception as e:
            raise errors.RadioError("Failed to communicate with radio: %s" % e)

    def _upload(self):
        """Talk to an original Wouxun and do an upload"""
        try:
            self._identify()
            self._start_transfer()
            return do_upload(self, 0x0000, 0x2000, 0x0010)
        except errors.RadioError:
            raise
        except Exception as e:
            raise errors.RadioError("Failed to communicate with radio: %s" % e)

    def sync_in(self):
        self._mmap = self._download()
        self.process_mmap()

    def sync_out(self):
        self._upload()

    def process_mmap(self):
        if len(self._mmap.get_packed()) != 8192:
            LOG.info("Fixing old-style Wouxun image")
            # Originally, CHIRP's Wouxun image had eight bytes of
            # static data, followed by the first memory at offset
            # 0x0008.  Between 0.1.11 and 0.1.12, this was fixed to 16
            # bytes of (whatever) followed by the first memory at
            # offset 0x0010, like the radio actually stores it.  So,
            # if we find one of those old ones, convert it to the new
            # format, padding 16 bytes of 0xFF in front.
            self._mmap = memmap.MemoryMap(
                    ("\xFF" * 16) + self._mmap.get_packed()[8:8184])
        self._memobj = bitwise.parse(self._MEM_FORMAT, self._mmap)

    def get_features(self):
        rf = chirp_common.RadioFeatures()
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
        rf.valid_cross_modes = [
                        "Tone->Tone",
                        "Tone->DTCS",
                        "DTCS->Tone",
                        "DTCS->",
                        "->Tone",
                        "->DTCS",
                        "DTCS->DTCS",
                    ]
        rf.valid_modes = ["FM", "NFM"]
        rf.valid_power_levels = self.POWER_LEVELS
        rf.valid_bands = self.valid_freq
        rf.valid_characters = "".join(self.CHARSET)
        rf.valid_name_length = 6
        rf.valid_duplexes = ["", "+", "-", "split", "off"]
        rf.valid_tuning_steps = [5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]
        rf.has_ctone = True
        rf.has_rx_dtcs = True
        rf.has_cross = True
        rf.has_tuning_step = False
        rf.has_bank = False
        rf.has_settings = True
        rf.memory_bounds = (1, 128)
        rf.can_odd_split = True
        return rf

    def get_settings(self):
        freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges")
        fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
        cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings")
        group = RadioSettings(cfg_s, freq_ranges, fm_preset)

        rs = RadioSetting("menu_available", "Menu Available",
                          RadioSettingValueBoolean(
                              self._memobj.settings.menu_available))
        cfg_s.append(rs)

        rs = RadioSetting("vhf_rx_start", "1st band RX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              50, 174, decode_freq(
                                  self._memobj.freq_ranges.vhf_rx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_rx_stop", "1st band RX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              50, 174, decode_freq(
                                  self._memobj.freq_ranges.vhf_rx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_rx_start", "2nd band RX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              136, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_rx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_rx_stop", "2nd band RX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              136, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_rx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_tx_start", "1st band TX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              50, 174, decode_freq(
                                  self._memobj.freq_ranges.vhf_tx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_tx_stop", "1st TX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              50, 174, decode_freq(
                                  self._memobj.freq_ranges.vhf_tx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_tx_start", "2nd band TX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              136, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_tx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_tx_stop", "2nd band TX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              136, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_tx_stop)))
        freq_ranges.append(rs)

        # tell the decoded ranges to UI
        freq_ranges = self._memobj.freq_ranges
        self.valid_freq = \
            [(decode_freq(freq_ranges.vhf_rx_start) * 1000000,
             (decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000),
             (decode_freq(freq_ranges.uhf_rx_start) * 1000000,
             (decode_freq(freq_ranges.uhf_rx_stop) + 1) * 1000000)]

        def _filter(name):
            filtered = ""
            for char in str(name):
                if char in chirp_common.CHARSET_ASCII:
                    filtered += char
                else:
                    filtered += " "
            return filtered

        # add some radio specific settings
        options = ["Off", "Welcome", "V bat"]
        rs = RadioSetting("ponmsg", "Poweron message",
                          RadioSettingValueList(
                              options, options[self._memobj.settings.ponmsg]))
        cfg_s.append(rs)
        rs = RadioSetting("strings.welcome1", "Power-On Message 1",
                          RadioSettingValueString(
                              0, 6, _filter(self._memobj.strings.welcome1)))
        cfg_s.append(rs)
        rs = RadioSetting("strings.welcome2", "Power-On Message 2",
                          RadioSettingValueString(
                              0, 6, _filter(self._memobj.strings.welcome2)))
        cfg_s.append(rs)
        rs = RadioSetting("strings.single_band", "Single Band Message",
                          RadioSettingValueString(
                              0, 6, _filter(self._memobj.strings.single_band)))
        cfg_s.append(rs)
        options = ["Channel", "ch/freq", "Name", "VFO"]
        rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_a_ch_disp]))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_b_ch_disp]))
        cfg_s.append(rs)
        options = ["5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
        rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_a_fr_step]))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_b_fr_step]))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
                          RadioSettingValueInteger(
                              0, 9, self._memobj.settings.vfo_a_squelch))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
                          RadioSettingValueInteger(
                              0, 9, self._memobj.settings.vfo_b_squelch))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
                          RadioSettingValueInteger(
                              1, 128, self._memobj.settings.vfo_a_cur_chan))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
                          RadioSettingValueInteger(
                              1, 128, self._memobj.settings.vfo_b_cur_chan))
        cfg_s.append(rs)
        rs = RadioSetting("priority_chan", "Priority channel",
                          RadioSettingValueInteger(
                              0, 199, self._memobj.settings.priority_chan))
        cfg_s.append(rs)
        rs = RadioSetting("power_save", "Power save",
                          RadioSettingValueBoolean(
                              self._memobj.settings.power_save))
        cfg_s.append(rs)
        options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
        rs = RadioSetting("pf1_function", "PF1 Function select",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.pf1_function]))
        cfg_s.append(rs)
        options = ["Off", "Begin", "End", "Both"]
        rs = RadioSetting("roger_beep", "Roger beep select",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.roger_beep]))
        cfg_s.append(rs)
        options = ["%s" % x for x in range(15, 615, 15)]
        transmit_time_out = options[self._memobj.settings.transmit_time_out]
        rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
                          RadioSettingValueList(
                              options, transmit_time_out))
        cfg_s.append(rs)
        rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
                          RadioSettingValueInteger(
                              0, 10, self._memobj.settings.tx_time_out_alert))
        cfg_s.append(rs)
        rs = RadioSetting("vox", "Vox",
                          RadioSettingValueInteger(
                              0, 10, self._memobj.settings.vox))
        cfg_s.append(rs)
        options = ["Off", "Chinese", "English"]
        rs = RadioSetting("voice", "Voice",
                          RadioSettingValueList(
                              options, options[self._memobj.settings.voice]))
        cfg_s.append(rs)
        rs = RadioSetting("beep", "Beep",
                          RadioSettingValueBoolean(
                              self._memobj.settings.beep))
        cfg_s.append(rs)
        rs = RadioSetting("ani_id_enable", "ANI id enable",
                          RadioSettingValueBoolean(
                              self._memobj.settings.ani_id_enable))
        cfg_s.append(rs)
        rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
                          RadioSettingValueInteger(
                              0, 30, self._memobj.settings.ani_id_tx_delay))
        cfg_s.append(rs)
        options = ["Off", "Key", "ANI", "Key+ANI"]
        rs = RadioSetting("ani_id_sidetone", "ANI id sidetone",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.ani_id_sidetone]))
        cfg_s.append(rs)
        options = ["Time", "Carrier", "Search"]
        rs = RadioSetting("scan_mode", "Scan mode",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.scan_mode]))
        cfg_s.append(rs)
        rs = RadioSetting("kbd_lock", "Keyboard lock",
                          RadioSettingValueBoolean(
                              self._memobj.settings.kbd_lock))
        cfg_s.append(rs)
        rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
                          RadioSettingValueBoolean(
                              self._memobj.settings.auto_lock_kbd))
        cfg_s.append(rs)
        rs = RadioSetting("auto_backlight", "Auto backlight",
                          RadioSettingValueBoolean(
                              self._memobj.settings.auto_backlight))
        cfg_s.append(rs)
        options = ["CH A", "CH B"]
        rs = RadioSetting("sos_ch", "SOS CH",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.sos_ch]))
        cfg_s.append(rs)
        rs = RadioSetting("stopwatch", "Stopwatch",
                          RadioSettingValueBoolean(
                              self._memobj.settings.stopwatch))
        cfg_s.append(rs)
        rs = RadioSetting("dual_band_receive", "Dual band receive",
                          RadioSettingValueBoolean(
                              self._memobj.settings.dual_band_receive))
        cfg_s.append(rs)
        options = ["VFO A", "VFO B"]
        rs = RadioSetting("current_vfo", "Current VFO",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.current_vfo]))
        cfg_s.append(rs)

        options = ["Dual", "Single"]
        rs = RadioSetting("sd_available", "Single/Dual Band",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.sd_available]))
        cfg_s.append(rs)

        _pwd = self._memobj.settings.mode_password
        rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
                          RadioSettingValueInteger(0, 9, _pwd[0]),
                          RadioSettingValueInteger(0, 9, _pwd[1]),
                          RadioSettingValueInteger(0, 9, _pwd[2]),
                          RadioSettingValueInteger(0, 9, _pwd[3]),
                          RadioSettingValueInteger(0, 9, _pwd[4]),
                          RadioSettingValueInteger(0, 9, _pwd[5]))
        cfg_s.append(rs)
        _pwd = self._memobj.settings.reset_password
        rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
                          RadioSettingValueInteger(0, 9, _pwd[0]),
                          RadioSettingValueInteger(0, 9, _pwd[1]),
                          RadioSettingValueInteger(0, 9, _pwd[2]),
                          RadioSettingValueInteger(0, 9, _pwd[3]),
                          RadioSettingValueInteger(0, 9, _pwd[4]),
                          RadioSettingValueInteger(0, 9, _pwd[5]))
        cfg_s.append(rs)

        dtmfchars = "0123456789 *#ABCD"
        _codeobj = self._memobj.settings.ani_id_content
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
        val = RadioSettingValueString(0, 6, _code, False)
        val.set_charset(dtmfchars)
        rs = RadioSetting("settings.ani_id_content", "PTT-ID Code", val)

        def apply_ani_id(setting, obj):
            value = []
            for j in range(0, 6):
                try:
                    value.append(dtmfchars.index(str(setting.value)[j]))
                except IndexError:
                    value.append(0xFF)
            obj.ani_id_content = value
        rs.set_apply_callback(apply_ani_id, self._memobj.settings)
        cfg_s.append(rs)

        for i in range(0, 9):
            if self._memobj.fm_presets_0[i] != 0xFFFF:
                used = True
                preset = self._memobj.fm_presets_0[i] / 10.0 + 76
            else:
                used = False
                preset = 76
            rs = RadioSetting("fm_presets_0_%1i" % i,
                              "Team 1 Location %i" % (i + 1),
                              RadioSettingValueBoolean(used),
                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
            fm_preset.append(rs)
        for i in range(0, 9):
            if self._memobj.fm_presets_1[i] != 0xFFFF:
                used = True
                preset = self._memobj.fm_presets_1[i] / 10.0 + 76
            else:
                used = False
                preset = 76
            rs = RadioSetting("fm_presets_1_%1i" % i,
                              "Team 2 Location %i" % (i + 1),
                              RadioSettingValueBoolean(used),
                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
            fm_preset.append(rs)

        return group

    def set_settings(self, settings):
        for element in settings:
            if not isinstance(element, RadioSetting):
                if element.get_name() == "freq_ranges":
                    self._set_freq_settings(element)
                elif element.get_name() == "fm_preset":
                    self._set_fm_preset(element)
                else:
                    self.set_settings(element)
                    continue
            else:
                try:
                    if "." in element.get_name():
                        bits = element.get_name().split(".")
                        obj = self._memobj
                        for bit in bits[:-1]:
                            obj = getattr(obj, bit)
                        setting = bits[-1]
                    else:
                        obj = self._memobj.settings
                        setting = element.get_name()

                    if element.has_apply_callback():
                        LOG.debug("Using apply callback")
                        element.run_apply_callback()
                    else:
                        LOG.debug("Setting %s = %s" % (setting, element.value))
                        setattr(obj, setting, element.value)
                except Exception:
                    LOG.debug(element.get_name())
                    raise

    def _set_fm_preset(self, settings):
        obj = self._memobj
        for element in settings:
            try:
                (bank, index) = \
                    (int(a) for a in element.get_name().split("_")[-2:])
                val = element.value
                if val[0].get_value():
                    value = int(val[1].get_value()*10-760)
                else:
                    value = 0xffff
                LOG.debug("Setting fm_presets_%1i[%1i] = %s" %
                          (bank, index, value))
                if bank == 0:
                    setting = self._memobj.fm_presets_0
                else:
                    setting = self._memobj.fm_presets_1
                setting[index] = value
            except Exception:
                LOG.debug(element.get_name())
                raise

    def _set_freq_settings(self, settings):
        for element in settings:
            try:
                setattr(self._memobj.freq_ranges,
                        element.get_name(),
                        encode_freq(int(element.value)))
            except Exception:
                LOG.debug(element.get_name())
                raise

    def get_raw_memory(self, number):
        return repr(self._memobj.memory[number - 1])

    def _get_tone(self, _mem, mem):
        def _get_dcs(val):
            code = int("%03o" % (val & 0x07FF))
            pol = (val & 0x8000) and "R" or "N"
            return code, pol

        tpol = False
        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
            tcode, tpol = _get_dcs(_mem.tx_tone)
            mem.dtcs = tcode
            txmode = "DTCS"
        elif _mem.tx_tone != 0xFFFF:
            mem.rtone = _mem.tx_tone / 10.0
            txmode = "Tone"
        else:
            txmode = ""

        rpol = False
        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
            rcode, rpol = _get_dcs(_mem.rx_tone)
            mem.rx_dtcs = rcode
            rxmode = "DTCS"
        elif _mem.rx_tone != 0xFFFF:
            mem.ctone = _mem.rx_tone / 10.0
            rxmode = "Tone"
        else:
            rxmode = ""

        if txmode == "Tone" and not rxmode:
            mem.tmode = "Tone"
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
            mem.tmode = "TSQL"
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
            mem.tmode = "DTCS"
        elif rxmode or txmode:
            mem.tmode = "Cross"
            mem.cross_mode = "%s->%s" % (txmode, rxmode)

        # always set it even if no dtcs is used
        mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N")

        LOG.debug("Got TX %s (%i) RX %s (%i)" %
                  (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))

    def _is_txinh(self, _mem):
        return _mem.tx_freq.get_raw() == b"\xFF\xFF\xFF\xFF"

    def get_memory(self, number):
        _mem = self._memobj.memory[number - 1]
        _nam = self._memobj.names[number - 1]

        mem = chirp_common.Memory()
        mem.number = number

        if _mem.get_raw() == (b"\xff" * 16):
            mem.empty = True
            return mem

        mem.freq = int(_mem.rx_freq) * 10
        if _mem.splitdup:
            mem.duplex = "split"
        elif self._is_txinh(_mem):
            mem.duplex = "off"
        elif int(_mem.rx_freq) < int(_mem.tx_freq):
            mem.duplex = "+"
        elif int(_mem.rx_freq) > int(_mem.tx_freq):
            mem.duplex = "-"

        if mem.duplex == "" or mem.duplex == "off":
            mem.offset = 0
        elif mem.duplex == "split":
            mem.offset = int(_mem.tx_freq) * 10
        else:
            mem.offset = abs(int(_mem.tx_freq) - int(_mem.rx_freq)) * 10

        if not _mem.skip:
            mem.skip = "S"
        if not _mem.iswide:
            mem.mode = "NFM"

        self._get_tone(_mem, mem)

        mem.power = self.POWER_LEVELS[not _mem.power_high]

        for i in _nam.name:
            if i == 0xFF:
                break
            mem.name += self.CHARSET[i]

        mem.extra = RadioSettingGroup("extra", "Extra")
        bcl = RadioSetting("bcl", "BCL",
                           RadioSettingValueBoolean(bool(_mem.bcl)))
        bcl.set_doc("Busy Channel Lockout")
        mem.extra.append(bcl)

        options = ["NFM", "FM"]
        iswidex = RadioSetting("iswidex", "Mode TX(KG-UV6X)",
                               RadioSettingValueList(
                                   options, options[_mem.iswidex]))
        iswidex.set_doc("Mode TX")
        mem.extra.append(iswidex)

        return mem

    def _set_tone(self, mem, _mem):
        def _set_dcs(code, pol):
            val = int("%i" % code, 8) + 0x2800
            if pol == "R":
                val += 0x8000
            return val

        rx_mode = tx_mode = None
        rx_tone = tx_tone = 0xFFFF

        if mem.tmode == "Tone":
            tx_mode = "Tone"
            rx_mode = None
            tx_tone = int(mem.rtone * 10)
        elif mem.tmode == "TSQL":
            rx_mode = tx_mode = "Tone"
            rx_tone = tx_tone = int(mem.ctone * 10)
        elif mem.tmode == "DTCS":
            tx_mode = rx_mode = "DTCS"
            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
        elif mem.tmode == "Cross":
            tx_mode, rx_mode = mem.cross_mode.split("->")
            if tx_mode == "DTCS":
                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
            elif tx_mode == "Tone":
                tx_tone = int(mem.rtone * 10)
            if rx_mode == "DTCS":
                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
            elif rx_mode == "Tone":
                rx_tone = int(mem.ctone * 10)

        _mem.rx_tone = rx_tone
        _mem.tx_tone = tx_tone

        LOG.debug("Set TX %s (%i) RX %s (%i)" %
                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))

    def set_memory(self, mem):
        _mem = self._memobj.memory[mem.number - 1]
        _nam = self._memobj.names[mem.number - 1]

        if mem.empty:
            wipe_memory(_mem, "\xFF")
            return

        if _mem.get_raw() == (b"\xFF" * 16):
            wipe_memory(_mem, "\x00")

        _mem.rx_freq = int(mem.freq / 10)
        if mem.duplex == "split":
            _mem.tx_freq = int(mem.offset / 10)
        elif mem.duplex == "off":
            _mem.tx_freq.fill_raw(b"\xFF")
        elif mem.duplex == "+":
            _mem.tx_freq = int(mem.freq / 10) + int(mem.offset / 10)
        elif mem.duplex == "-":
            _mem.tx_freq = int(mem.freq / 10) - int(mem.offset / 10)
        else:
            _mem.tx_freq = int(mem.freq / 10)
        _mem.splitdup = mem.duplex == "split"
        _mem.skip = mem.skip != "S"
        _mem.iswide = mem.mode != "NFM"

        self._set_tone(mem, _mem)

        if mem.power:
            _mem.power_high = not self.POWER_LEVELS.index(mem.power)
        else:
            _mem.power_high = True

        _nam.name = [0xFF] * 6
        for i in range(0, len(mem.name)):
            try:
                _nam.name[i] = self.CHARSET.index(mem.name[i])
            except IndexError:
                raise Exception("Character `%s' not supported")

        for setting in mem.extra:
            setattr(_mem, setting.get_name(), setting.value)

    @classmethod
    def match_model(cls, filedata, filename):
        # New-style image (CHIRP 0.1.12)
        if len(filedata) == 8192 and \
                filedata[0x60:0x64] != b"2009" and \
                filedata[0x170:0x173] != b"LX-" and \
                filedata[0x1f77:0x1f7d] == b"\xff\xff\xff\xff\xff\xff" and \
                filedata[0x0d70:0x0d80] == \
                b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                b"\xff\xff\xff\xff\xff\xff\xff\xff":
            # those areas are (seems to be) unused
            return True
        # Old-style image (CHIRP 0.1.11)
        if len(filedata) == 8200 and \
                filedata[0:4] == b"\x01\x00\x00\x00":
            return True
        return False


@directory.register
class KGUV6DRadio(KGUVD1PRadio):
    """Wouxun KG-UV6 (D and X variants)"""
    MODEL = "KG-UV6"

    _querymodel = (b"HiWXUVD1\x02", b"HiKGUVD1\x02")

    _MEM_FORMAT = """
        #seekto 0x0010;
        struct {
          lbcd rx_freq[4];
          lbcd tx_freq[4];
          ul16 rx_tone;
          ul16 tx_tone;
          u8 _3_unknown_1:4,
             bcl:1,
             _3_unknown_2:3;
          u8 splitdup:1,
             skip:1,
             power_high:1,
             iswide:1,
             _2_unknown_2:4;
          u8 pad;
          u8 _0_unknown_1:3,
             iswidex:1,
             _0_unknown_2:4;
        } memory[199];

        #seekto 0x0F00;
        struct {
          char welcome1[6];
          char welcome2[6];
          char single_band[6];
        } strings;

        #seekto 0x0F20;
        struct {
          u8 unknown_flag_01:6,
             vfo_b_ch_disp:2;
          u8 unknown_flag_02:5,
             vfo_a_fr_step:3;
          u8 unknown_flag_03:4,
             vfo_a_squelch:4;
          u8 unknown_flag_04:7,
             power_save:1;
          u8 unknown_flag_05:5,
             pf2_function:3;
          u8 unknown_flag_06:6,
             roger_beep:2;
          u8 unknown_flag_07:2,
             transmit_time_out:6;
          u8 unknown_flag_08:4,
             vox:4;
          u8 unknown_1[4];
          u8 unknown_flag_09:6,
             voice:2;
          u8 unknown_flag_10:7,
             beep:1;
          u8 unknown_flag_11:7,
             ani_id_enable:1;
          u8 unknown_2[2];
          u8 unknown_flag_12:5,
             vfo_b_fr_step:3;
          u8 unknown_3[1];
          u8 unknown_flag_13:3,
             ani_id_tx_delay:5;
          u8 unknown_4[1];
          u8 unknown_flag_14:6,
             ani_id_sidetone:2;
          u8 unknown_flag_15:4,
             tx_time_out_alert:4;
          u8 unknown_flag_16:6,
             vfo_a_ch_disp:2;
          u8 unknown_flag_20:6,
             scan_mode:2;
          u8 unknown_flag_31:7,
             kbd_lock:1;
          u8 unknown_flag_17:6,
             ponmsg:2;
          u8 unknown_flag_18:5,
             pf1_function:3;
          u8 unknown_5[1];
          u8 unknown_flag_19:7,
             auto_backlight:1;
          u8 unknown_flag_26:7,
             sos_ch:1;
          u8 unknown_6;
          u8 sd_available;
          u8 unknown_flag_32:7,
             auto_lock_kbd:1;
          u8 unknown_flag_22:4,
             vfo_b_squelch:4;
          u8 unknown_7[1];
          u8 unknown_flag_23:7,
             stopwatch:1;
          u8 vfo_a_cur_chan;
          u8 unknown_flag_24:7,
             dual_band_receive:1;
          u8 current_vfo:1,
             unknown_flag_33:7;
          u8 unknown_8[2];
          u8 mode_password[6];
          u8 reset_password[6];
          u8 ani_id_content[6];
          u8 unknown_flag_25:7,
             menu_available:1;
          u8 unknown_9[1];
          u8 priority_chan;
          u8 vfo_b_cur_chan;
        } settings;

        //#seekto 0x0f60;
        struct {
          lbcd rx_freq[4];
          lbcd tx_freq[4];
          ul16 rx_tone;
          ul16 tx_tone;
          u8 _3_unknown_3:4,
             bcl:1,
             _3_unknown_4:3;
          u8 splitdup:1,
             _2_unknown_3:1,
             power_high:1,
             iswide:1,
             _2_unknown_4:4;
          u8 pad[2];
        } vfo_settings[2];

        #seekto 0x0f82;
        u16 fm_presets_0[9];

        #seekto 0x0ff0;
        struct {
            u16 vhf_rx_start;
            u16 vhf_rx_stop;
            u16 uhf_rx_start;
            u16 uhf_rx_stop;
            u16 vhf_tx_start;
            u16 vhf_tx_stop;
            u16 uhf_tx_start;
            u16 uhf_tx_stop;
        } freq_ranges;

        #seekto 0x1010;
        struct {
          u8 name[6];
          u8 pad[10];
        } names[199];

        #seekto 0x1f60;
        struct {
            u8 unknown_flag_26:6,
               tx_offset_dir:2;
            u8 tx_offset[6];
            u8 pad[9];
        } vfo_offset[2];

        #seekto 0x1f82;
        u16 fm_presets_1[9];
    """

    def get_features(self):
        rf = KGUVD1PRadio.get_features(self)
        rf.memory_bounds = (1, 199)
        rf.valid_tuning_steps = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0,
                                 100.0]
        return rf

    def get_settings(self):
        freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges")
        fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
        cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings")
        group = RadioSettings(cfg_s, freq_ranges, fm_preset)

        rs = RadioSetting("menu_available", "Menu Available",
                          RadioSettingValueBoolean(
                              self._memobj.settings.menu_available))
        cfg_s.append(rs)

        rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.vhf_rx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.vhf_rx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.uhf_rx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.uhf_rx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.vhf_tx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.vhf_tx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.uhf_tx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)",
                          RadioSettingValueInteger(
                              1, 1000, decode_freq(
                                  self._memobj.freq_ranges.uhf_tx_stop)))
        freq_ranges.append(rs)

        # tell the decoded ranges to UI
        freq_ranges = self._memobj.freq_ranges
        self.valid_freq = \
            [(decode_freq(freq_ranges.vhf_rx_start) * 1000000,
             (decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000),
             (decode_freq(freq_ranges.uhf_rx_start) * 1000000,
             (decode_freq(freq_ranges.uhf_rx_stop) + 1) * 1000000)]

        def _filter(name):
            filtered = ""
            for char in str(name):
                if char in chirp_common.CHARSET_ASCII:
                    filtered += char
                else:
                    filtered += " "
            return filtered

        # add some radio specific settings
        options = ["Off", "Welcome", "V bat", "N/A(KG-UV6X)"]
        rs = RadioSetting("ponmsg", "Poweron message",
                          RadioSettingValueList(
                              options, options[self._memobj.settings.ponmsg]))
        cfg_s.append(rs)
        rs = RadioSetting("strings.welcome1", "Power-On Message 1",
                          RadioSettingValueString(
                              0, 6, _filter(self._memobj.strings.welcome1)))
        cfg_s.append(rs)
        rs = RadioSetting("strings.welcome2", "Power-On Message 2",
                          RadioSettingValueString(
                              0, 6, _filter(self._memobj.strings.welcome2)))
        cfg_s.append(rs)
        rs = RadioSetting("strings.single_band", "Single Band Message",
                          RadioSettingValueString(
                              0, 6, _filter(self._memobj.strings.single_band)))
        cfg_s.append(rs)
        options = ["Channel", "ch/freq", "Name", "VFO"]
        rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_a_ch_disp]))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_b_ch_disp]))
        cfg_s.append(rs)
        options = \
            ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
        rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_a_fr_step]))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.vfo_b_fr_step]))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
                          RadioSettingValueInteger(
                              0, 9, self._memobj.settings.vfo_a_squelch))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
                          RadioSettingValueInteger(
                              0, 9, self._memobj.settings.vfo_b_squelch))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
                          RadioSettingValueInteger(
                              1, 199, self._memobj.settings.vfo_a_cur_chan))
        cfg_s.append(rs)
        rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
                          RadioSettingValueInteger(
                              1, 199, self._memobj.settings.vfo_b_cur_chan))
        cfg_s.append(rs)
        rs = RadioSetting("priority_chan", "Priority channel",
                          RadioSettingValueInteger(
                              0, 199, self._memobj.settings.priority_chan))
        cfg_s.append(rs)
        rs = RadioSetting("power_save", "Power save",
                          RadioSettingValueBoolean(
                              self._memobj.settings.power_save))
        cfg_s.append(rs)
        options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
        rs = RadioSetting("pf1_function", "PF1 Function select",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.pf1_function]))
        cfg_s.append(rs)
        options = ["Off", "Radio", "fr/ch", "Rpt", "Stopwatch", "Lamp", "SOS"]
        rs = RadioSetting("pf2_function", "PF2 Function select",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.pf2_function]))
        cfg_s.append(rs)
        options = ["Off", "Begin", "End", "Both"]
        rs = RadioSetting("roger_beep", "Roger beep select",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.roger_beep]))
        cfg_s.append(rs)
        options = ["%s" % x for x in range(15, 615, 15)]
        transmit_time_out = options[self._memobj.settings.transmit_time_out]
        rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
                          RadioSettingValueList(
                              options, transmit_time_out))
        cfg_s.append(rs)
        rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
                          RadioSettingValueInteger(
                              0, 10, self._memobj.settings.tx_time_out_alert))
        cfg_s.append(rs)
        rs = RadioSetting("vox", "Vox",
                          RadioSettingValueInteger(
                              0, 10, self._memobj.settings.vox))
        cfg_s.append(rs)
        options = ["Off", "Chinese", "English"]
        rs = RadioSetting("voice", "Voice",
                          RadioSettingValueList(
                              options, options[self._memobj.settings.voice]))
        cfg_s.append(rs)
        rs = RadioSetting("beep", "Beep",
                          RadioSettingValueBoolean(
                              self._memobj.settings.beep))
        cfg_s.append(rs)
        rs = RadioSetting("ani_id_enable", "ANI id enable",
                          RadioSettingValueBoolean(
                              self._memobj.settings.ani_id_enable))
        cfg_s.append(rs)
        rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
                          RadioSettingValueInteger(
                              0, 30, self._memobj.settings.ani_id_tx_delay))
        cfg_s.append(rs)
        options = ["Off", "Key", "ANI", "Key+ANI"]
        rs = RadioSetting("ani_id_sidetone", "ANI id sidetone",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.ani_id_sidetone]))
        cfg_s.append(rs)
        options = ["Time", "Carrier", "Search"]
        rs = RadioSetting("scan_mode", "Scan mode",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.scan_mode]))
        cfg_s.append(rs)
        rs = RadioSetting("kbd_lock", "Keyboard lock",
                          RadioSettingValueBoolean(
                              self._memobj.settings.kbd_lock))
        cfg_s.append(rs)
        rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
                          RadioSettingValueBoolean(
                              self._memobj.settings.auto_lock_kbd))
        cfg_s.append(rs)
        rs = RadioSetting("auto_backlight", "Auto backlight",
                          RadioSettingValueBoolean(
                              self._memobj.settings.auto_backlight))
        cfg_s.append(rs)
        options = ["CH A", "CH B"]
        rs = RadioSetting("sos_ch", "SOS CH",
                          RadioSettingValueList(
                              options, options[self._memobj.settings.sos_ch]))
        cfg_s.append(rs)
        rs = RadioSetting("stopwatch", "Stopwatch",
                          RadioSettingValueBoolean(
                              self._memobj.settings.stopwatch))
        cfg_s.append(rs)
        rs = RadioSetting("dual_band_receive", "Dual band receive",
                          RadioSettingValueBoolean(
                              self._memobj.settings.dual_band_receive))
        cfg_s.append(rs)
        options = ["VFO A", "VFO B"]
        rs = RadioSetting("current_vfo", "Current VFO",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.current_vfo]))
        cfg_s.append(rs)

        options = ["Dual", "Single"]
        rs = RadioSetting("sd_available", "Single/Dual Band",
                          RadioSettingValueList(
                              options,
                              options[self._memobj.settings.sd_available]))
        cfg_s.append(rs)

        _pwd = self._memobj.settings.mode_password
        rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
                          RadioSettingValueInteger(0, 9, _pwd[0]),
                          RadioSettingValueInteger(0, 9, _pwd[1]),
                          RadioSettingValueInteger(0, 9, _pwd[2]),
                          RadioSettingValueInteger(0, 9, _pwd[3]),
                          RadioSettingValueInteger(0, 9, _pwd[4]),
                          RadioSettingValueInteger(0, 9, _pwd[5]))
        cfg_s.append(rs)
        _pwd = self._memobj.settings.reset_password
        rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
                          RadioSettingValueInteger(0, 9, _pwd[0]),
                          RadioSettingValueInteger(0, 9, _pwd[1]),
                          RadioSettingValueInteger(0, 9, _pwd[2]),
                          RadioSettingValueInteger(0, 9, _pwd[3]),
                          RadioSettingValueInteger(0, 9, _pwd[4]),
                          RadioSettingValueInteger(0, 9, _pwd[5]))
        cfg_s.append(rs)

        dtmfchars = "0123456789 *#ABCD"
        _codeobj = self._memobj.settings.ani_id_content
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
        val = RadioSettingValueString(0, 6, _code, False)
        val.set_charset(dtmfchars)
        rs = RadioSetting("settings.ani_id_content", "ANI Code", val)

        def apply_ani_id(setting, obj):
            value = []
            for j in range(0, 6):
                try:
                    value.append(dtmfchars.index(str(setting.value)[j]))
                except IndexError:
                    value.append(0xFF)
            obj.ani_id_content = value
        rs.set_apply_callback(apply_ani_id, self._memobj.settings)
        cfg_s.append(rs)

        for i in range(0, 9):
            if self._memobj.fm_presets_0[i] != 0xFFFF:
                used = True
                preset = self._memobj.fm_presets_0[i]/10.0+76
            else:
                used = False
                preset = 76
            rs = RadioSetting("fm_presets_0_%1i" % i,
                              "Team 1 Location %i" % (i+1),
                              RadioSettingValueBoolean(used),
                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
            fm_preset.append(rs)
        for i in range(0, 9):
            if self._memobj.fm_presets_1[i] != 0xFFFF:
                used = True
                preset = self._memobj.fm_presets_1[i]/10.0+76
            else:
                used = False
                preset = 76
            rs = RadioSetting("fm_presets_1_%1i" % i,
                              "Team 2 Location %i" % (i+1),
                              RadioSettingValueBoolean(used),
                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
            fm_preset.append(rs)

        return group

    def set_settings(self, settings):
        for element in settings:
            if not isinstance(element, RadioSetting):
                if element.get_name() == "freq_ranges":
                    self._set_freq_settings(element)
                elif element.get_name() == "fm_preset":
                    self._set_fm_preset(element)
                else:
                    self.set_settings(element)
            else:
                try:
                    if "." in element.get_name():
                        bits = element.get_name().split(".")
                        obj = self._memobj
                        for bit in bits[:-1]:
                            obj = getattr(obj, bit)
                        setting = bits[-1]
                    else:
                        obj = self._memobj.settings
                        setting = element.get_name()

                    if element.has_apply_callback():
                        LOG.debug("Using apply callback")
                        element.run_apply_callback()
                    else:
                        LOG.debug("Setting %s = %s" % (setting, element.value))
                        setattr(obj, setting, element.value)
                except Exception:
                    LOG.debug(element.get_name())
                    raise

    def _set_fm_preset(self, settings):
        obj = self._memobj
        for element in settings:
            try:
                (bank, index) = \
                    (int(a) for a in element.get_name().split("_")[-2:])
                val = element.value
                if val[0].get_value():
                    value = int(val[1].get_value()*10-760)
                else:
                    value = 0xffff
                LOG.debug("Setting fm_presets_%1i[%1i] = %s" %
                          (bank, index, value))
                if bank == 0:
                    setting = self._memobj.fm_presets_0
                else:
                    setting = self._memobj.fm_presets_1
                setting[index] = value
            except Exception:
                LOG.debug(element.get_name())
                raise

    @classmethod
    def match_model(cls, filedata, filename):
        if len(filedata) == 8192 and \
                filedata[0x1f77:0x1f7d] == b"WELCOM":
            return True
        return False


@directory.register
class KG816Radio(KGUVD1PRadio, chirp_common.ExperimentalRadio):
    """Wouxun KG-816"""
    MODEL = "KG-816"

    _querymodel = b"HiWOUXUN\x02"

    _MEM_FORMAT = """
        #seekto 0x0010;
        struct {
          lbcd rx_freq[4];
          lbcd tx_freq[4];
          ul16 rx_tone;
          ul16 tx_tone;
          u8 _3_unknown_1:4,
             bcl:1,
             _3_unknown_2:3;
          u8 splitdup:1,
             skip:1,
             power_high:1,
             iswide:1,
             _2_unknown_2:4;
          u8 unknown;
          u8 _0_unknown_1:3,
             iswidex:1,
             _0_unknown_2:4;
        } memory[199];

        #seekto 0x0d70;
        struct {
            u16 vhf_rx_start;
            u16 vhf_rx_stop;
            u16 uhf_rx_start;
            u16 uhf_rx_stop;
            u16 vhf_tx_start;
            u16 vhf_tx_stop;
            u16 uhf_tx_start;
            u16 uhf_tx_stop;
        } freq_ranges;

        #seekto 0x1010;
        struct {
            u8 name[6];
            u8 pad[10];
        } names[199];
    """

    @classmethod
    def get_experimental_warning(cls):
        return ('We have not that much information on this model '
                'up to now we only know it has the same memory '
                'organization of KGUVD1 but uses 199 memories. '
                'it has been reported to work but '
                'proceed at your own risk!')

    def get_features(self):
        rf = KGUVD1PRadio.get_features(self)
        rf.memory_bounds = (1, 199)  # this is the only known difference
        return rf

    def get_settings(self):
        freq_ranges = RadioSettingGroup("freq_ranges",
                                        "Freq Ranges (read only)")
        group = RadioSettings(freq_ranges)

        rs = RadioSetting("vhf_rx_start", "vhf rx start",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.vhf_rx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_rx_stop", "vhf rx stop",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.vhf_rx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_rx_start", "uhf rx start",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_rx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_rx_stop", "uhf rx stop",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_rx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_tx_start", "vhf tx start",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.vhf_tx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("vhf_tx_stop", "vhf tx stop",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.vhf_tx_stop)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_tx_start", "uhf tx start",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_tx_start)))
        freq_ranges.append(rs)
        rs = RadioSetting("uhf_tx_stop", "uhf tx stop",
                          RadioSettingValueInteger(
                              66, 520, decode_freq(
                                  self._memobj.freq_ranges.uhf_tx_stop)))
        freq_ranges.append(rs)

        # tell the decoded ranges to UI
        self.valid_freq = \
            [(decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000,
             (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000)]

        return group

    @classmethod
    def match_model(cls, filedata, filename):
        if len(filedata) == 8192 and \
                filedata[0x60:0x64] != b"2009" and \
                filedata[0x170:0x173] != b"LX-" and \
                filedata[0xF7E:0xF80] != b"\x01\xE2" and \
                filedata[0x1f77:0x1f7d] == b"\xff\xff\xff\xff\xff\xff" and \
                filedata[0x0d70:0x0d80] != (
                    b"\xff\xff\xff\xff\xff\xff\xff\xff"
                    b"\xff\xff\xff\xff\xff\xff\xff\xff"):
            return True
        return False


@directory.register
class KG818Radio(KG816Radio):
    """Wouxun KG-818"""
    MODEL = "KG-818"

    @classmethod
    def match_model(cls, filedata, filename):
        return False
