/*
 *  Hamlib Alinco backend - DX77 description
 *  Copyright (c) 2001-2005 by Stephane Fillod
 *
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>  /* String function definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <math.h>


#include <hamlib/rig.h>
#include "idx_builtin.h"
#include "alinco.h"
#include <serial.h>
#include <misc.h>
#include <cal.h>

/*
 * modes in use by the "2G" command
 */
#define MD_LSB  '0'
#define MD_USB  '1'
#define MD_CWL  '2'
#define MD_CWU  '3'
#define MD_AM   '4'
#define MD_FM   '5'

/* Line Feed */
#define EOM "\x0d"
#define LF "\x0a"

#define CMD_TXFREQ  "0A"    /* Transmit frequency */
#define CMD_RXFREQ  "0B"    /* Receive frequency */
#define CMD_VFO     "1A"
#define CMD_MEMMD   "1B"    /* Memory mode */
#define CMD_CHAN    "1D"    /* Channel Display */
#define CMD_UPDWN   "2A"    /* UP/DOWN */
#define CMD_MON     "2B"    /* Check Transmit Frequency */
#define CMD_PWR     "2C"    /* Transmit Output Power */
#define CMD_SCAN    "2D"    /* Scanning */
#define CMD_PRIO    "2E"    /* Priority */
#define CMD_SPLT    "2F"    /* Split */
#define CMD_MODE    "2G"    /* Mode */
#define CMD_RFGAIN  "2H"    /* RF Gain */
#define CMD_AGC     "2I"
#define CMD_FLTER   "2J"    /* Filter */
#define CMD_NB      "2K"
#define CMD_CTCSS   "2L"
#define CMD_TUNE    "2M"
#define CMD_SELECT  "2N"
#define CMD_MCALL   "2V"    /* Memory Channel Call Up */
#define CMD_SDATA   "2W"    /* Set Data */

/* Data Output Commands */
#define CMD_SMETER  "3A"    /* S-meter read */
#define CMD_PTT     "3B"    /* PTT status read */
#define CMD_SQL     "3C"    /* Squelch status */
#define CMD_RIT     "3D"    /* RIT status */
#define CMD_RMEM    "3E"    /* Current Memory-channel Number read */
#define CMD_RMV     "3G"    /* Memory/VFO -mode read */
#define CMD_RDATA   "3H"    /* Current Data read */
#define CMD_RSPLT   "3I"    /* Split read */
#define CMD_RPOWER  "3J"    /* Transmitter Output read */
#define CMD_RSELECT "3K"    /* SELECT Position read */


#define DX77_ALL_MODES (RIG_MODE_AM|RIG_MODE_CW|RIG_MODE_SSB|RIG_MODE_FM)
#define DX77_OTHER_TX_MODES (RIG_MODE_CW|RIG_MODE_SSB|RIG_MODE_FM)
#define DX77_AM_TX_MODES RIG_MODE_AM

#define DX77_FUNC (RIG_FUNC_FAGC|RIG_FUNC_NB|RIG_FUNC_TONE)

#define DX77_LEVEL_ALL (RIG_LEVEL_RAWSTR|RIG_LEVEL_RFPOWER|RIG_LEVEL_KEYSPD|RIG_LEVEL_BKINDL|RIG_LEVEL_CWPITCH)

#define DX77_PARM_ALL (RIG_PARM_BEEP|RIG_PARM_BACKLIGHT)

#define DX77_VFO (RIG_VFO_A|RIG_VFO_B)

/* 90 is S9 */
#define DX77_STR_CAL { 13, {                    \
            {   0, -60 },                       \
            {  28, -48 },                       \
            {  36, -42 },                       \
            {  42, -36 },                       \
            {  50, -30 },                       \
            {  58, -24 },                       \
            {  66, -18 },                       \
            {  74, -12 },                       \
            {  82, -6 },                        \
            {  90, 0 },                         \
            { 132, 20 },                        \
            { 174, 40 },                        \
            { 216, 60 },                        \
        } }


