# Copyright 2025 Mike Iacovacci <ascendr@linuxmail.org>
#
# 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 2 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 struct
import logging
import time
from datetime import datetime
from chirp import chirp_common, directory, memmap
from chirp import bitwise, errors
from chirp.settings import RadioSetting, RadioSettingGroup, \
    RadioSettingValueInteger, RadioSettingValueList, \
    RadioSettingValueBoolean, RadioSettingValueString, \
    RadioSettings, RadioSettingValueMap, RadioSettingSubGroup

LOG = logging.getLogger(__name__)

MEM_FORMAT = """
# seekto 0x0050;
struct{
    char        soft_ver[7];
    u8          pad_0x57;
    char        hard_ver[6];
    u8          pad_0x5e_f[2];
    char        last_prog[16];
} dev_info;

# seekto 0x0080;
struct{
    lbcd        rx_freq[4];
    lbcd        tx_freq[4];
    bbcd        rx_tone[2];
    bbcd        tx_tone[2];
    u8          unk_0x0d[4];
    u8          power:1,
                unk_0x10:1,
                bandwidth:1,
                unk1_0x10:3,
                invert:1,
                talkaround:1;
    u8          pttid_mode:4,
                sqlch_mode:4;
    u8          signaling:4,
                jump_mode:4;
    u8          unk_0x13:2,
                launch_banned:1,
                unk1_0x13:5;
    u8          unk_0x14_18[5];
    u8          dtmf;
    u8          twotone;
    u8          unk_0x1b[1];
    u8          mdc;
    u8          unk_0x1d[1];
    u8          emerg_sys;
    u8          unk_0x1f[1];
    char        name[16];
} memory[640];

# seekto 0x7980;
struct{
    u8          band_a_wrkmode;
    u8          band_b_wrkmode;
    u8          unk_0x82_0x85[4];
    u8          band_a_workzone;
    u8          band_b_workzone;
    u8          backlight_tm;
    u8          tone_lvl;
    u8          band_a_chandisp:4,
                band_b_chandisp:4;
    u8          dbl_waiting;
    u8          main_band;
    u8          squelch;
    u8          vox_lvl;
    u8          vox_dly_detect;
    u8          power_save_off;
    u8          power_save_time;
    u8          lone_wrk_remind;
    u8          lone_wrk_resp;
    u8          apo;
    u8          tot;
    u8          tot_alert;
    u8          unk_0x97;
    u8          gps_zone;
    u8          unk_0x99;
    u8          hz1750;
    u8          unk_0x9b_9d[3];
    u8          noaa_chan;
    u8          unk_0x_9f;
    u8          vox:1,
                aprs:1,
                alone_worker:1,
                falling_alarm:1,
                voice_announce:2,
                unk_0xa0:1,
                busy_lock:1;
    u8          auto_keypad;
    u8          tone;
    u8          unk_0xa3;
    u8          gps;
    u8          power_on_char:1,
                power_on_volt:1,
                language:1,
                wx:1,
                eng_mode:1,
                unk_0xa5:3;
    u8          tail_mode:3,
                noaa:1,
                disp_reverse:1,
                unk_0xa6:2,
                enhance_func:1;
} settings;

# seekto 0x79b0;
struct{
    u8      pf1_short;
    u8      pf2_short;
    u8      pf1_long;
    u8      pf2_long;
}prog_key;

# seekto 0x79c0;
struct{
    u8      power_on_pwd[8];
    u8      program_pwd[8];
    u8      power_on_char[8];
} power_on;

# seekto 0x7a20;
struct{
    lbit      bitfield[640];
} chan_empty;

# seekto 0x8102;
struct{
    lbcd        upper_freq[2];
    u8          sc_pad[2];
    lbcd        lower_freq[2];
} scan_freq;

# seekto 0x8180;
struct{
    u8      scan_mode;
    u8      flyback;
    u8      rx_recovery;
    u8      tx_recovery;
    u8      channel_rtn;
    u8      priority;
    u8      unk_0x8186[1];
    u8      prio_scan_chan;
    u8      scan_range;
} scan_menu;

# seekto 0x81a0;
struct{
    lbit      bitfield[640];
} scan_skip;

# seekto 0x8200;
struct{
    u8      dtmf_ani;
    u8      sending_rate;
    u8      first_tm_code;
    u8      precarrier_tm;
    u8      delay_tm;
    u8      ptt_pause_tm;
    u8      dtmf_st;
    u8      auto_reset_tm;
    u8      sepr_opts;
    u8      group_num;
    u8      decode_resp;
    u8      unk_0xb_f[5];
    u8      self_id[3];
    u8      unk_0x13_17[5];
    u8      ptt_id[16];
    u8      ptt_id_offline[16];
    u8      stun[11];
    u8      pad_0x43_47[5];
    u8      kill[11];
}dtmf;

# seekto 0x8260;
struct{
    u8      entry[16];
} dtmf_list[16];

# seekto 0x9e00;
struct{
    char        selfid[6];
    u8          self_ssid;
    u8          self_pad;
    char        targetid[6];
    u8          target_ssid;
    u8          target_pad;
    u8          precarrier_tm;
    u8          code_dly_tm;

} aprs_menu;

# seekto 0x9e18;
struct{
    char        entry[6];
    u8          ssid;
    u8          unused;
} ssid_tbl[8];

# seekto 0xa010;
struct{
    u8          code;
    u8          unused2;
    char        name[14];
} contacts[80];
"""

ADDRS = [0x000000, 0x100000, 0x200000, 0x300000,
         0x400000, 0x500000, 0x600000, 0x700000,
         0x800000, 0x900000, 0xa00000]

BLK_SZ = 0x1000


def raw_send(serial, data, exlen):
    serial.write(data)
    return (serial.read(exlen))


def wakeup(serial):
    cmd = struct.pack('>QLL', 0, 0, 0xffffffff)
    raw_send(serial, cmd, 0)
    time.sleep(.5)
    resp = raw_send(serial, cmd, 1)
    if len(resp) != 1 or resp != b'\x41':
        err = f"Radio Wakeup error expected 0x41 got {resp}"
        LOG.error(err)
        raise errors.RadioError(err)


