# Copyright 2010 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/>.

import logging

from chirp.drivers import yaesu_clone
from chirp import chirp_common, directory, bitwise
from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings
from chirp.settings import RadioSettingValueString, RadioSettingValueList
from chirp.settings import RadioSettingValueBoolean
import re

LOG = logging.getLogger(__name__)

# flags.{even|odd}_pskip: These are actually "preferential *scan* channels".
# Is that what they mean on other radios as well?

# memory {
#   step_changed: Channel step has been changed. Bit stays on even after
#                 you switch back to default step. Don't know why you would
#                 care
#   half_deviation: 2.5 kHz deviation
#   cpu_shifted:  CPU freq has been shifted (to move a birdie out of channel)
#   power:        0-3: ["L1", "L2", "L3", "Hi"]
#   pager:        Set if this is a paging memory
#   tmodes:       0-7: ["", "Tone", "TSQL", "DTCS", "Rv Tn", "D Code",
#                       "T DCS", "D Tone"]
#                      Rv Tn: Reverse CTCSS - mutes receiver on tone
#                      The final 3 are for split:
#                      D Code: DCS Encode only
#                      T DCS:  Encodes tone, decodes DCS code
#                      D Tone: Encodes DCS code, decodes tone
# }
MEM_FORMAT = """
#seekto 0x010A;
struct {
  u8 auto_power_off;
  u8 arts_beep;
  u8 bell;
  u8 beep_level;
  u8 arts_cwid_alpha[16];
  u8 unk1[2];
  u8 channel_counter_width;
  u8 lcd_dimmer;
  u8 last_dtmf;
  u8 unk2;
  u8 internet_code;
  u8 last_internet_dtmf;
  u8 unk3[3];
  u8 emergency;
  u8 unk4[16];
  u8 lamp;
  u8 lock;
  u8 unk5;
  u8 mic_gain;
  u8 unk6[2];
  u8 on_timer;
  u8 open_message_mode;
  u8 open_message[6];
  u8 unk7;
  u8 unk8:6,
     pager_answer_back:1,
     unk9:1;
  u8 pager_rx_tone1;
  u8 pager_rx_tone2;
  u8 pager_tx_tone1;
  u8 pager_tx_tone2;
  u8 password[4];
  u8 ptt_delay;
  u8 rf_squelch;
  u8 rx_save;
  u8 resume;
  u8 unk10[5];
  u8 tx_timeout;
  u8 wakeup;
  u8 vfo_mode:1,
     arts_cwid:1,
     scan_lamp:1,
     ts_speed:1,
     unk11:1,
     beep:1,
     unk12:1,
     dtmf_autodial:1;
  u8 busy_led:1,
     tone_search_mute:1,
     int_autodial:1,
     bclo:1,
     edge_beep:1,
     unk13:1,
     dmr_wrt:1,
     tx_saver:1;
  u8 unk14:2,
     smart_search:1,
     unk15:3,
     home_rev:1,
     moni_tcall:1;
  u8 unk16:3,
     arts_interval:1,
     unk17:3,
     memory_method:1;
  u8 unk18:2,
     internet_mode:1,
     wx_alert:1,
     unk19:1,
     att:1,
     unk20:2;
} settings;

#seekto 0x018A;
struct {
  u16 in_use;
} bank_used[24];

#seekto 0x01D8;
u8 clock_shift;

#seekto 0x0214;
u16 banksoff1;

#seekto 0x0248;
u8 lastsetting1;

#seekto 0x0294;
u16 banksoff2;

#seekto 0x0248;
u8 lastsetting2;

#seekto 0x02CA;
struct {
  u8 memory[16];
} dtmf[10];

#seekto 0x03CA;
struct {
  u8 memory[8];
  u8 empty_ff[8];
} internet_dtmf[64];

#seekto 0x097A;
struct {
  u8 name[6];
} bank_names[24];

#seekto 0x0C0A;
struct {
  u16 channels[100];
} banks[24];

#seekto 0x1ECA;
struct {
  u8 even_pskip:1,
     even_skip:1,
     even_valid:1,
     even_masked:1,
     odd_pskip:1,
     odd_skip:1,
     odd_valid:1,
     odd_masked:1;
} flags[500];

#seekto 0x21CA;
struct {
  u8 unknown11:1,
     step_changed:1,
     half_deviation:1,
     cpu_shifted:1,
     unknown12:4;
  u8 mode:2,
     duplex:2,
     tune_step:4;
  bbcd freq[3];
  u8 power:2,
     unknown2:2,
     pager:1,
     tmode:3;
  u8 name[6];
  bbcd offset[3];
  u8 tone;
  u8 dcs;
  u8 unknown5;
} memory[999];
"""