int dx77_set_vfo(RIG *rig, vfo_t vfo);
int dx77_get_vfo(RIG *rig, vfo_t *vfo);
int dx77_set_freq(RIG *rig, vfo_t vfo, freq_t freq);
int dx77_get_freq(RIG *rig, vfo_t vfo, freq_t *freq);
int dx77_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width);
int dx77_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width);
int dx77_set_split_vfo(RIG *rig, vfo_t vfo, split_t split, vfo_t tx_vfo);
int dx77_get_split_vfo(RIG *rig, vfo_t vfo, split_t *split, vfo_t *tx_vfo);
int dx77_set_split_freq(RIG *rig, vfo_t vfo, freq_t tx_freq);
int dx77_get_split_freq(RIG *rig, vfo_t vfo, freq_t *tx_freq);
int dx77_set_func(RIG *rig, vfo_t vfo, setting_t func, int status);
int dx77_get_func(RIG *rig, vfo_t vfo, setting_t func, int *status);
int dx77_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val);
int dx77_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val);
int dx77_set_parm(RIG *rig, setting_t parm, value_t val);
int dx77_get_parm(RIG *rig, setting_t parm, value_t *val);
int dx77_set_ctcss_tone(RIG *rig, vfo_t vfo, tone_t tone);
int dx77_get_rit(RIG *rig, vfo_t vfo, shortfreq_t *rit);
int dx77_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt);
int dx77_get_dcd(RIG *rig, vfo_t vfo, dcd_t *dcd);
int dx77_set_mem(RIG *rig, vfo_t vfo, int ch);
int dx77_get_mem(RIG *rig, vfo_t vfo, int *ch);



/*
 * dx77 rig capabilities.
 *
 * protocol is documented at
 *      http://www.alinco.com/pdf.files/DX77-77_SOFTWARE_MANUAL.pdf
 *
 * This backend was a pleasure to develop. Documentation is clear,
 * and the protocol logical. I'm wondering is the rig's good too. --SF
 *
 * TODO:
 *  - get_parm/set_parm and some LEVELs left (Set Data "2W" command).
 *  - tuner
 *  - up/down
 *  - scan
 */