def exit_prog(serial, xor):
    ba = [b ^ xor for b in b'END\x00']
    resp = raw_send(serial, bytes(ba), 1)
    if (resp[0] ^ xor) != 0x41:
        err = f"Exit Prog expect 0x41 got {resp.hex()}"
        LOG.error(err)


def do_init(serial, xor):
    cmd = b'PROGRAM' + bytes([xor])
    resp = raw_send(serial, cmd, 1)
    if (resp[0] ^ xor) != 0x41:
        err = f"Init Error expected 0x41 got {resp}"
        LOG.error(err)
        raise errors.RadioError(err)
    xb = xor ^ 0xff
    cmd = bytes([xb] * 8)
    resp = raw_send(serial, cmd, 1)
    if (resp[0] ^ xor) != 0x41:
        err = f"Init Error expected 0x41 got {resp}"
        LOG.error(err)
        raise errors.RadioError(err)
    xs = [(xor ^ ord(c)) for c in 'INFORMATION']
    resp = raw_send(serial, bytes(xs), 16)
    id = [chr(b ^ xor) for b in resp[:6]]
    if "".join(id) != 'BF-5RH':
        err = f"Radio Mismatch got {id}"
        LOG.error(err)
        raise errors.RadioError(err)
    resp = raw_send(serial, bytes(0x52 ^ xor), 1)
    if (resp[0] ^ xor) != 0x41:
        err = f"Init Error expected 0x41 got 0x{resp.hex()}"
        LOG.error(err)
        raise errors.RadioError(err)


def read_block(serial, xor, addr, blk_len):
    i_cmd = (0x52000000 + addr) ^ (0x01010101 * xor)
    cmd = struct.pack('>L', i_cmd)
    resp = raw_send(serial, cmd, blk_len + 4)
    i_resp = struct.unpack('>L', resp[:4])[0]
    if i_resp != (0x57000000 + addr) ^ (0x1010101 * xor):
        err = "Read block echo failed"
        LOG.error(err)
        raise errors.RadioError(err)
    return resp[4:]


def write_block(serial, xor, addr, data, blk_len):
    i_cmd = (0x57000000 + addr) ^ (0x01010101 * xor)
    cmd = struct.pack('>L', i_cmd)
    with open('testwrite.img', 'ab') as f:
        f.write(data)
    resp = raw_send(serial, cmd + data, 1)
    if (resp[0] ^ xor) != 0x41:
        err = f"Write block ack failed expect 0x41 got {resp[0]}"
        LOG.error(err)
        raise errors.RadioError(err)
    return blk_len


def do_download(radio):
    data = b''
    try:
        xor = 0x00
        serial = radio.pipe
        serial.timeout = 5.0
        status = chirp_common.Status()
        status.msg = "Connecting to Radio..."
        radio.status_fn(status)
        wakeup(serial)
        do_init(serial, xor)
        status.max = len(ADDRS) * BLK_SZ
        status.msg = "Downloading..."
        for addr in ADDRS:
            resp = read_block(serial, xor, addr, BLK_SZ)
            da = [b ^ xor for b in resp]
            data += bytes(da)
            status.cur += len(resp)
            radio.status_fn(status)
    except errors.RadioError :
        raise
    except Exception as e:
        LOG.error(f"Error downloading {e}")
    finally:
        exit_prog(serial, xor)
    return memmap.MemoryMapBytes(data)


def do_upload(radio):
    try:
        fmt = "%Y.%m.%d %H:%M"
        prog_dt = datetime.now().strftime(fmt)
        radio._memobj.dev_info.last_prog = prog_dt
        xor = 0x26
        serial = radio.pipe
        serial.timeout = 5.0
        status = chirp_common.Status()
        status.msg = "Connecting to Radio..."
        radio.status_fn(status)
        wakeup(serial)
        do_init(serial, xor)
        status.max = len(ADDRS) * BLK_SZ
        status.msg = "Uploading..."
        for i, addr in enumerate(ADDRS):
            si = BLK_SZ * i
            ei = BLK_SZ * (i+1)
            data = radio._mmap[si:ei]
            enc_data = bytes([b ^ xor for b in data])
            resp = write_block(serial, xor, addr, enc_data, BLK_SZ)
            status.cur += resp
            radio.status_fn(status)
    except errors.RadioError:
        raise
    except Exception as e:
        LOG.error(f"Error uploading {e}")
    finally:
        exit_prog(serial, xor)