DUPLEX = ["", "-", "+", "split"]
MODES = ["FM", "AM", "WFM", "FM"]  # last is auto
TMODES = ["", "Tone", "TSQL",
          "DTCS",
          "TSQL-R",  # This is "RV TN" as Yaesu calls it
          "DTCS->", "Tone->DTCS", "DTCS->Tone"]
DTMFCHARSET = list("0123456789ABCD*#-")
STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0,
         9.0, 200.0, 5.0]  # last is auto, 9.0k and 200.0k are unadvertised

CHARSET = ["%i" % int(x) for x in range(10)] + \
    [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
    list(" +-/?[]u_" + ("\x00" * 9) + "$%%\x00*#.|=\\o@") + \
    list("\x00" * 100)

PASS_CHARSET = list("0123456789ABCDEF")

POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
                chirp_common.PowerLevel("L3", watts=2.50),
                chirp_common.PowerLevel("L2", watts=1.00),
                chirp_common.PowerLevel("L1", watts=0.30)]
POWER_LEVELS_220 = [chirp_common.PowerLevel("Hi", watts=1.50),
                    chirp_common.PowerLevel("L3", watts=1.00),
                    chirp_common.PowerLevel("L2", watts=0.50),
                    chirp_common.PowerLevel("L1", watts=0.20)]


class VX6Bank(chirp_common.NamedBank):
    """A VX6 Bank"""
    def get_name(self):
        _bank = self._model._radio._memobj.bank_names[self.index]
        name = ""
        for i in _bank.name:
            if i == 0xFF:
                break
            name += CHARSET[i & 0x7F]
        return name.rstrip()

    def set_name(self, name):
        name = name.upper()
        _bank = self._model._radio._memobj.bank_names[self.index]
        _bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]]