const struct rig_caps dx77_caps =
{
    RIG_MODEL(RIG_MODEL_DX77),
    .model_name =       "DX-77",
    .mfg_name =         "Alinco",
    .version =          BACKEND_VER ".0",
    .copyright =        "LGPL",
    .status =           RIG_STATUS_BETA,
    .rig_type =         RIG_TYPE_TRANSCEIVER,
    .ptt_type =         RIG_PTT_NONE,
    .dcd_type =         RIG_DCD_RIG,
    .port_type =        RIG_PORT_SERIAL,
    .serial_rate_min =  9600,
    .serial_rate_max =  9600,
    .serial_data_bits = 8,
    .serial_stop_bits = 2,
    .serial_parity =    RIG_PARITY_NONE,
    .serial_handshake = RIG_HANDSHAKE_NONE,
    .write_delay =      0,
    .post_write_delay = 0,
    .timeout =          200,
    .retry =            3,

    .has_get_func =     DX77_FUNC,
    .has_set_func =     DX77_FUNC | RIG_FUNC_MON | RIG_FUNC_COMP,
    .has_get_level =    DX77_LEVEL_ALL,
    .has_set_level =    RIG_LEVEL_SET(DX77_LEVEL_ALL),
    .has_get_parm =     RIG_PARM_NONE,
    .has_set_parm =     RIG_PARM_SET(DX77_PARM_ALL),
    .level_gran =
    {
        [LVL_RAWSTR] =  { .min = { .i = 0 }, .max = { .i = 255 } },
    },
    .parm_gran =        {},
    .ctcss_list =       common_ctcss_list,
    .dcs_list =         NULL,
    .preamp =           { 10, RIG_DBLST_END },
    .attenuator =       { 10, 20, RIG_DBLST_END },
    .max_rit =          kHz(1),
    .max_xit =          Hz(0),
    .max_ifshift =      Hz(0),
    .targetable_vfo =   0,
    .transceive =       RIG_TRN_OFF,
    .bank_qty =         0,
    .chan_desc_sz =     0,

    .chan_list =
    {
        { 0, 99, RIG_MTYPE_MEM },
        RIG_CHAN_END,
    },

    .rx_range_list1 =  { RIG_FRNG_END, },    /* FIXME: enter region 1 setting */
    .tx_range_list1 =  { RIG_FRNG_END, },
    .rx_range_list2 =
    {
        {kHz(500), MHz(30), DX77_ALL_MODES, -1, -1, DX77_VFO},
        RIG_FRNG_END,
    },
    .tx_range_list2 =
    {
        {kHz(1800), MHz(2) - 100, DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {kHz(1800), MHz(2) - 100, DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {kHz(3500), MHz(4) - 100, DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {kHz(3500), MHz(4) - 100, DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {MHz(7), kHz(7300), DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {MHz(7), kHz(7300), DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {kHz(10100), kHz(10150), DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {kHz(10100), kHz(10150), DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {MHz(14), kHz(14350), DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {MHz(14), kHz(14350), DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {kHz(18068), kHz(18168), DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {kHz(18068), kHz(18168), DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {MHz(21), kHz(21450), DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {MHz(21), kHz(21450), DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {kHz(24890), kHz(24990), DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {kHz(24890), kHz(24990), DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        {MHz(28), kHz(29700), DX77_OTHER_TX_MODES, W(10), W(100), DX77_VFO},
        {MHz(28), kHz(29700), DX77_AM_TX_MODES, W(4), W(40), DX77_VFO},
        RIG_FRNG_END,
    },
    .tuning_steps =
    {
        {DX77_ALL_MODES, 10},  /* FIXME: add other ts */
        RIG_TS_END,
    },
    /* mode/filter list, remember: order matters! */
    .filters =
    {
        {RIG_MODE_SSB | RIG_MODE_CW, kHz(2.7)},
        {RIG_MODE_CW, kHz(0.5)},
        {RIG_MODE_AM | RIG_MODE_FM, kHz(8)},
        {RIG_MODE_AM, kHz(2.7)},
        RIG_FLT_END,
    },
    .str_cal =          DX77_STR_CAL,

    .set_freq =         dx77_set_freq,
    .get_freq =         dx77_get_freq,
    .set_mode =         dx77_set_mode,
    .get_mode =         dx77_get_mode,
    .set_vfo =          dx77_set_vfo,
    .get_vfo =          dx77_get_vfo,
    .set_split_vfo =    dx77_set_split_vfo,
    .get_split_vfo =    dx77_get_split_vfo,
    .set_split_freq =   dx77_set_split_freq,
    .get_split_freq =   dx77_get_split_freq,
    .set_ctcss_tone =   dx77_set_ctcss_tone,
    .get_rit =          dx77_get_rit,
    .get_ptt =          dx77_get_ptt,
    .get_dcd =          dx77_get_dcd,
    .set_func =         dx77_set_func,
    .get_func =         dx77_get_func,
    .set_parm =         dx77_set_parm,
    .set_level =        dx77_set_level,
    .get_level =        dx77_get_level,
    .set_mem =          dx77_set_mem,
    .get_mem =          dx77_get_mem,

};


/*
 * Function definitions below
 */

/*
 * dx77_transaction
 * We assume that rig!=NULL, rig->state!= NULL, data!=NULL, data_len!=NULL
 * Otherwise, you'll get a nice seg fault. You've been warned!
 * TODO: error case handling
 */
int dx77_transaction(RIG *rig,
                     const char *cmd,
                     int cmd_len,
                     char *data,
                     int *data_len)
{

    int retval;
    struct rig_state *rs;
    char echobuf[BUFSZ + 1];

    if (cmd == NULL)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: null argument for cmd?\n", __func__);
        return -RIG_EINTERNAL;
    }

    rs = &rig->state;

    rig_flush(&rs->rigport);

    retval = write_block(&rs->rigport, cmd, cmd_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    /*
     * Transceiver sends an echo of cmd followed by a CR/LF
     * TODO: check whether cmd and echobuf match (optional)
     */
    retval = read_string(&rs->rigport, echobuf, BUFSZ, LF, strlen(LF));

    if (retval < 0)
    {
        return retval;
    }

    if (!(data && data_len))
    {
        rig_debug(RIG_DEBUG_ERR, "%s: data and datalen not both NULL??\n", __func__);
        return -RIG_EINTERNAL;
    }

    /* no data expected, check for OK returned */
    if (data == NULL)
    {
        retval = read_string(&rs->rigport, echobuf, BUFSZ, LF, strlen(LF));

        if (retval < 0)
        {
            return retval;
        }

        if (retval > 2) { retval -= 2; }

        echobuf[retval] = 0;

        if (strcmp(echobuf, "OK") == 0)
        {
            return RIG_OK;
        }
        else
        {
            return -RIG_ERJCTED;
        }
    }

    retval = read_string(&rs->rigport, data, BUFSZ, LF, strlen(LF));

    if (retval < 0)
    {
        return retval;
    }

    *data_len = retval;

    /* strip CR/LF from string
     */
    data[0] = 0;

    if (*data_len > 2)
    {
        *data_len -= 2;
        data[*data_len] = 0;
    }

    return RIG_OK;
}

/*
 * dx77_set_vfo
 * Assumes rig!=NULL
 */
int dx77_set_vfo(RIG *rig, vfo_t vfo)
{
    char cmdbuf[BUFSZ];
    int cmd_len;
    char vfo_num;

    switch (vfo)
    {
    case RIG_VFO_A: vfo_num = '1'; break;

    case RIG_VFO_B: vfo_num = '2'; break;

    case RIG_VFO_MEM:
        return dx77_transaction(rig,
                                AL CMD_MEMMD "0" EOM,
                                strlen(AL CMD_MEMMD "0" EOM),
                                NULL,
                                NULL);

    default:
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_set_vfo: unsupported VFO %s\n",
                  rig_strvfo(vfo));

        return -RIG_EINVAL;
    }

    cmd_len = sprintf(cmdbuf, AL CMD_VFO "%c" EOM, vfo_num);

    return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);
}


/*
 * dx77_get_vfo
 * Assumes rig!=NULL, !vfo
 */
int dx77_get_vfo(RIG *rig, vfo_t *vfo)
{
    char vfobuf[BUFSZ];
    int vfo_len, retval;

    retval = dx77_transaction(rig,
                              AL CMD_RMV EOM,
                              strlen(AL CMD_RMV EOM),
                              vfobuf,
                              &vfo_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (vfo_len != 4)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_vfo: wrong answer %s, "
                  "len=%d\n",
                  vfobuf,
                  vfo_len);

        return -RIG_ERJCTED;
    }

    vfobuf[vfo_len] = '\0';

    if (!strcmp(vfobuf, "VFOA"))
    {
        *vfo = RIG_VFO_A;
    }
    else if (!strcmp(vfobuf, "VFOB"))
    {
        *vfo = RIG_VFO_B;
    }
    else if (!strcmp(vfobuf, "MEMO"))
    {
        *vfo = RIG_VFO_MEM;
    }
    else
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_vfo: unsupported VFO %s\n",
                  vfobuf);

        return -RIG_EPROTO;
    }

    return RIG_OK;
}


/*
 * dx77_set_freq
 * Assumes rig!=NULL
 */
int dx77_set_freq(RIG *rig, vfo_t vfo, freq_t freq)
{
    char freqbuf[BUFSZ];
    int freq_len;

    /* max 10 digits */
    if (freq >= GHz(10))
    {
        return -RIG_EINVAL;
    }

    /* at least 6 digits */
    // cppcheck-suppress *
    freq_len = sprintf(freqbuf, AL CMD_RXFREQ "%06"PRIll EOM, (int64_t)freq);

    return dx77_transaction(rig, freqbuf, freq_len, NULL, NULL);
}


/*
 * where databuf points to a 26 char long buffer
 */
static int current_data_read(RIG *rig, char *databuf)
{
    int data_len, retval;

    retval = dx77_transaction(rig,
                              AL CMD_RDATA EOM,
                              strlen(AL CMD_RDATA EOM),
                              databuf,
                              &data_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (data_len != 26)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_current_data_read: wrong answer %s, len=%d\n",
                  databuf,
                  data_len);

        return -RIG_ERJCTED;
    }

    return RIG_OK;
}


/*
 * dx77_get_freq
 * Assumes rig!=NULL, freq!=NULL
 */
int dx77_get_freq(RIG *rig, vfo_t vfo, freq_t *freq)
{
    int retval;
    char freqbuf[BUFSZ];

    retval = current_data_read(rig, freqbuf);

    if (retval != RIG_OK)
    {
        return retval;
    }

    /* extract RX freq */
    freqbuf[16] = '\0';
    sscanf(freqbuf + 6, "%"SCNfreq, freq);

    return RIG_OK;
}


/*
 * dx77_set_mode
 * Assumes rig!=NULL
 */
int dx77_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
{
    char mdbuf[BUFSZ];
    int mdbuf_len, wide_filter, retval;
    char amode;

    switch (mode)
    {
    /* FIXME: MD_CWL or MD_CWU? */
    case RIG_MODE_CW:       amode = MD_CWU; break;

    case RIG_MODE_USB:      amode = MD_USB; break;

    case RIG_MODE_LSB:      amode = MD_LSB; break;

    case RIG_MODE_FM:       amode = MD_FM; break;

    case RIG_MODE_AM:       amode = MD_AM; break;

    default:
        rig_debug(RIG_DEBUG_ERR, "dx77_set_mode: unsupported mode %s\n",
                  rig_strrmode(mode));

        return -RIG_EINVAL;
    }

    mdbuf_len = sprintf(mdbuf, AL CMD_MODE "%c" EOM, amode);
    retval = dx77_transaction(rig, mdbuf, mdbuf_len, NULL, NULL);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (width == RIG_PASSBAND_NOCHANGE) { return retval; }

    /*
     * TODO: please DX77 owners, check this, I'm not sure
     *          which passband is default!
     */
    if (width != RIG_PASSBAND_NORMAL
            && width < rig_passband_normal(rig, mode))
    {
        wide_filter = 0;
    }
    else
    {
        wide_filter = 1;
    }

    mdbuf_len = sprintf(mdbuf, AL CMD_FLTER "%02d" EOM, wide_filter);
    retval = dx77_transaction(rig, mdbuf, mdbuf_len, NULL, NULL);

    return retval;
}


/*
 * dx77_get_mode
 * Assumes rig!=NULL, mode!=NULL
 */
int dx77_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width)
{
    int retval;
    int settings;
    char modebuf[BUFSZ];

    retval = current_data_read(rig, modebuf);

    if (retval != RIG_OK)
    {
        return retval;
    }

    /* FIXME: CWL&CWU: what are they? CW & CWR? */
    switch (modebuf[3])
    {
    case MD_CWL:
    case MD_CWU:      *mode = RIG_MODE_CW; break;

    case MD_USB:      *mode = RIG_MODE_USB; break;

    case MD_LSB:      *mode = RIG_MODE_LSB; break;

    case MD_AM:       *mode = RIG_MODE_AM; break;

    case MD_FM:       *mode = RIG_MODE_FM; break;

    default:
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_mode: unknown mode %c%c\n",
                  modebuf[2],
                  modebuf[3]);

        return -RIG_EINVAL;
    }

    modebuf[2] = '\0';
    settings = strtol(modebuf, (char **)NULL, 16);

    /*
     * TODO: please DX77 owners, check this, I'm not sure
     *          which passband is default!
     */
    if (settings & 0x02)
    {
        *width = rig_passband_narrow(rig, *mode);
    }
    else
    {
        *width = rig_passband_normal(rig, *mode);
    }

    return RIG_OK;
}


/*
 * dx77_set_split
 * Assumes rig!=NULL
 */
int dx77_set_split_vfo(RIG *rig, vfo_t vfo, split_t split, vfo_t tx_vfo)
{
    int cmd_len;
    char cmdbuf[BUFSZ];

    cmd_len = sprintf(cmdbuf,
                      AL CMD_SPLT "%d" EOM,
                      split == RIG_SPLIT_ON ? 1 : 0);

    return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);
}


/*
 * dx77_get_split
 * Assumes rig!=NULL, split!=NULL
 */
int dx77_get_split_vfo(RIG *rig, vfo_t vfo, split_t *split, vfo_t *tx_vfo)
{
    int splt_len, retval;
    char spltbuf[BUFSZ];

    retval = dx77_transaction(rig,
                              AL CMD_RSPLT EOM,
                              strlen(AL CMD_RSPLT EOM),
                              spltbuf,
                              &splt_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (splt_len != 2)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_split: wrong answer %s, len=%d\n",
                  spltbuf,
                  splt_len);

        return -RIG_ERJCTED;
    }

    spltbuf[splt_len] = '\0';

    if (!strcmp(spltbuf, "OF"))
    {
        *split = RIG_SPLIT_OFF;
    }
    else if (!strcmp(spltbuf, "ON"))
    {
        *split = RIG_SPLIT_ON;
    }
    else
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_split: unsupported SPLIT %s\n",
                  spltbuf);

        return -RIG_EPROTO;
    }

    return RIG_OK;
}


/*
 * dx77_set_split_freq
 * Assumes rig!=NULL
 */
int dx77_set_split_freq(RIG *rig, vfo_t vfo, freq_t tx_freq)
{
    char freqbuf[BUFSZ];
    int freq_len;
    int retval;

    /* max 10 digits */
    if (tx_freq >= GHz(10))
    {
        return -RIG_EINVAL;
    }

    /* at least 6 digits */
    freq_len = sprintf(freqbuf, AL CMD_TXFREQ "%06"PRIll EOM, (int64_t)tx_freq);

    retval = dx77_transaction(rig, freqbuf, freq_len, NULL, NULL);

    return retval;
}


/*
 * dx77_get_split_freq
 * Assumes rig!=NULL, rx_freq!=NULL, tx_freq!=NULL
 */
int dx77_get_split_freq(RIG *rig, vfo_t vfo, freq_t *tx_freq)
{
    int retval;
    char freqbuf[BUFSZ];

    retval = current_data_read(rig, freqbuf);

    if (retval != RIG_OK)
    {
        return retval;
    }

    /* extract TX freq first, as RX kills freqbuf[16] */
    freqbuf[26] = '\0';
    sscanf(freqbuf + 16, "%"SCNfreq, tx_freq);

    return RIG_OK;
}


/*
 * dx77_get_rit
 * Assumes rig!=NULL, split!=NULL
 */
int dx77_get_rit(RIG *rig, vfo_t vfo, shortfreq_t *rit)
{
    int rit_len, retval;
    char ritbuf[BUFSZ];

    /* read in Hertz unit */
    retval = dx77_transaction(rig,
                              AL CMD_RIT "0" EOM,
                              strlen(AL CMD_RIT "0" EOM),
                              ritbuf,
                              &rit_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (rit_len != 8)   /* || (ritbuf[0] != '+' && ritbuf[0] != '-')) { */
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_rit: wrong answer %s, len=%d\n",
                  ritbuf,
                  rit_len);

        return -RIG_ERJCTED;
    }

    ritbuf[rit_len] = '\0';
    ritbuf[0] = ' ';
    ritbuf[1] = ' ';
    ritbuf[2] = ' ';

    *rit = atoi(ritbuf);

    return RIG_OK;
}


/*
 * dx77_set_func
 * Assumes rig!=NULL
 */
int dx77_set_func(RIG *rig, vfo_t vfo, setting_t func, int status)
{
    int cmd_len;
    char cmdbuf[BUFSZ];

    /* Optimize:
     *   sort the switch cases with the most frequent first
     */
    switch (func)
    {
    case RIG_FUNC_TONE:
        cmd_len = sprintf(cmdbuf, AL CMD_CTCSS "%02d" EOM, status ? 51 : 0);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_FUNC_FAGC:
        cmd_len = sprintf(cmdbuf, AL CMD_AGC "%02d" EOM, status ? 1 : 2);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_FUNC_NB:
        cmd_len = sprintf(cmdbuf, AL CMD_NB "%d" EOM, status ? 1 : 0);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_FUNC_COMP:
        cmd_len = sprintf(cmdbuf, AL CMD_SDATA "C%d" EOM, status ? 1 : 0);
        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_FUNC_MON:
        cmd_len = sprintf(cmdbuf, AL CMD_MON "%d" EOM, status ? 1 : 0);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    default:
        rig_debug(RIG_DEBUG_ERR, "Unsupported set_func %d\n", (int)func);
        return -RIG_EINVAL;
    }

    return RIG_OK;
}


/*
 * dx77_get_func
 * Assumes rig!=NULL, status!=NULL
 */
int dx77_get_func(RIG *rig, vfo_t vfo, setting_t func, int *status)
{
    int retval;
    int settings;
    char funcbuf[BUFSZ];

    /* Optimize:
     *   sort the switch cases with the most frequent first
     */
    switch (func)
    {
    case RIG_FUNC_TONE:
        retval = current_data_read(rig, funcbuf);

        if (retval != RIG_OK)
        {
            return retval;
        }

        funcbuf[2] = '\0';
        settings = strtol(funcbuf, (char **)NULL, 16);
        *status = (settings & 0x08) ? 1 : 0;
        break;

    case RIG_FUNC_FAGC:
        retval = current_data_read(rig, funcbuf);

        if (retval != RIG_OK)
        {
            return retval;
        }

        funcbuf[2] = '\0';
        settings = strtol(funcbuf, (char **)NULL, 16);
        *status = (settings & 0x01) ? 1 : 0;
        break;

    case RIG_FUNC_NB:
        retval = current_data_read(rig, funcbuf);

        if (retval != RIG_OK)
        {
            return retval;
        }

        funcbuf[2] = '\0';
        settings = strtol(funcbuf, (char **)NULL, 16);
        *status = (settings & 0x04) ? 1 : 0;
        break;

    default:
        rig_debug(RIG_DEBUG_ERR, "Unsupported get_func %d\n", (int)func);
        return -RIG_EINVAL;
    }

    return RIG_OK;
}


/*
 * dx77_set_level
 * Assumes rig!=NULL
 * FIXME: cannot support PREAMP and ATT both at same time (make sense though)
 */
int dx77_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val)
{
    int cmd_len, lvl;
    char cmdbuf[BUFSZ];

    /* Optimize:
     *   sort the switch cases with the most frequent first
     */
    switch (level)
    {
    case RIG_LEVEL_PREAMP:
        switch (val.i)
        {
        case 0: lvl = 0; break;

        case 10: lvl = 1; break;

        default: rig_debug(RIG_DEBUG_ERR,
                               "Unsupported Preamp %d\n",
                               val.i);

            return -RIG_EINVAL;
        }

        cmd_len = sprintf(cmdbuf, AL CMD_RFGAIN "%02d" EOM, lvl);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_LEVEL_ATT:
        switch (val.i)
        {
        case 0: lvl = 0; break;

        case 10: lvl = 11; break;

        case 20: lvl = 10; break;

        default: rig_debug(RIG_DEBUG_ERR,
                               "Unsupported Att %d\n",
                               val.i);

            return -RIG_EINVAL;
        }

        cmd_len = sprintf(cmdbuf, AL CMD_RFGAIN "%02d" EOM, lvl);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_LEVEL_RFPOWER:
        cmd_len = sprintf(cmdbuf, AL CMD_PWR "%1d" EOM, val.f < 0.5 ? 1 : 0);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_LEVEL_KEYSPD:
        if (val.i < 6)
        {
            lvl = 31;
        }
        else if (val.i >= 6 && val.i < 20)
        {
            lvl = val.i + 25;
        }
        else if (val.i >= 20 && val.i <= 50)
        {
            lvl = val.i - 20;
        }
        else
        {
            lvl = 30;
        }

        cmd_len = sprintf(cmdbuf, AL CMD_SDATA "P%02d" EOM, lvl);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_LEVEL_CWPITCH:
        lvl = 4;

        if (val.i < 426)
        {
            lvl = 5;
        }
        else if (val.i >= 426 && val.i <= 475)
        {
            lvl = 6;
        }
        else if (val.i >= 476 && val.i <= 525)
        {
            lvl = 7;
        }
        else if (val.i >= 526 && val.i <= 575)
        {
            lvl = 8;
        }
        else if (val.i >= 576 && val.i <= 625)
        {
            lvl = 9;
        }
        else if (val.i >= 626 && val.i <= 675)
        {
            lvl = 10;
        }
        else if (val.i >= 676 && val.i <= 725)
        {
            lvl = 11;
        }
        else if (val.i >= 726 && val.i <= 775)
        {
            lvl = 12;
        }
        else if (val.i >= 776 && val.i <= 825)
        {
            lvl = 0;
        }
        else if (val.i >= 826 && val.i <= 875)
        {
            lvl = 1;
        }
        else if (val.i >= 876 && val.i <= 925)
        {
            lvl = 2;
        }
        else if (val.i >= 926 && val.i <= 975)
        {
            lvl = 3;
        }
        else if (val.i >= 976 && val.i <= 1025)
        {
            lvl = 4;
        }

        cmd_len = sprintf(cmdbuf, AL CMD_SDATA "M%02d" EOM, lvl);

        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    default:
        rig_debug(RIG_DEBUG_ERR, "Unsupported set_level %s\n", rig_strlevel(level));

        return -RIG_EINVAL;
    }

    return RIG_OK;
}


/*
 * dx77_get_level
 * Assumes rig!=NULL, val!=NULL
 */
int dx77_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val)
{
    int retval, lvl_len;
    char lvlbuf[BUFSZ];

    /* Optimize:
     *   sort the switch cases with the most frequent first
     */
    switch (level)
    {
    case RIG_LEVEL_RAWSTR:
        /* read A/D converted value */
        retval = dx77_transaction(rig,
                                  AL CMD_SMETER "1" EOM,
                                  strlen(AL CMD_SMETER "1" EOM),
                                  lvlbuf,
                                  &lvl_len);

        if (retval != RIG_OK)
        {
            return retval;
        }

        if (lvl_len != 6)
        {
            rig_debug(RIG_DEBUG_ERR,
                      "dx77_get_level: wrong answer len=%d\n",
                      lvl_len);

            return -RIG_ERJCTED;
        }

        lvlbuf[6] = '\0';
        val->i = atoi(lvlbuf + 3);
        break;

    case RIG_LEVEL_PREAMP:
        retval = current_data_read(rig, lvlbuf);

        if (retval != RIG_OK)
        {
            return retval;
        }

        switch (lvlbuf[5])
        {
        case '2':
        case '3':
        case '0': val->i = 0; break;

        case '1': val->i = 10; break;

        default: rig_debug(RIG_DEBUG_ERR,
                               "Unknown RF Gain %c%c\n",
                               lvlbuf[4],
                               lvlbuf[5]);
        }

        break;

    case RIG_LEVEL_ATT:
        retval = current_data_read(rig, lvlbuf);

        if (retval != RIG_OK)
        {
            return retval;
        }

        switch (lvlbuf[5])
        {
        case '1':
        case '0': val->i = 0; break;

        case '2': val->i = 20; break;

        case '3': val->i = 10; break;

        default: rig_debug(RIG_DEBUG_ERR,
                               "Unknown RF Gain %c%c\n",
                               lvlbuf[4],
                               lvlbuf[5]);
        }

        break;

    case RIG_LEVEL_RFPOWER:
        retval = dx77_transaction(rig,
                                  AL CMD_RPOWER EOM,
                                  strlen(AL CMD_RPOWER EOM),
                                  lvlbuf,
                                  &lvl_len);

        if (retval != RIG_OK)
        {
            return retval;
        }

        if (lvl_len != 1)
        {
            rig_debug(RIG_DEBUG_ERR,
                      "dx77_get_level: wrong answer len=%d\n",
                      lvl_len);

            return -RIG_ERJCTED;
        }

        /* H or L */
        val->f = lvlbuf[0] == 'H' ? 1.0 : 0.0;
        break;

    default:
        rig_debug(RIG_DEBUG_ERR, "Unsupported get_level %s\n", rig_strlevel(level));

        return -RIG_EINVAL;
    }

    return RIG_OK;
}


/*
 * dx77_set_parm
 */
int dx77_set_parm(RIG *rig, setting_t parm, value_t val)
{
    int cmd_len;
    char cmdbuf[BUFSZ];

    /* Optimize:
     *   sort the switch cases with the most frequent first
     */
    switch (parm)
    {
    case RIG_PARM_BEEP:
        rig_debug(RIG_DEBUG_ERR, "val is %d\n", val.i);
        cmd_len = sprintf(cmdbuf, AL CMD_SDATA "A%d" EOM, val.i ? 1 : 0);
        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    case RIG_PARM_BACKLIGHT:
        rig_debug(RIG_DEBUG_ERR, "val is %0f\n", val.f);
        cmd_len = sprintf(cmdbuf, AL CMD_SDATA "O%d" EOM, (int)(val.f * 5));
        return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);

    default:
        rig_debug(RIG_DEBUG_ERR, "Unsupported set_parm %d\n", (int)parm);
        return -RIG_EINVAL;
    }

    return RIG_OK;
}