@directory.register
class BF5RHPro(chirp_common.CloneModeRadio):
    """Baofeng BF-5RH Pro GPS"""
    VENDOR = "Baofeng"
    MODEL = "BF-5RH-Pro"
    BAUD_RATE = 19200
    POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=0.50),
                    chirp_common.PowerLevel("High", watts=3.00)]
    VALID_MODES = ["NFM", "FM"]
    VALID_BANDS = [(136000000, 174000000), (400000000, 470000000)]
    VALID_TONES = (63.0, ) + chirp_common.TONES
    VALID_DCS = (17, 50, 645) + chirp_common.DTCS_CODES
    VALID_CHARSET = "".join(chr(i) for i in range(32, 127))
    VALID_DTMF = [str(i) for i in range(0, 10)] + \
        ["A", "B", "C", "D", "*", "#"] + [' ']
    ASCII_NUM = [str(i) for i in range(10)] + [' ']

    SSID_OPMAP = [('None', 255), ('0', 0), ('-1', 1), ('-2', 2),
                  ('-3', 3), ('-4', 4), ('-5', 5), ('-6', 6),
                  ('-7', 7), ('-8', 8), ('-9', 9), ('-10', 10),
                  ('-11', 11), ('-12', 12), ('-13', 13),
                  ('-14', 14), ('-15', 15)]

    def get_features(self):
        rf = chirp_common.RadioFeatures()
        rf.has_settings = True
        rf.has_bank = False
        rf.has_tuning_step = False
        rf.has_name = True
        rf.valid_characters = self.VALID_CHARSET
        rf.valid_name_length = 6
        rf.has_offset = True
        rf.has_mode = True
        rf.has_dtcs = True
        rf.has_rx_dtcs = True
        rf.has_dtcs_polarity = True
        rf.has_ctone = True
        rf.has_cross = True
        rf.valid_tones = self.VALID_TONES
        rf.can_odd_split = False
        rf.can_delete = True
        rf.valid_modes = self.VALID_MODES
        rf.valid_duplexes = ["", "-", "+", "off"]
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
        rf.valid_cross_modes = [
            "Tone->DTCS",
            "DTCS->Tone",
            "->Tone",
            "Tone->Tone",
            "->DTCS",
            "DTCS->",
            "DTCS->DTCS"]
        rf.valid_power_levels = self.POWER_LEVELS
        rf.valid_skips = ["", "S"]
        rf.valid_bands = self.VALID_BANDS
        rf.memory_bounds = (1, 640)
        return rf

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

    def sync_in(self):
        try:
            data = do_download(self)
        except Exception as e:
            err = f'Error during download {e}'
            LOG.error(err)
            raise errors.RadioError(err)
        self._mmap = data
        self.process_mmap()

    def sync_out(self):
        try:
            do_upload(self)
        except Exception as e:
            raise errors.RadioError(f"Error during upload {e}")

    def get_memory(self, number):
        mem = chirp_common.Memory()
        mem.number = number
        _mem = self._memobj.memory[number-1]
        mem.freq = int(_mem.rx_freq) * 10
        if self.get_empty(number - 1):
            mem.empty = True
            return mem
        _name = "".join(str(c) if str(
            c) in self.VALID_CHARSET else ' ' for c in _mem.name)
        mem.name = _name.rstrip()
        mem.power = self.POWER_LEVELS[_mem.power]
        mem.mode = self.VALID_MODES[_mem.bandwidth]
        mem.skip = 'S' if self.get_skip(number - 1) else ""

        if int(_mem.tx_freq) == int(_mem.rx_freq):
            mem.duplex = ''
            mem.offset = 0
        elif int(_mem.tx_freq) == 0:
            mem.duplex = 'off'
            mem.offset = 0
        elif int(_mem.tx_freq) > int(_mem.rx_freq):
            mem.duplex = '+'
            mem.offset = (int(_mem.tx_freq) - int(_mem.rx_freq)) * 10
        elif int(_mem.rx_freq) > int(_mem.tx_freq):
            mem.duplex = '-'
            mem.offset = ((int(_mem.rx_freq) - int(_mem.tx_freq)) * 10)

        self.get_tones(_mem, mem)

        mem.extra = RadioSettingGroup("Extra", "extra")

        rstype = RadioSettingValueBoolean(_mem.invert)
        rs = RadioSetting("invert", "TX Invert", rstype)
        mem.extra.append(rs)

        rstype = RadioSettingValueBoolean(_mem.talkaround)
        rs = RadioSetting("talkaround", "Talkaround", rstype)
        mem.extra.append(rs)

        _options = [('Off', 0), ('PTT BOT', 1), ('PTT EOT', 2),
                    ('PTT Both', 3), ('5-Tone BOT', 4),
                    ('5-Tone EOT', 8), ('5-Tone Both', 12)]
        rstype = RadioSettingValueMap(_options, _mem.pttid_mode)
        rs = RadioSetting("pttid_mode", "PTT/5-Tone ID", rstype)
        mem.extra.append(rs)

        _options = [('Off', 0), ('CTDCS', 1), ('Optional', 2),
                    ('Both', 3)]
        rstype = RadioSettingValueMap(_options, _mem.sqlch_mode)
        rs = RadioSetting("sqlch_mode", "Squelch Mode", rstype)
        mem.extra.append(rs)

        _options = [('Off', 0), ('DTMF', 2), ('2-Tone', 4),
                    ('5-Tone', 6), ('MDC', 8), ('BIIS', 12)]
        rstype = RadioSettingValueMap(_options, _mem.signaling)
        rs = RadioSetting("signaling", "Signaling", rstype)
        mem.extra.append(rs)

        _options = [('Off', 0), ('Normal', 1), ('Strict', 2)]
        rstype = RadioSettingValueMap(_options, _mem.jump_mode)
        rs = RadioSetting("jump_mode", "Jump Freq.", rstype)
        mem.extra.append(rs)

        rstype = RadioSettingValueBoolean(_mem.launch_banned)
        rs = RadioSetting("launch_banned", "Launch Banned", rstype)
        mem.extra.append(rs)

        _options = [str(i) for i in range(16+1)]
        rstype = RadioSettingValueList(options=_options,
                                       current_index=int(_mem.dtmf))
        rs = RadioSetting("dtmf", "DTMF Index", rstype)
        mem.extra.append(rs)

        rstype = RadioSettingValueList(options=_options,
                                       current_index=int(_mem.twotone))
        rs = RadioSetting("twotone", "2-Tone Index", rstype)
        mem.extra.append(rs)

        _options = [str(i) for i in range(5+1)]
        rstype = RadioSettingValueList(options=_options,
                                       current_index=int(_mem.mdc))
        rs = RadioSetting("mdc", "MDC Index", rstype)
        mem.extra.append(rs)

        _options = [str(i) for i in range(10+1)]
        rstype = RadioSettingValueList(options=_options,
                                       current_index=int(_mem.emerg_sys))
        rs = RadioSetting("emerg_sys", "Emergency System", rstype)
        mem.extra.append(rs)

        return mem

    def set_memory(self, mem):

        _mem = self._memobj.memory[mem.number - 1]

        if mem.empty:
            self.set_empty(mem.number - 1, 1)
            _mem.fill_raw(b'\x00')
            return

        _mem.rx_freq = mem.freq // 10

        if self.get_empty(mem.number - 1):
            #new memory
            self.set_empty(mem.number - 1, 0)
            mem.duplex == ''
            _mem.tx_freq.set_value(int(_mem.rx_freq))
            _mem.power = 1
        
        _name = [c for c in mem.name]
        if len(_name) < 16:
            _name = _name + [' '] * (16 - len(_name))
        _mem.name = _name
        
        _skip = 1 if mem.skip == 'S' else 0
        self.set_skip(mem.number - 1, _skip)

        _mem.bandwidth = self.VALID_MODES.index(mem.mode)
        self.set_tone(_mem, mem)
        
        if mem.duplex == '+':
            _mem.tx_freq = (mem.freq + mem.offset) // 10
        elif mem.duplex == '-':
            _mem.tx_freq = (mem.freq - mem.offset) // 10
        elif mem.duplex == '':
            _mem.tx_freq.set_value(int(_mem.rx_freq))
        elif mem.duplex == 'off':
            _mem.tx_freq.set_value(0)

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

    def get_tones(self, _mem, mem):
        _memrxtone = int(_mem.rx_tone)
        msb = _mem.rx_tone.get_raw()[0]
        if _memrxtone == 16665 or _memrxtone == 0:
            rxtone = ("", 0, None)
        elif 0x80 <= msb < 0xc0:
            # DCS Normal
            rxtone = ("DTCS", _memrxtone % 8000, "N")
        elif msb >= 0xc0:
            # DCS Inverted
            rxtone = ("DTCS", _memrxtone % 12000, "R")
        else:
            # CTCSS
            rxtone = ("Tone",  _memrxtone / 10, None)
        _memtxtone = int(_mem.tx_tone)
        msb = _mem.tx_tone.get_raw()[0]
        if _memtxtone == 16665 or _memtxtone == 0:
            txtone = ("", 0, None)
        elif 0x80 <= msb < 0xc0:
            # DCS Normal
            txtone = ("DTCS",  _memtxtone % 8000, "N")
        elif msb >= 0xc0:
            # DCS Inverted
            txtone = ("DTCS",  _memtxtone % 12000, "R")
        else:
            # CTCSS
            txtone = ("Tone",  _memtxtone / 10, None)
        chirp_common.split_tone_decode(mem, txtone, rxtone)

    def set_tone(self, _mem, mem):
        ((txmode, txval, txpol),
         (rxmode, rxval, rxpol)) = chirp_common.split_tone_encode(mem)
        if txmode == "":
            _mem.tx_tone.set_raw(b'\xff\xff')
        if rxmode == "":
            _mem.rx_tone.set_raw(b'\xff\xff')
        if txmode == "Tone":
            _mem.tx_tone = int(txval * 10)
        if rxmode == "Tone":
            _mem.rx_tone = int(rxval * 10)
        if txmode == "DTCS" and txpol == "N":
            _mem.tx_tone = int(txval + 8000)
        if rxmode == "DTCS" and rxpol == "N":
            _mem.rx_tone = int(rxval + 8000)
        if txmode == "DTCS" and txpol == "R":
            msb = (txval // 100) + 0xc0
            lsb = int("%02i" % (txval % 100), 16)
            _mem.tx_tone.set_raw(bytes([msb, lsb]))
        if rxmode == "DTCS" and rxpol == "R":
            msb = (rxval // 100) + 0xc0
            lsb = int("%02i" % (rxval % 100), 16)
            _mem.rx_tone.set_raw(bytes([msb, lsb]))

    def get_skip(self, chan_n):
        return self._memobj.scan_skip['bitfield'][chan_n].get_value()

    def set_skip(self, chan_n, value):
        self._memobj.scan_skip['bitfield'][chan_n].set_value(value)

    def get_empty(self, chan_n):
        return self._memobj.chan_empty['bitfield'][chan_n].get_value()
    
    def set_empty(self, chan_n, value):
        self._memobj.chan_empty['bitfield'][chan_n].set_value(value)

    def get_settings(self):
        _dev_info = self._memobj.dev_info
        _settings = self._memobj.settings
        _prog_key = self._memobj.prog_key
        _dtmf = self._memobj.dtmf
        _dtmf_list = self._memobj.dtmf_list
        _ssid_tbl = self._memobj.ssid_tbl
        _scan_freq = self._memobj.scan_freq
        _scan_menu = self._memobj.scan_menu
        _aprs_menu = self._memobj.aprs_menu
        _contacts = self._memobj.contacts

        def _idx_or_default(setting, _list, default):
            try:
                _list[setting]
                return setting
            except IndexError:
                return default

        info = RadioSettingGroup("info", "Device Info")
        group = RadioSettings(info)

        _current = "".join(str(c) for c in _dev_info.soft_ver)
        rs = RadioSettingValueString(minlength=7, maxlength=7,
                                     current=_current,
                                     charset=self.VALID_CHARSET,
                                     autopad=False)
        rs.set_mutable(False)
        rset = RadioSetting("dev_info.soft_ver", "Software Version", rs)
        info.append(rset)

        _current = "".join(str(c) for c in _dev_info.hard_ver)
        rs = RadioSettingValueString(minlength=6, maxlength=6,
                                     current=_current,
                                     charset=self.VALID_CHARSET,
                                     autopad=False)
        rs.set_mutable(False)
        rset = RadioSetting("dev_info.hard_ver", "Hardware Version", rs)
        info.append(rset)

        _current = "".join(str(c) for c in _dev_info.last_prog)
        rs = RadioSettingValueString(minlength=16, maxlength=16,
                                     current=_current,
                                     charset=self.VALID_CHARSET,
                                     autopad=False)
        rs.set_mutable(False)
        rset = RadioSetting("dev_info.last_prog", "Last Programmed", rs)
        info.append(rset)

        settings = RadioSettingGroup("settings", "Settings")
        group.append(settings)

        rs = RadioSettingValueInteger(minval=0, maxval=9,
                                      current=_settings.squelch, step=1)
        rset = RadioSetting("settings.squelch", "Squelch Level", rs)
        settings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.vox, mem_vals=(0, 1))
        rset = RadioSetting("settings.vox", "VOX", rs)
        settings.append(rset)

        a = ['n/a'] + [str(i/10) for i in range(10, 101, 5)]
        b = [0] + list(range(10, 100+1, 5))
        _opmap = list(zip(a, b))
        rs = RadioSettingValueMap(_opmap, _settings.vox_dly_detect)
        rset = RadioSetting("settings.vox_dly_detect",
                            "VOX Delay Detect(s)", rs)
        settings.append(rset)

        rs = RadioSettingValueInteger(minval=0, maxval=9,
                                      current=_settings.vox_lvl, step=1)
        rset = RadioSetting("settings.vox_lvl", "Vox Level", rs)
        settings.append(rset)

        _opmap = [('1', 0), ('2', 1), ('3', 2), ('4', 3), ('5', 4)]
        rs = RadioSettingValueMap(_opmap, _settings.tone_lvl)
        rset = RadioSetting("settings.tone_lvl", "Tone Level", rs)
        settings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.wx, mem_vals=(0, 1))
        rset = RadioSetting("settings.wx", "WX", rs)
        settings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.noaa, mem_vals=(0, 1))
        rset = RadioSetting("settings.noaa", "NOAA", rs)
        settings.append(rset)

        _options = [str(i) for i in range(1, 10+1)]
        rs = RadioSettingValueList(
            _options, current_index=_settings.noaa_chan)
        rset = RadioSetting("settings.noaa_chan",
                            "NOAA Channel", rs)
        settings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.gps, mem_vals=(0, 1))
        rset = RadioSetting("settings.gps", "GPS", rs)
        settings.append(rset)

        _options = [str(i) for i in range(-12, 13, 1)]
        rs = RadioSettingValueList(
            _options, current_index=_settings.gps_zone)
        rset = RadioSetting("settings.gps_zone",
                            "GPS Zone", rs)
        settings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.aprs, mem_vals=(0, 1))
        rset = RadioSetting("settings.aprs", "APRS", rs)
        settings.append(rset)

        _options = ['Off', 'Chinese', 'English']
        rs = RadioSettingValueList(
            _options, current_index=_settings.voice_announce)
        rset = RadioSetting("settings.voice_announce",
                            "Voice Announce", rs)
        settings.append(rset)

        _options = ['Freq', 'Name', 'Number', 'Freq. + Name']
        rs = RadioSettingValueList(
            _options, current_index=_settings.band_a_chandisp)
        rset = RadioSetting("settings.band_a_chandisp",
                            "Band A Chan. Display", rs)
        settings.append(rset)

        _options = ['Freq', 'Name', 'Number', 'Freq. + Name']
        rs = RadioSettingValueList(
            _options, current_index=_settings.band_b_chandisp)
        rset = RadioSetting("settings.band_b_chandisp",
                            "Band B Chan. Display", rs)
        settings.append(rset)

        _options = ['A', 'B']
        rs = RadioSettingValueList(
            _options, current_index=_settings.main_band)
        rset = RadioSetting("settings.main_band",
                            "Main Band", rs)
        settings.append(rset)

        _options = ['VFO', 'MR']
        rs = RadioSettingValueList(
            _options, current_index=_settings.band_a_wrkmode)
        rset = RadioSetting("settings.band_a_wrkmode",
                            "Band A Mode", rs)
        settings.append(rset)

        _options = ['VFO', 'MR']
        rs = RadioSettingValueList(
            _options, current_index=_settings.band_b_wrkmode)
        rset = RadioSetting("settings.band_b_wrkmode",
                            "Band B Mode", rs)
        settings.append(rset)

        _options = ['1', '2']
        rs = RadioSettingValueList(
            _options, current_index=_settings.band_a_workzone)
        rset = RadioSetting("settings.band_a_workzone",
                            "Band A Zone", rs)
        settings.append(rset)

        _options = ['1', '2']
        rs = RadioSettingValueList(
            _options, current_index=_settings.band_b_workzone)
        rset = RadioSetting("settings.band_b_workzone",
                            "Band B Zone", rs)
        settings.append(rset)

        a = ['always'] + [str(i) for i in range(5, 31)]
        b = [0] + [i for i in range(5, 31)]
        _opmap = list(zip(a, b))
        rs = RadioSettingValueMap(_opmap, _settings.backlight_tm)
        rset = RadioSetting("settings.backlight_tm", "Backlight Time(s)", rs)
        settings.append(rset)

        _options = ['Off', '1:1', '1:2', '1:4']
        rs = RadioSettingValueList(
            _options, current_index=_settings.power_save_off)
        rset = RadioSetting("settings.power_save_off",
                            "Power Save Off", rs)
        settings.append(rset)

        _options = ['5', '10', '15', '20', '25']
        rs = RadioSettingValueList(
            _options, current_index=_settings.power_save_time)
        rset = RadioSetting("settings.power_save_time",
                            "Power Save Time", rs)
        settings.append(rset)

        _options = ['Off', 'Dual', 'Single']
        rs = RadioSettingValueList(
            _options, current_index=_settings.dbl_waiting)
        rset = RadioSetting("settings.dbl_waiting",
                            "Double Waiting (s)", rs)
        settings.append(rset)

        _options = ['Off', '30', '60', '120', '240', '480']
        rs = RadioSettingValueList(
            _options, current_index=_settings.apo)
        rset = RadioSetting("settings.apo",
                            "APO (min)", rs)
        settings.append(rset)

        _options = [str(i) for i in range(0, 211, 15)]
        rs = RadioSettingValueList(
            _options, current_index=_settings.tot)
        rset = RadioSetting("settings.tot",
                            "TOT (s)", rs)
        settings.append(rset)

        _options = [str(i) for i in range(0, 10+1)]
        rs = RadioSettingValueList(
            _options, current_index=_settings.tot_alert)
        rset = RadioSetting("settings.tot_alert",
                            "TOT Alert (s)", rs)
        settings.append(rset)

        _opmap = [("Off", 0), ("55Hz", 1), ("120deg", 2),
                  ("180deg", 3), ("240deg", 4)]
        rs = RadioSettingValueMap(_opmap, _settings.tail_mode)
        rset = RadioSetting("settings.tail_mode", "Tail Mode", rs)
        settings.append(rset)

        exsettings = RadioSettingSubGroup("exsettings",
                                          "Extra Settings")
        settings.append(exsettings)

        _options = ['1000Hz', '1450Hz', '1750Hz', '2100Hz']
        rs = RadioSettingValueList(
            _options, current_index=_settings.hz1750)
        rset = RadioSetting("settings.hz1750",
                            "1750Hz", rs)
        exsettings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.eng_mode, mem_vals=(0, 1))
        rset = RadioSetting("settings.eng_mode", "Engineering Mode", rs)
        exsettings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.falling_alarm, mem_vals=(0, 1))
        rset = RadioSetting("settings.falling_alarm", "Falling Alarm", rs)
        exsettings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.alone_worker, mem_vals=(0, 1))
        rset = RadioSetting("settings.alone_worker", "Alone Worker", rs)
        exsettings.append(rset)

        rs = RadioSettingValueBoolean(
            current=_settings.enhance_func, mem_vals=(0, 1))
        rset = RadioSetting("settings.enhance_func", "Enhance Function", rs)
        exsettings.append(rset)

        _options = ['STAN', 'Fail']
        rs = RadioSettingValueList(
            _options, current_index=_settings.disp_reverse)
        rset = RadioSetting("settings.disp_reverse",
                            "Display Reverse", rs)
        exsettings.append(rset)

        _options = ['English', 'Chinese']
        rs = RadioSettingValueList(_options, current_index=_settings.language)
        rset = RadioSetting("settings.language",
                            "Language", rs)
        exsettings.append(rset)

        _current = _settings.lone_wrk_remind
        rs = RadioSettingValueInteger(minval=0, maxval=255,
                                      current=_current, step=1)
        rset = RadioSetting("settings.lone_wrk_remind",
                            "Lone Worker Remind (s)", rs)
        exsettings.append(rset)

        _current = _settings.lone_wrk_resp
        rs = RadioSettingValueInteger(minval=0, maxval=255,
                                      current=_current, step=1)
        rset = RadioSetting("settings.lone_wrk_resp",
                            "Lone Worker Response (min)", rs)
        exsettings.append(rset)

        # SCAN SETTINGS
        scanmenu = RadioSettingGroup("scanmenu", "Scan Settings")
        group.append(scanmenu)

        def apply_freq(setting, mem_setting):
            new_value = int(setting.values()[0]) * 10
            mem_setting.set_value(new_value)

        _current = int(_scan_freq.upper_freq)
        rs = RadioSettingValueInteger(minval=1360, maxval=4700,
                                      current=_current)
        rset = RadioSetting("scan_freq.lower_freq", "Scan Lower (KHz)", rs)
        rset.set_apply_callback(apply_freq, _scan_freq.lower_freq)
        scanmenu.append(rset)
        _current = int(_scan_freq.upper_freq)
        rs = RadioSettingValueInteger(minval=1360, maxval=4700,
                                      current=_current)
        rset = RadioSetting("scan_freq.upper_freq", "Scan Upper (KHz)", rs)
        rset.set_apply_callback(apply_freq, _scan_freq.upper_freq)
        scanmenu.append(rset)

        _options = ['TO', 'CO', 'SO']
        rs = RadioSettingValueList(
            _options, current_index=_scan_menu.scan_mode)
        rset = RadioSetting("scan_menu.scan_mode", "Scan Mode", rs)
        scanmenu.append(rset)

        a = [str(i/10) for i in range(5, 50+1, 1)]
        b = list(range(5, 50+1))
        _opmap = list(zip(a, b))
        rs = RadioSettingValueMap(_opmap, _scan_menu.flyback)
        rset = RadioSetting("scan_menu.flyback", "Flyback Time", rs)
        scanmenu.append(rset)

        a = [str(i/10) for i in range(1, 50+1, 1)]
        b = list(range(1, 50+1))
        _opmap = list(zip(a, b))
        rs = RadioSettingValueMap(_opmap, _scan_menu.rx_recovery)
        rset = RadioSetting("scan_menu.rx_recovery",
                            "RX Recovery Delay (s)", rs)
        scanmenu.append(rset)

        rs = RadioSettingValueMap(_opmap, _scan_menu.tx_recovery)
        rset = RadioSetting("scan_menu.tx_recovery",
                            "TX Recovery Delay (s)", rs)
        scanmenu.append(rset)

        _options = ['selected', 'selected + current', 'last call received',
                    'last use ', 'priority', 'priority + current']
        rs = RadioSettingValueList(
            _options, current_index=_scan_menu.channel_rtn)
        rset = RadioSetting("scan_menu.channel_rtn", "Channel Return", rs)
        scanmenu.append(rset)

        rs = RadioSettingValueBoolean(
            current=_scan_menu.priority, mem_vals=(0, 1))
        rset = RadioSetting("scan_menu.priority", "Priority", rs)
        scanmenu.append(rset)

        a = [str(i) for i in range(1, 64+1)]
        b = list(range(64))
        _opmap = list(zip(a, b))
        _current = _idx_or_default(_scan_menu.prio_scan_chan, b, 0)
        rs = RadioSettingValueMap(_opmap, _current)
        rset = RadioSetting("scan_menu.prio_scan_chan",
                            "Priority Scan Channel", rs)
        scanmenu.append(rset)

        _options = ['All', 'Memory Scan']
        rs = RadioSettingValueList(_options,
                                   current_index=_scan_menu.scan_range)
        rset = RadioSetting("scan_menu.scan_range", "Scan Range", rs)
        scanmenu.append(rset)

        # PROG KEY SETTINGS
        progkey = RadioSettingGroup("progkey", "Program Key")
        group.append(progkey)

        _opmap = [("None", 0), ("Scan On/Off", 1), ("Monitor", 2),
                  ("FM Radio", 4), ("Emergency", 5), ("GPS", 6),
                  ("Freq. Measuring", 7), ("1750Hz", 9),
                  ("Falling Alarm", 10), ("One Touch Call", 11),
                  ("Zone Change", 12), ("Battery Indicator", 13),
                  ("Tx Power", 14), ("VOX On/Off", 15)]

        rs = RadioSettingValueMap(
            _opmap, _prog_key.pf1_short)
        rset = RadioSetting("prog_key.pf1_short", "PF1 Short", rs)
        progkey.append(rset)

        rs = RadioSettingValueMap(
            _opmap, _prog_key.pf1_long)
        rset = RadioSetting("prog_key.pf1_long", "PF1 Long", rs)
        progkey.append(rset)

        rs = RadioSettingValueMap(
            _opmap, _prog_key.pf2_short)
        rset = RadioSetting("prog_key.pf2_short", "PF2 Short", rs)
        progkey.append(rset)

        rs = RadioSettingValueMap(
            _opmap, _prog_key.pf2_long)
        rset = RadioSetting("prog_key.pf2_long", "PF2 Long", rs)
        progkey.append(rset)

        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
        group.append(dtmf)

        rs = RadioSettingValueBoolean(
            current=_dtmf.dtmf_ani, mem_vals=(0, 1))
        rset = RadioSetting("dtmf.dtmf_ani", "DTMF ANI", rs)
        dtmf.append(rset)

        _options = ['50', '100', '200', '300', '500']
        rs = RadioSettingValueList(
            _options, current_index=_dtmf.sending_rate)
        rset = RadioSetting("dtmf.sending_rate", "Sending Rate (ms)", rs)
        dtmf.append(rset)

        _options = [str(i) for i in range(0, 2501, 10)]
        rs = RadioSettingValueList(
            _options, current_index=_dtmf.first_tm_code)
        rset = RadioSetting("dtmf.first_tm_code", "First Time Code (ms)", rs)
        dtmf.append(rset)

        rs = RadioSettingValueList(
            _options, current_index=_dtmf.precarrier_tm)
        rset = RadioSetting("dtmf.precarrier_tm", "Precarrier Time (ms)", rs)
        dtmf.append(rset)

        a = [str(i) for i in range(10, 2501, 10)]
        b = list(range(1, 250+1))
        _opmap = list(zip(a, b))
        rs = RadioSettingValueMap(
            _opmap, _dtmf.delay_tm)
        rset = RadioSetting("dtmf.delay_tm", "Delay Time(ms)", rs)
        dtmf.append(rset)

        _options = [str(i) for i in range(0, 76, 5)]
        rs = RadioSettingValueList(
            _options, current_index=_dtmf.ptt_pause_tm)
        rset = RadioSetting("dtmf.ptt_pause_tm", "PTT Pause Time (s)", rs)
        dtmf.append(rset)

        rs = RadioSettingValueBoolean(current=_dtmf.dtmf_st, mem_vals=(0, 1))
        rset = RadioSetting("dtmf.dtmf_st", "DTMF-ST", rs)
        dtmf.append(rset)

        rs = RadioSettingValueInteger(minval=10, maxval=25,
                                      current=_dtmf.auto_reset_tm, step=1)
        rset = RadioSetting("dtmf.auto_reset_tm", "Auto Reset Time (s)", rs)
        dtmf.append(rset)

        _opmap = [("A", 0xa), ("B", 0xb), ("C", 0xc),
                  ("D", 0xd), ("*", 0xe), ("#", 0xf)]
        rs = RadioSettingValueMap(_opmap, _dtmf.sepr_opts)
        rset = RadioSetting("dtmf.sepr_opts", "Separator Option", rs)
        dtmf.append(rset)

        rs = RadioSettingValueMap(_opmap, _dtmf.group_num)
        rset = RadioSetting("dtmf.group_num", "Group Option", rs)
        dtmf.append(rset)

        _options = ['None', 'Tone', 'Tone Reply']
        rs = RadioSettingValueList(
            _options, current_index=_dtmf.decode_resp)
        rset = RadioSetting("dtmf.decode_resp", "Decode Response", rs)
        dtmf.append(rset)

        _current = "".join(str(int(c)) for c in _dtmf.self_id)
        rs = RadioSettingValueString(minlength=3, maxlength=3,
                                     current=_current,
                                     charset=[str(i) for i in range(10)],
                                     autopad=False)
        rset = RadioSetting("dtmf.self_id",
                            "Self ID", rs)
        dtmf.append(rset)

        def dtmf_xlate(setting):
            """ Translates * and # to ascii code E / F respectively"""
            s = ""
            for i in setting:
                if chr(i) == 'E':
                    s += '*'
                elif chr(i) == 'F':
                    s += '#'
                elif chr(i) in self.VALID_DTMF:
                    s += chr(i)
            return s

        _current = dtmf_xlate(_dtmf.ptt_id)
        rs = RadioSettingValueString(minlength=0, maxlength=16,
                                     current=_current,
                                     charset=self.VALID_DTMF)
        rset = RadioSetting("dtmf.ptt_id", "PTT ID Online", rs)
        dtmf.append(rset)

        _current = dtmf_xlate(_dtmf.ptt_id_offline)
        rs = RadioSettingValueString(minlength=0, maxlength=16,
                                     current=_current,
                                     charset=self.VALID_DTMF)
        rset = RadioSetting("dtmf.ptt_id_offline", "PTT ID Offine", rs)
        dtmf.append(rset)

        _current = dtmf_xlate(_dtmf.stun)
        rs = RadioSettingValueString(minlength=0, maxlength=11,
                                     current=_current,
                                     charset=self.VALID_DTMF)
        rset = RadioSetting("dtmf.stun", "STUN", rs)
        dtmf.append(rset)

        _current = dtmf_xlate(_dtmf.kill)
        rs = RadioSettingValueString(minlength=0, maxlength=11,
                                     current=_current,
                                     charset=self.VALID_DTMF)
        rset = RadioSetting("dtmf.kill", "KILL", rs)
        dtmf.append(rset)

        # DTMF Encode List
        dtmf_mem = RadioSettingGroup("dtmf_mem", "DTMF Encode List")
        group.append(dtmf_mem)

        for i in range(0, 16):  # 1-16
            rs = RadioSettingValueString(minlength=0, maxlength=16,
                                         current=dtmf_xlate(
                                             _dtmf_list[i].entry),
                                         charset=self.VALID_DTMF)
            rset = RadioSetting(f"dtmf_list[{i}].entry", f"Entry{i+1}", rs)
            dtmf_mem.append(rset)

        # APRS MENU
        aprs = RadioSettingGroup("aprs", "APRS Settings")
        group.append(aprs)

        _current = "".join(str(c) for c in _aprs_menu.selfid if str(
            c) in chirp_common.CHARSET_ALPHANUMERIC)
        rs = RadioSettingValueString(minlength=0, maxlength=6,
                                     current=_current,
                                     charset=chirp_common.CHARSET_ALPHANUMERIC)
        rset = RadioSetting("aprs_menu.selfid", "Self ID", rs)
        aprs.append(rset)

        _current = "".join(str(c) for c in _aprs_menu.targetid if str(
            c) in chirp_common.CHARSET_ALPHANUMERIC)
        rs = RadioSettingValueString(minlength=0, maxlength=6,
                                     current=_current,
                                     charset=chirp_common.CHARSET_ALPHANUMERIC)
        rset = RadioSetting("aprs_menu.targetid", "Target ID", rs)
        aprs.append(rset)

        _options = [str(i*-1) for i in range(16)]
        _idx = _idx_or_default(_aprs_menu.self_ssid, _current, 0)
        rs = RadioSettingValueList(
            _options, current_index=_idx)
        rset = RadioSetting("aprs_menu.self_ssid", "Self SSID", rs)
        aprs.append(rset)

        _idx = _idx_or_default(_aprs_menu.target_ssid, _current, 0)
        rs = RadioSettingValueList(
            _options, current_index=_idx)
        rset = RadioSetting("aprs_menu.target_ssid", "Target SSID", rs)
        aprs.append(rset)

        _options = [str(i) for i in range(0, 2551, 10)]
        rs = RadioSettingValueList(
            _options, current_index=_aprs_menu.precarrier_tm)
        rset = RadioSetting("aprs_menu.precarrier_tm",
                            "Precarrier Time (ms)", rs)
        aprs.append(rset)

        rs = RadioSettingValueList(
            _options, current_index=_aprs_menu.code_dly_tm)
        rset = RadioSetting("aprs_menu.code_dly_tm",
                            "Code Delay Time (ms)", rs)
        aprs.append(rset)

        # APRS SSID List
        ssid = RadioSettingGroup("ssid", "APRS List")
        group.append(ssid)
        for i in range(0, 8):  # 1-8
            rs = RadioSettingValueMap(self.SSID_OPMAP, _ssid_tbl[i].ssid)
            rset = RadioSetting(f"ssid_tbl[{i}].ssid", f"{i+1}: SSID", rs)
            ssid.append(rset)
            _charset = chirp_common.CHARSET_ALPHANUMERIC
            _current = "".join(str(c) for c in _ssid_tbl[i].entry if str(
                c) in chirp_common.CHARSET_ALPHANUMERIC)
            rs = RadioSettingValueString(minlength=0, maxlength=6,
                                         current=_current,
                                         charset=_charset)
            rset = RadioSetting(f"ssid_tbl[{i}].entry", "Call Sign", rs)
            ssid.append(rset)

        # Contacts List
        contacts = RadioSettingGroup("contacts", "Contacts List")
        group.append(contacts)

        for i in range(0, 80):  # 1-80
            rs = RadioSettingValueInteger(minval=0, maxval=255,
                                          current=_contacts[i].code)
            rset = RadioSetting(f"contacts[{i}].code", f"{i+1}:", rs)
            contacts.append(rset)
            _charset = self.VALID_CHARSET
            _current = "".join(
                str(c) for c in _contacts[i].name if str(c) in _charset)
            rs = RadioSettingValueString(minlength=0, maxlength=14,
                                         current=_current,
                                         charset=_charset)
            rset = RadioSetting(f"contacts[{i}].name", "Name", rs)
            contacts.append(rset)

        return group

    def _dtmf_set__mem(self, obj, setting, element):
        _dtmf_map = [str(i) for i in range(10)]
        _dtmf_map += ['A', 'B', 'C', 'D', '*', '#']
        _val = [0xff] * len(obj[setting])
        _os = 0  # offset _mem setting idx for pad chars
        for i in range(len(element.value)):
            if element.value[i] in _dtmf_map:
                _val[i - _os] = _dtmf_map.index(element.value[i])
            else:
                _os += 1
        setattr(obj, setting, _val)

    def _ssid_set__mem(self, obj, setting, element):
        _ssid_map = list(chirp_common.CHARSET_ALPHANUMERIC)
        _ssid_map.remove(' ')
        _val = [0] * len(obj[setting])
        _os = 0  # offset _mem setting idx for pad chars
        for i in range(len(element.value)):
            if element.value[i] in _ssid_map:
                _val[i - _os] = _ssid_map.index(element.value[i])
            else:
                _os += 1
        setattr(obj, setting, _val)

    def set_settings(self, settings):
        for element in settings:
            if not isinstance(element, RadioSetting):
                self.set_settings(element)
                continue
            else:
                try:
                    if "." in element.get_name():
                        toks = element.get_name().split(".")
                        obj = self._memobj
                        for tok in toks[:-1]:
                            if '[' in tok:
                                t, i = tok.split("[")
                                i = int(i[:-1])
                                obj = getattr(obj, t)[i]
                            else:
                                obj = getattr(obj, tok)
                        setting = toks[-1]
                    else:
                        obj = self._memobj.settings
                        setting = element.get_name()

                    if element.has_apply_callback():
                        LOG.debug("applying callback")
                        element.run_apply_callback()

                    if element.value.get_mutable():
                        if setting in ['ptt_id', 'ptt_id_offline',
                                       'stun', 'kill']:
                            self._dtmf_set__mem(obj, setting, element)
                        elif obj._name in ['ssid_tbl', 'dtmf_list']:
                            if obj._name == 'ssid_tbl':
                                if setting == 'entry':
                                    self._ssid_set__mem(obj, setting, element)
                                elif setting == 'ssid':
                                    print(element.value)
                                    _value = 255 if element.value == 'None' else \
                                        abs(int(element.value))
                                    setattr(obj, setting, _value)
                                else:
                                    setattr(obj, setting, element.value)
                            elif obj._name == 'dtmf_list':
                                if setting == 'entry':
                                    self._dtmf_set__mem(obj, setting, element)
                                else:
                                    setattr(obj, setting, element.value)
                        else:
                            setattr(obj, setting, element.value)
                except Exception:
                    LOG.debug(element.get_name())
                    raise

    @classmethod
    def match_model(cls, filedata, filename):
        # new drivers identified by metadata
        return False