class VX6BankModel(chirp_common.BankModel):
    """A VX-6 bank model"""

    def get_num_mappings(self):
        return len(self.get_mappings())

    def get_mappings(self):
        banks = self._radio._memobj.banks
        bank_mappings = []
        for index, _bank in enumerate(banks):
            bank = VX6Bank(self, "%i" % index, "b%i" % (index + 1))
            bank.index = index
            bank_mappings.append(bank)

        return bank_mappings

    def _get_channel_numbers_in_bank(self, bank):
        _bank_used = self._radio._memobj.bank_used[bank.index]
        if _bank_used.in_use == 0xFFFF:
            return set()

        _members = self._radio._memobj.banks[bank.index]
        return set([int(ch) + 1 for ch in _members.channels if ch != 0xFFFF])

    def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
        _members = self._radio._memobj.banks[bank.index]
        if len(channels_in_bank) > len(_members.channels):
            raise Exception("Too many entries in bank %d" % bank.index)

        empty = 0
        for index, channel_number in enumerate(sorted(channels_in_bank)):
            _members.channels[index] = channel_number - 1
            empty = index + 1
        for index in range(empty, len(_members.channels)):
            _members.channels[index] = 0xFFFF

    def add_memory_to_mapping(self, memory, bank):
        channels_in_bank = self._get_channel_numbers_in_bank(bank)
        channels_in_bank.add(memory.number)
        self._update_bank_with_channel_numbers(bank, channels_in_bank)
        _bank_used = self._radio._memobj.bank_used[bank.index]
        _bank_used.in_use = 0x0000  # enable

        # also needed for unit to recognize any banks?
        self._radio._memobj.banksoff1 = 0x0000
        self._radio._memobj.banksoff2 = 0x0000
        # TODO: turn back off (0xFFFF) when all banks are empty?

    def remove_memory_from_mapping(self, memory, bank):
        channels_in_bank = self._get_channel_numbers_in_bank(bank)
        try:
            channels_in_bank.remove(memory.number)
        except KeyError:
            raise Exception("Memory %i is not in bank %s. Cannot remove" %
                            (memory.number, bank))
        self._update_bank_with_channel_numbers(bank, channels_in_bank)

        if not channels_in_bank:
            _bank_used = self._radio._memobj.bank_used[bank.index]
            _bank_used.in_use = 0xFFFF  # disable bank

    def get_mapping_memories(self, bank):
        memories = []
        for channel in self._get_channel_numbers_in_bank(bank):
            memories.append(self._radio.get_memory(channel))

        return memories

    def get_memory_mappings(self, memory):
        banks = []
        for bank in self.get_mappings():
            if memory.number in self._get_channel_numbers_in_bank(bank):
                banks.append(bank)

        return banks