/*
 * dx77_set_ctcss_tone
 * Assumes rig!=NULL, rig->caps->ctcss_list != NULL
 */
int dx77_set_ctcss_tone(RIG *rig, vfo_t vfo, tone_t tone)
{
    const struct rig_caps *caps;
    unsigned char tonebuf[BUFSZ];
    int tone_len;
    int i;

    caps = rig->caps;

    for (i = 0; caps->ctcss_list[i] != 0; i++)
    {
        if (caps->ctcss_list[i] == tone)
        {
            break;
        }
    }

    if (caps->ctcss_list[i] != tone)
    {
        return -RIG_EINVAL;
    }

    tone_len = sprintf((char *) tonebuf, AL CMD_CTCSS "%02d" EOM, i + 1);

    return dx77_transaction(rig, (char *) tonebuf, tone_len, NULL, NULL);
}


/*
 * dx77_get_ptt
 * Assumes rig!=NULL, ptt!=NULL
 */
int dx77_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt)
{
    char pttbuf[BUFSZ];
    int ptt_len, retval;

    retval = dx77_transaction(rig,
                              AL CMD_PTT EOM,
                              strlen(AL CMD_PTT EOM),
                              pttbuf,
                              &ptt_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (ptt_len != 3 && ptt_len != 4)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_ptt: wrong answer %s, len=%d\n",
                  pttbuf,
                  ptt_len);

        return -RIG_ERJCTED;
    }

    pttbuf[ptt_len] = '\0';

    if (!strcmp(pttbuf, "SEND"))
    {
        *ptt = RIG_PTT_OFF;
    }
    else if (!strcmp(pttbuf, "REV"))
    {
        *ptt = RIG_PTT_ON;
    }
    else
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_ptt: unknown PTT %s\n",
                  pttbuf);

        return -RIG_EPROTO;
    }

    return RIG_OK;
}


/*
 * dx77_get_dcd
 * Assumes rig!=NULL, dcd!=NULL
 */
int dx77_get_dcd(RIG *rig, vfo_t vfo, dcd_t *dcd)
{
    char dcdbuf[BUFSZ];
    int dcd_len, retval;

    retval = dx77_transaction(rig,
                              AL CMD_SQL EOM,
                              strlen(AL CMD_SQL EOM),
                              dcdbuf,
                              &dcd_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (dcd_len != 4 && dcd_len != 5)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_dcd: wrong answer %s, len=%d\n",
                  dcdbuf,
                  dcd_len);

        return -RIG_ERJCTED;
    }

    dcdbuf[dcd_len] = '\0';

    if (!strcmp(dcdbuf, "OPEN"))
    {
        *dcd = RIG_DCD_ON;
    }
    else if (!strcmp(dcdbuf, "CLOSE"))
    {
        *dcd = RIG_DCD_OFF;
    }
    else
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_dcd: unknown SQL %s\n",
                  dcdbuf);

        return -RIG_EPROTO;
    }

    return RIG_OK;
}


/*
 * dx77_set_mem
 * Assumes rig!=NULL
 * FIXME: check we're in memory mode first
 */
int dx77_set_mem(RIG *rig, vfo_t vfo, int ch)
{
    char cmdbuf[BUFSZ];
    int cmd_len;

    if (ch < 0 || ch > 99)
    {
        return -RIG_EINVAL;
    }

    cmd_len = sprintf(cmdbuf, AL CMD_MCALL "%02d" EOM, ch);

    return dx77_transaction(rig, cmdbuf, cmd_len, NULL, NULL);
}


/*
 * dx77_get_mem
 * Assumes rig!=NULL, !vfo
 */
int dx77_get_mem(RIG *rig, vfo_t vfo, int *ch)
{
    char membuf[BUFSZ];
    int mem_len, retval;

    retval = dx77_transaction(rig,
                              AL CMD_RMEM EOM,
                              strlen(AL CMD_RMEM EOM),
                              membuf,
                              &mem_len);

    if (retval != RIG_OK)
    {
        return retval;
    }

    if (mem_len != 2)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "dx77_get_mem: wrong answer %s, len=%d\n",
                  membuf,
                  mem_len);

        return -RIG_ERJCTED;
    }

    membuf[mem_len] = '\0';

    *ch = atoi(membuf);

    if (*ch < 0 || *ch > 99)
    {
        rig_debug(RIG_DEBUG_ERR, "dx77_get_mem: unknown mem %s\n",
                  membuf);
        return -RIG_EPROTO;
    }

    return RIG_OK;
}