@directory.register
class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
    """Yaesu VX-6"""
    BAUD_RATE = 19200
    VENDOR = "Yaesu"
    MODEL = "VX-6"

    _model = b"AH021"
    _memsize = 32587
    _block_lengths = [10, 32577]
    _block_size = 16

    _APO = ("OFF", "30 min", "1 hour", "3 hour", "5 hour", "8 hour")
    _ARTSBEEP = ("Off", "In Range", "Always")
    _ARTS_INT = ("15 S", "25 S")
    _BELL = ("OFF", "1", "3", "5", "8", "Continuous")
    _BEEP_LEVEL = ["%i" % int(x) for x in range(1, 10)]
    _CH_CNT = ("5 MHz", "10 MHz", "50 MHz", "100 MHz")
    _DIM_LEVEL = ["%i" % int(x) for x in range(0, 13)]
    _EMERGENCY = ("Beep", "Strobe", "Bp+Str", "Beam", "Bp+Bem", "CW",
                  "Bp+CW", "CWT")
    _HOME_REV = ("HOME", "REV")
    _INT_CD = ["%i" % int(x) for x in range(0, 10)] + \
        [chr(x) for x in range(ord("A"), ord("F")+1)]
    _INT_MD = ("SRG: Sister Radio Group", "FRG: Friendly Radio Group")
    _LAMP = ("Key", "Continuous", "Off")
    _LOCK = ("Key", "Dial", "Key+Dial", "PTT", "Key+PTT", "Dial+PTT", "All")
    _MAN_AUTO = ("Manual", "Auto")
    _MEM_W_MD = ("Lower", "Next")
    _MONI_TCALL = ("MONI", "T-CALL")
    _NUM_1_9 = ["%i" % int(x) for x in range(1, 10)]
    _NUM_0_9 = ["%i" % int(x) for x in range(10)]
    _NUM_0_63 = ["%i" % int(x) for x in range(64)]
    _NUM_1_50 = ["%i" % int(x) for x in range(1, 51)]
    _ON_TIMER = ["OFF"] + \
        ["%02d:%02d" % (t / 60, t % 60) for t in range(10, 1450, 10)]
    _OPEN_MSG = ("Off", "DC Voltage", "Message")
    _PTT_DELAY = ("OFF", "20 ms", "50 ms", "100 ms", "200 ms")
    _RF_SQL = ("OFF", "S1", "S2", "S3", "S4", "S5",
               "S6", "S7", "S8", "S9", "S9+")
    _RX_SAVE = ("OFF", "200 ms", "300 ms", "500 ms", "1 s", "2 s")
    _RESUME = ("3 SEC", "5 SEC", "10 SEC", "BUSY", "HOLD")
    _SMART_SEARCH = ("SINGLE", "CONT")
    _TOT = ("OFF", "1MIN", "3MIN", "5MIN", "10MIN")
    _TS_SPEED = ("FAST", "SLOW")
    _VFO_MODE = ("ALL", "BAND")
    _WAKEUP = ("OFF", "5S", "10S", "20S", "30S", "EAI")

    @classmethod
    def get_prompts(cls):
        rp = chirp_common.RadioPrompts()
        rp.pre_download = _(
            "1. Turn radio off.\n"
            "2. Connect cable to MIC/SP jack.\n"
            "3. Press and hold in the [F/W] key while turning the radio on\n"
            "     (\"CLONE\" will appear on the display).\n"
            "4. <b>After clicking OK</b>, press the [BAND] key to send"
            " image.\n")
        rp.pre_upload = _(
            "1. Turn radio off.\n"
            "2. Connect cable to MIC/SP jack.\n"
            "3. Press and hold in the [F/W] key while turning the radio on\n"
            "     (\"CLONE\" will appear on the display).\n"
            "4. Press the [V/M] key (\"-WAIT-\" will appear on the LCD).\n")
        return rp

    def _checksums(self):
        return [yaesu_clone.YaesuChecksum(0x0000, 0x7F49)]

    def process_mmap(self):
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)

    def get_features(self):
        rf = chirp_common.RadioFeatures()
        rf.has_bank = True
        rf.has_bank_names = True
        rf.has_dtcs_polarity = False
        rf.valid_modes = ["FM", "WFM", "AM", "NFM"]
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "TSQL-R", "Cross"]
        rf.valid_cross_modes = [x for x in TMODES if '->' in x]
        rf.valid_duplexes = DUPLEX
        rf.valid_tuning_steps = STEPS
        rf.valid_power_levels = POWER_LEVELS
        rf.memory_bounds = (1, 999)
        rf.valid_bands = [(500000, 998990000)]
        rf.valid_characters = "".join(CHARSET)
        rf.valid_name_length = 6
        rf.can_odd_split = True
        rf.has_ctone = False
        rf.has_settings = True
        return rf

    def get_raw_memory(self, number):
        return repr(self._memobj.memory[number-1]) + \
            repr(self._memobj.flags[(number-1)/2])

    def get_memory(self, number):
        _mem = self._memobj.memory[number-1]
        _flg = self._memobj.flags[(number-1)/2]

        nibble = ((number-1) % 2) and "even" or "odd"
        used = _flg["%s_masked" % nibble]
        valid = _flg["%s_valid" % nibble]
        pskip = _flg["%s_pskip" % nibble]
        skip = _flg["%s_skip" % nibble]

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

        if not used:
            mem.empty = True
        if not valid:
            mem.empty = True
            mem.power = POWER_LEVELS[0]
            return mem

        mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
        mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000)
        mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone & 0x3f]
        tmode = TMODES[_mem.tmode]
        if '->' in tmode:
            mem.tmode = 'Cross'
            mem.cross_mode = tmode
        else:
            mem.tmode = tmode
        mem.duplex = DUPLEX[_mem.duplex]
        mem.mode = MODES[_mem.mode]
        if mem.mode == "FM" and _mem.half_deviation:
            mem.mode = "NFM"
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs & 0x7f]
        mem.tuning_step = STEPS[_mem.tune_step]
        mem.skip = pskip and "P" or skip and "S" or ""

        if mem.freq > 220000000 and mem.freq < 225000000:
            mem.power = POWER_LEVELS_220[3 - _mem.power]
        else:
            mem.power = POWER_LEVELS[3 - _mem.power]

        for i in _mem.name:
            if i == 0xFF:
                break
            mem.name += CHARSET[i & 0x7F]
        mem.name = mem.name.rstrip()

        return mem

    def set_memory(self, mem):
        _mem = self._memobj.memory[mem.number-1]
        _flag = self._memobj.flags[(mem.number-1)/2]

        nibble = ((mem.number-1) % 2) and "even" or "odd"
        used = _flag["%s_masked" % nibble]
        valid = _flag["%s_valid" % nibble]

        # initialize new channel to safe defaults
        if not mem.empty and not valid:
            _flag["%s_valid" % nibble] = True
            _mem.unknown11 = 0
            _mem.step_changed = 0
            _mem.cpu_shifted = 0
            _mem.unknown12 = 0
            _mem.unknown2 = 0
            _mem.pager = 0
            _mem.unknown5 = 0

        if mem.empty and valid and not used:
            _flag["%s_valid" % nibble] = False
            return
        _flag["%s_masked" % nibble] = not mem.empty

        if mem.empty:
            return

        _mem.freq = mem.freq / 1000
        _mem.offset = mem.offset / 1000
        _mem.tone = chirp_common.TONES.index(mem.rtone)
        if mem.tmode == 'Cross':
            _mem.tmode = TMODES.index(mem.cross_mode)
        else:
            _mem.tmode = TMODES.index(mem.tmode)
        _mem.duplex = DUPLEX.index(mem.duplex)
        if mem.mode == "NFM":
            _mem.mode = MODES.index("FM")
            _mem.half_deviation = 1
        else:
            _mem.mode = MODES.index(mem.mode)
            _mem.half_deviation = 0
        _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
        _mem.tune_step = STEPS.index(mem.tuning_step)
        if mem.power:
            _mem.power = 3 - self._resolve_power(mem)
        else:
            _mem.power = 0

        _flag["%s_pskip" % nibble] = mem.skip == "P"
        _flag["%s_skip" % nibble] = mem.skip == "S"

        _mem.name = [0xFF] * 6
        for i in range(0, 6):
            _mem.name[i] = CHARSET.index(mem.name.ljust(6)[i])

        if mem.name.strip():
            _mem.name[0] |= 0x80

    def _resolve_power(self, mem):
        # Since we have two sets of power levels, we may need to match the
        # one in mem.power by name instead of absolute power to handle things
        # like editing a 2m channel to a 220 MHz frequency, etc.
        by_name = [str(x) for x in POWER_LEVELS]
        index = by_name.index(str(mem.power))
        LOG.debug('Resolving power %r by name to index %i' % (
            mem.power, index))
        return index

    def validate_memory(self, mem):
        # Override memory validation with by-name power level matching,
        # since we do not expose those power levels in our RadioFeatures.
        if mem.power and mem.power not in POWER_LEVELS:
            mem = mem.dupe()
            mem.power = POWER_LEVELS[self._resolve_power(mem)]
        return super().validate_memory(mem)

    def get_bank_model(self):
        return VX6BankModel(self)

    def _decode_chars(self, inarr):
        outstr = ""
        for i in inarr:
            if i == 0xFF:
                break
            outchar = CHARSET[i & 0x7F]
            if outchar != "\x00":
                outstr += outchar
        return outstr.rstrip()

    def _encode_chars(self, instr, length=16):
        outarr = []
        instr = str(instr)
        for i in range(length):
            if i < len(instr):
                outarr.append(CHARSET.index(instr[i]))
            else:
                outarr.append(0xFF)
        return outarr

    def _get_settings(self):
        _settings = self._memobj.settings
        basic = RadioSettingGroup("basic", "Basic")
        arts = RadioSettingGroup("arts", "ARTS")
        dtmf = RadioSettingGroup("dtmf", "DTMF")
        wires = RadioSettingGroup("wires", "WIRES")
        misc = RadioSettingGroup("misc", "Misc")
        top = RadioSettings(basic, arts, dtmf, wires, misc)

        # BASIC

        val = RadioSettingValueList(
            self._APO, self._APO[_settings.auto_power_off])
        rs = RadioSetting("auto_power_off", "Auto Power Off", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._BEEP_LEVEL, self._BEEP_LEVEL[_settings.beep_level])
        rs = RadioSetting("beep_level", "Beep Level", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._DIM_LEVEL, self._DIM_LEVEL[_settings.lcd_dimmer])
        rs = RadioSetting("lcd_dimmer", "Dimmer Level", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._LAMP, self._LAMP[_settings.lamp])
        rs = RadioSetting("lamp", "Keypad Lamp", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._LOCK, self._LOCK[_settings.lock])
        rs = RadioSetting("lock", "Lock", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._NUM_1_9, self._NUM_1_9[_settings.mic_gain])
        rs = RadioSetting("mic_gain", "Mic Gain", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._OPEN_MSG, self._OPEN_MSG[_settings.open_message_mode])
        rs = RadioSetting("open_message_mode",
                          "Open Message Mode", val)
        basic.append(rs)

        val = RadioSettingValueString(0, 6,
                                      self._decode_chars(
                                          _settings.open_message))
        val.set_charset(CHARSET)
        rs = RadioSetting("open_message", "Opening Message", val)
        basic.append(rs)

        passstr = ""
        for c in _settings.password:
            if c < len(PASS_CHARSET):
                passstr += PASS_CHARSET[c]
        val = RadioSettingValueString(0, 4, passstr)
        val.set_charset(PASS_CHARSET)
        rs = RadioSetting("password", "Password", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._RESUME, self._RESUME[_settings.resume])
        rs = RadioSetting("resume", "Scan Resume", val)
        basic.append(rs)

        val = RadioSettingValueList(
            self._MONI_TCALL, self._MONI_TCALL[_settings.moni_tcall])
        rs = RadioSetting("moni_tcall", "MONI/T-CALL switch", val)
        basic.append(rs)

        rs = RadioSetting("scan_lamp", "Scan Lamp",
                          RadioSettingValueBoolean(_settings.scan_lamp))
        basic.append(rs)

        rs = RadioSetting("beep", "Keypad Beep",
                          RadioSettingValueBoolean(_settings.beep))
        basic.append(rs)

        rs = RadioSetting("busy_led", "Busy LED",
                          RadioSettingValueBoolean(_settings.busy_led))
        basic.append(rs)

        rs = RadioSetting("bclo", "Busy Channel Lock-Out",
                          RadioSettingValueBoolean(_settings.bclo))
        basic.append(rs)

        rs = RadioSetting("wx_alert", "WX Alert",
                          RadioSettingValueBoolean(_settings.wx_alert))
        basic.append(rs)

        rs = RadioSetting("att", "Attenuator",
                          RadioSettingValueBoolean(_settings.att))
        basic.append(rs)

        # ARTS

        val = RadioSettingValueList(
            self._ARTS_INT, self._ARTS_INT[_settings.arts_interval])
        rs = RadioSetting("arts_interval", "ARTS Interval", val)
        arts.append(rs)

        val = RadioSettingValueList(
            self._ARTSBEEP, self._ARTSBEEP[_settings.arts_beep])
        rs = RadioSetting("arts_beep", "ARTS Beep", val)
        arts.append(rs)

        rs = RadioSetting("arts_cwid", "ARTS Send CWID",
                          RadioSettingValueBoolean(_settings.arts_cwid))
        arts.append(rs)

        val = RadioSettingValueString(0, 16,
                                      self._decode_chars(
                                          _settings.arts_cwid_alpha))
        val.set_charset(CHARSET)
        rs = RadioSetting("arts_cwid_alpha", "ARTS CW ID", val)
        arts.append(rs)

        # DTMF

        val = RadioSettingValueList(
            self._MAN_AUTO, self._MAN_AUTO[_settings.dtmf_autodial])
        rs = RadioSetting("dtmf_autodial", "DTMF Autodial", val)
        dtmf.append(rs)

        val = RadioSettingValueList(
            self._NUM_0_9, self._NUM_0_9[_settings.last_dtmf])
        rs = RadioSetting("last_dtmf", "Last DTMF Memory Set", val)
        dtmf.append(rs)

        for i in range(10):
            name = "dtmf_" + str(i)
            dtmfsetting = self._memobj.dtmf[i]
            dtmfstr = ""
            for c in dtmfsetting.memory:
                if c < len(DTMFCHARSET):
                    dtmfstr += DTMFCHARSET[c]
            dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
            rs = RadioSetting(name, name.upper(), dtmfentry)
            dtmf.append(rs)

        # WIRES

        val = RadioSettingValueList(
            self._INT_CD, self._INT_CD[_settings.internet_code])
        rs = RadioSetting("internet_code", "Internet Code", val)
        wires.append(rs)

        val = RadioSettingValueList(
            self._INT_MD, self._INT_MD[_settings.internet_mode])
        rs = RadioSetting("internet_mode",
                          "Internet Link Connection mode", val)
        wires.append(rs)

        val = RadioSettingValueList(
            self._MAN_AUTO, self._MAN_AUTO[_settings.int_autodial])
        rs = RadioSetting("int_autodial", "Internet Autodial", val)
        wires.append(rs)

        val = RadioSettingValueList(
            self._NUM_0_63, self._NUM_0_63[_settings.last_internet_dtmf])
        rs = RadioSetting("last_internet_dtmf",
                          "Last Internet DTMF Memory Set", val)
        wires.append(rs)

        for i in range(64):
            name = "wires_dtmf_" + str(i)
            dtmfsetting = self._memobj.internet_dtmf[i]
            dtmfstr = ""
            for c in dtmfsetting.memory:
                if c < len(DTMFCHARSET):
                    dtmfstr += DTMFCHARSET[c]
            dtmfentry = RadioSettingValueString(0, 8, dtmfstr)
            rs = RadioSetting(name, name.upper(), dtmfentry)
            wires.append(rs)

        # MISC

        val = RadioSettingValueList(
            self._BELL, self._BELL[_settings.bell])
        rs = RadioSetting("bell", "CTCSS/DCS Bell", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._CH_CNT, self._CH_CNT[_settings.channel_counter_width])
        rs = RadioSetting("channel_counter_width",
                          "Channel Counter Search Width", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._EMERGENCY, self._EMERGENCY[_settings.emergency])
        rs = RadioSetting("emergency", "Emergency alarm", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._ON_TIMER, self._ON_TIMER[_settings.on_timer])
        rs = RadioSetting("on_timer", "On Timer", val)
        misc.append(rs)

        rs = RadioSetting("pager_answer_back", "Pager Answer Back",
                          RadioSettingValueBoolean(
                              _settings.pager_answer_back))
        misc.append(rs)

        val = RadioSettingValueList(
            self._NUM_1_50, self._NUM_1_50[_settings.pager_rx_tone1])
        rs = RadioSetting("pager_rx_tone1", "Pager RX Tone 1", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._NUM_1_50, self._NUM_1_50[_settings.pager_rx_tone2])
        rs = RadioSetting("pager_rx_tone2", "Pager RX Tone 2", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._NUM_1_50, self._NUM_1_50[_settings.pager_tx_tone1])
        rs = RadioSetting("pager_tx_tone1", "Pager TX Tone 1", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._NUM_1_50, self._NUM_1_50[_settings.pager_tx_tone2])
        rs = RadioSetting("pager_tx_tone2", "Pager TX Tone 2", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._PTT_DELAY, self._PTT_DELAY[_settings.ptt_delay])
        rs = RadioSetting("ptt_delay", "PTT Delay", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._RF_SQL, self._RF_SQL[_settings.rf_squelch])
        rs = RadioSetting("rf_squelch", "RF Squelch", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._RX_SAVE, self._RX_SAVE[_settings.rx_save])
        rs = RadioSetting("rx_save", "RX Save", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._TOT, self._TOT[_settings.tx_timeout])
        rs = RadioSetting("tx_timeout", "TOT", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._WAKEUP, self._WAKEUP[_settings.wakeup])
        rs = RadioSetting("wakeup", "Wakeup", val)
        misc.append(rs)

        rs = RadioSetting("edge_beep", "Band-Edge Beep",
                          RadioSettingValueBoolean(_settings.edge_beep))
        misc.append(rs)

        val = RadioSettingValueList(
            self._VFO_MODE, self._VFO_MODE[_settings.vfo_mode])
        rs = RadioSetting("vfo_mode", "VFO Band Edge Limiting", val)
        misc.append(rs)

        rs = RadioSetting("tone_search_mute", "Tone Search Mute",
                          RadioSettingValueBoolean(_settings.tone_search_mute))
        misc.append(rs)

        val = RadioSettingValueList(
            self._TS_SPEED, self._TS_SPEED[_settings.ts_speed])
        rs = RadioSetting("ts_speed", "Tone Search Speed", val)
        misc.append(rs)

        rs = RadioSetting("dmr_wrt", "Direct Memory Recall Overwrite",
                          RadioSettingValueBoolean(_settings.dmr_wrt))
        misc.append(rs)

        rs = RadioSetting("tx_saver", "TX Battery Saver",
                          RadioSettingValueBoolean(_settings.tx_saver))
        misc.append(rs)

        val = RadioSettingValueList(
            self._SMART_SEARCH, self._SMART_SEARCH[_settings.smart_search])
        rs = RadioSetting("smart_search", "Smart Search", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._HOME_REV, self._HOME_REV[_settings.home_rev])
        rs = RadioSetting("home_rev", "HM/RV(EMG)R/H key", val)
        misc.append(rs)

        val = RadioSettingValueList(
            self._MEM_W_MD, self._MEM_W_MD[_settings.memory_method])
        rs = RadioSetting("memory_method", "Memory Write Method", val)
        misc.append(rs)

        return top

    def get_settings(self):
        try:
            return self._get_settings()
        except:
            import traceback
            print(traceback.format_exc())
            return None

    def set_settings(self, uisettings):
        for element in uisettings:
            if not isinstance(element, RadioSetting):
                self.set_settings(element)
                continue
            if not element.changed():
                continue
            try:
                setting = element.get_name()
                _settings = self._memobj.settings
                if re.match(r'internet_dtmf_\d', setting):
                    # set dtmf fields
                    dtmfstr = str(element.value).strip()
                    newval = []
                    for i in range(0, 8):
                        if i < len(dtmfstr):
                            newval.append(DTMFCHARSET.index(dtmfstr[i]))
                        else:
                            newval.append(0xFF)
                    idx = int(setting[-1:])
                    _settings = self._memobj.internet_dtmf[idx]
                    _settings.memory = newval
                    continue
                elif re.match(r'dtmf_\d', setting):
                    # set dtmf fields
                    dtmfstr = str(element.value).strip()
                    newval = []
                    for i in range(0, 16):
                        if i < len(dtmfstr):
                            newval.append(DTMFCHARSET.index(dtmfstr[i]))
                        else:
                            newval.append(0xFF)
                    idx = int(setting[-1:])
                    _settings = self._memobj.dtmf[idx]
                    _settings.memory = newval
                    continue
                oldval = getattr(_settings, setting)
                newval = element.value
                if setting == "arts_cwid_alpha":
                    newval = self._encode_chars(newval)
                elif setting == "open_message":
                    newval = self._encode_chars(newval, 6)
                elif setting == "password":
                    newval = self._encode_chars(newval, 4)
                setattr(_settings, setting, newval)
            except Exception:
                raise
