/*
 *  Hamlib CODAN backend - main file
 *  Copyright (c) 2021 by Michael Black W9MDB
 *
 *
 *   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
 *
 */
#include <hamlib/config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

#include <hamlib/rig.h>
#include "serial.h"
#include "misc.h"
#include "cal.h"
#include "token.h"
#include "register.h"

#include "codan.h"

#define MAXCMDLEN 64

#define CODAN_VFOS (RIG_VFO_A|RIG_VFO_B)

#define CODAN_MODES (RIG_MODE_USB)

int codan_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt);
static int codan_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt);
int codan_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width);
int codan_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode,
                   pbwidth_t *width);

int codan_transaction(RIG *rig, char *cmd, int expected, char **result)
{
    char cmd_buf[MAXCMDLEN];
    int retval;
    struct rig_state *rs = &rig->state;
    struct codan_priv_data *priv = rig->state.priv;
    //int retry = 3;

    rig_debug(RIG_DEBUG_VERBOSE, "%s: cmd=%s\n", __func__, cmd);

    SNPRINTF(cmd_buf, sizeof(cmd_buf), "%s%s", cmd, EOM);

    rig_flush(&rs->rigport);
    retval = write_block(&rs->rigport, (unsigned char *) cmd_buf, strlen(cmd_buf));
    hl_usleep(rig->caps->post_write_delay);

    if (retval < 0)
    {
        return retval;
    }

    if (expected == 0)
    {
        // response format is reponse...0x0d0x0a
        retval = read_string(&rs->rigport, (unsigned char *) priv->ret_data,
                             sizeof(priv->ret_data),
                             "\x0a", 1, 0, 1);
        rig_debug(RIG_DEBUG_VERBOSE, "%s: result=%s, resultlen=%d\n", __func__,
                  priv->ret_data, (int)strlen(priv->ret_data));

        if (retval < 0)
        {
            return retval;
        }
    }
    else
    {
        retval = read_string(&rs->rigport, (unsigned char *) priv->ret_data,
                             sizeof(priv->ret_data),
                             "\x0a", 1, 0, 1);

        if (retval < 0)
        {
            return retval;
        }

        if (strncmp(priv->ret_data, "LEVELS:", 7) == 0)
        {
            rig_debug(RIG_DEBUG_VERBOSE, "%s: %s\n", __func__, priv->ret_data);
            retval = read_string(&rs->rigport, (unsigned char *) priv->ret_data,
                                 sizeof(priv->ret_data),
                                 "\x0a", 1, 0, 1);
            rig_debug(RIG_DEBUG_VERBOSE, "%s: %s\n", __func__, priv->ret_data);
        }
    }

#if 0
    int hr, min, sec;

    while (--retry >= 0 && strncmp(priv->ret_data, "OK", 2) != 0
            && sscanf(priv->ret_data, "%d:%d:%d", &hr, &min, &sec) != 3)
    {
        char tmpbuf[256];
        retval = read_string(&rs->rigport, (unsigned char *) tmpbuf,
                             sizeof(priv->ret_data),
                             "\x0a", 1, 0, 1);

        dump_hex((unsigned char *)priv->ret_data, strlen(priv->ret_data));
        rig_debug(RIG_DEBUG_VERBOSE, "%s: %s\n", __func__, priv->ret_data);
    }

#endif

    rig_debug(RIG_DEBUG_VERBOSE, "%s: retval=%d\n", __func__, retval);
    rig_debug(RIG_DEBUG_VERBOSE, "%s: %s\n", __func__, priv->ret_data);

    if (result != NULL)
    {
        *result = &(priv->ret_data[0]);
        rig_debug(RIG_DEBUG_VERBOSE, "%s: returning result=%s\n", __func__,
                  *result);
    }
    else
    {
        rig_debug(RIG_DEBUG_VERBOSE, "%s: no result requested\n", __func__);
    }

    return RIG_OK;
}

int codan_init(RIG *rig)
{
    rig_debug(RIG_DEBUG_VERBOSE, "%s version %s\n", __func__,
              rig->caps->version);
    // cppcheck claims leak here but it's freed in cleanup
    rig->state.priv = (struct codan_priv_data *)calloc(1,
                      sizeof(struct codan_priv_data));

    if (!rig->state.priv)
    {
        return -RIG_ENOMEM;
    }

    RETURNFUNC2(RIG_OK);
}

int codan_open(RIG *rig)
{
    char *results = NULL;
    codan_transaction(rig, "scan off\r", 1, &results);
    codan_transaction(rig, "echo off", 1, &results);
    //codan_transaction(rig, "prompt time", 1, &results);
    codan_transaction(rig, "login", 1, &results);

    if (!strstr(results, "admin"))
    {
        codan_transaction(rig, "login admin ''", 0, NULL);
    }

    codan_transaction(rig, "login", 1, &results);
    codan_set_freq(rig, RIG_VFO_A, 14074000.0);

    RETURNFUNC2(RIG_OK);
}

int codan_close(RIG *rig)
{
    char *results = NULL;
    codan_transaction(rig, "logout admin\rfreq", 1, &results);
    RETURNFUNC2(RIG_OK);
}

int codan_cleanup(RIG *rig)
{

    rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (!rig)
    {
        return -RIG_EINVAL;
    }

    if (rig->state.priv)
    {
        free(rig->state.priv);
    }

    rig->state.priv = NULL;

    return RIG_OK;
}


/*
 * codan_get_mode
 * Assumes rig!=NULL
 */
int codan_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width)
{
    char *result = NULL;
    char modeA[8], modeB[8];
    int widthA, center;
    int retval;

    rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo));

    retval = codan_transaction(rig, "mode", 0, &result);

    if (retval != RIG_OK)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: bad response=%s\n", __func__, result);
        return retval;
    }

    rig_debug(RIG_DEBUG_VERBOSE, "%s: result=%s", __func__, result);
    int n = sscanf(result, "MODE: %[A-Z], %[A-Z], %d, %d", modeA, modeB, &center,
                   &widthA);

    if (n != 4)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: sscanf expected 4, got %d, %s\n", __func__, n,
                  result);
        return -RIG_EPROTO;
    }

    if (strncmp(modeA, "USB", 3) == 0) { *mode = RIG_MODE_USB; }
    else if (strncmp(modeA, "LSB", 3) == 0) { *mode = RIG_MODE_LSB; }
    else
    {
        rig_debug(RIG_DEBUG_ERR, "%s: Unknown mode=%s'\n", __func__,  modeA)
        return -RIG_EPROTO;
    }

    *width = widthA; // we'll default this to 3000 for now
    rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s mode=%s width=%d\n", __func__,
              rig_strvfo(vfo), rig_strrmode(*mode), (int)*width);

    return RIG_OK;
}

/*
 * codan_set_mode
 * Assumes rig!=NULL
 */
int codan_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
{
    char cmd_buf[32], *ttmode;
    char *response = NULL;
    int retval;

    rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s mode=%s width=%d\n", __func__,
              rig_strvfo(vfo), rig_strrmode(mode), (int)width);

    switch (mode)
    {
    case RIG_MODE_USB:
        ttmode = "USBW";
        break;

    case RIG_MODE_LSB:
        ttmode = "LSBW";
        break;

    default:
        rig_debug(RIG_DEBUG_ERR, "%s: unsupported mode %s\n", __func__,
                  rig_strrmode(mode));
        return -RIG_EINVAL;
    }

    SNPRINTF((char *) cmd_buf, sizeof(cmd_buf), "mode %s", ttmode);

    retval = codan_transaction(rig, cmd_buf, 0, &response);

    if (retval < 0)
    {
        return retval;
    }

    return RIG_OK;
}


/*
 * codan_set_freq
 * assumes rig!=NULL, rig->state.priv!=NULL
 */
int codan_set_freq(RIG *rig, vfo_t vfo, freq_t freq)
{
    char cmd_buf[MAXCMDLEN];
    int retval;

    rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s freq=%.0f\n", __func__,
              rig_strvfo(vfo), freq);

    // Purportedly can't do split so we just set VFOB=VFOA
    SNPRINTF(cmd_buf, sizeof(cmd_buf), "connect tcvr rf %.0f %.0f\rfreq", freq,
             freq);

    char *response = NULL;
    retval = codan_transaction(rig, cmd_buf, 0, &response);

    if (retval < 0)
    {
        return retval;
    }

    return retval;
}

/*
 * codan_get_freq
 * Assumes rig!=NULL, rig->state.priv!=NULL, freq!=NULL
 */
int codan_get_freq(RIG *rig, vfo_t vfo, freq_t *freq)
{
    int retval;
    char *response = NULL;

    rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo));
    *freq = 0;

    retval = codan_transaction(rig, "freq", 0, &response);

    if (retval != RIG_OK)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: invalid response=%s\n", __func__, response);
        return retval;
    }

    retval = sscanf(response, "FREQ: %lg", freq);

    *freq *= 1000; // returne freq is in kHz

    if (retval != 1)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: Unable to parse response\n", __func__);
        return -RIG_EPROTO;
    }

    return RIG_OK;
}

/*
 * codan_get_ptt
 * Assumes rig!=NULL
 */
int codan_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt)
{
    int retval;
    char *response = NULL;

    rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo));

    retval = codan_transaction(rig, "connect tcvr rf ptt", 0, &response);

    if (retval != RIG_OK)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error response?='%s'\n", __func__, response);
        return retval;
    }

    char *p = strstr(response, "Ptt");

    if (p)
    {
        if (strcmp(p, "Ptt=Off") == 0) { *ptt = 0; }
        else { *ptt = 1; }
    }
    else
    {
        rig_debug(RIG_DEBUG_ERR, "%s: unable to find Ptt in %s\n", __func__, response);
        return -RIG_EPROTO;
    }

    return RIG_OK;
}

/*
 * codan_set_ptt
 * Assumes rig!=NULL
 */
int codan_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt)
{
    int retval;
    char cmd_buf[MAXCMDLEN];
    char *response;

    rig_debug(RIG_DEBUG_VERBOSE, "%s: ptt=%d\n", __func__, ptt);

    SNPRINTF(cmd_buf, sizeof(cmd_buf), "connect tcvr rf ptt %s\rptt",
             ptt == 0 ? "off" : "on");
    response = NULL;
    retval = codan_transaction(rig, cmd_buf, 0, &response);

    if (retval < 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: invalid response=%s\n", __func__, response);
        return retval;
    }

    rig_debug(RIG_DEBUG_VERBOSE, "%s: cmd result=%s\n", __func__, response);

    return RIG_OK;
}




const struct rig_caps envoy_caps =
{
    RIG_MODEL(RIG_MODEL_CODAN_ENVOY),
    .model_name =       "Envoy",
    .mfg_name =         "CODAN",
    .version =          BACKEND_VER ".0",
    .copyright =        "LGPL",
    .status =           RIG_STATUS_STABLE,
    .rig_type =         RIG_TYPE_TRANSCEIVER,
    .targetable_vfo =   RIG_TARGETABLE_FREQ,
    .ptt_type =         RIG_PTT_RIG,
    .dcd_type =         RIG_DCD_NONE,
    .port_type =        RIG_PORT_SERIAL,
    .serial_rate_min =  9600,
    .serial_rate_max =  115200,
    .serial_data_bits = 8,
    .serial_stop_bits = 1,
    .serial_parity =    RIG_PARITY_NONE,
    .serial_handshake = RIG_HANDSHAKE_NONE,
    .write_delay =      0,
    .post_write_delay = 50,
    .timeout =          1000,
    .retry =            3,

    .has_get_func =     RIG_FUNC_NONE,
    .has_set_func =     RIG_FUNC_NONE,
    .has_get_level =    RIG_LEVEL_NONE,
    .has_set_level =    RIG_LEVEL_NONE,
    .has_get_parm =     RIG_PARM_NONE,
    .has_set_parm =     RIG_PARM_NONE,
    .transceive =       RIG_TRN_RIG,
    .rx_range_list1 = {{
            .startf = kHz(1600), .endf = MHz(30), .modes = CODAN_MODES,
            .low_power = -1, .high_power = -1, CODAN_VFOS, RIG_ANT_1
        },
        RIG_FRNG_END,
    },
    .rx_range_list2 = {RIG_FRNG_END,},
    .tx_range_list1 = {RIG_FRNG_END,},
    .tx_range_list2 = {RIG_FRNG_END,},
    .tuning_steps =  { {CODAN_MODES, 1}, {CODAN_MODES, RIG_TS_ANY},  RIG_TS_END, },
    .filters = {
        {RIG_MODE_SSB, kHz(2.4)},
        {RIG_MODE_SSB, kHz(0.5)},
        {RIG_MODE_SSB, kHz(2.7)},
        {RIG_MODE_SSB, kHz(3.0)},
        RIG_FLT_END,
    },
    .priv = NULL,

    .rig_init =     codan_init,
    .rig_open =     codan_open,
    .rig_close =     codan_close,
    .rig_cleanup =  codan_cleanup,

    .set_freq = codan_set_freq,
    .get_freq = codan_get_freq,
    .set_mode = codan_set_mode,
    .get_mode = codan_get_mode,

    .set_ptt =      codan_set_ptt,
    .get_ptt =      codan_get_ptt,
    .hamlib_check_rig_caps = HAMLIB_CHECK_RIG_CAPS
};

const struct rig_caps ngs_caps =
{
    RIG_MODEL(RIG_MODEL_CODAN_NGT),
    .model_name =       "NGT",
    .mfg_name =         "CODAN",
    .version =          BACKEND_VER ".0",
    .copyright =        "LGPL",
    .status =           RIG_STATUS_STABLE,
    .rig_type =         RIG_TYPE_TRANSCEIVER,
    .targetable_vfo =   RIG_TARGETABLE_FREQ,
    .ptt_type =         RIG_PTT_RIG,
    .dcd_type =         RIG_DCD_NONE,
    .port_type =        RIG_PORT_SERIAL,
    .serial_rate_min =  9600,
    .serial_rate_max =  115200,
    .serial_data_bits = 8,
    .serial_stop_bits = 1,
    .serial_parity =    RIG_PARITY_NONE,
    .serial_handshake = RIG_HANDSHAKE_NONE,
    .write_delay =      0,
    .post_write_delay = 50,
    .timeout =          1000,
    .retry =            3,

    .has_get_func =     RIG_FUNC_NONE,
    .has_set_func =     RIG_FUNC_NONE,
    .has_get_level =    RIG_LEVEL_NONE,
    .has_set_level =    RIG_LEVEL_NONE,
    .has_get_parm =     RIG_PARM_NONE,
    .has_set_parm =     RIG_PARM_NONE,
    .transceive =       RIG_TRN_RIG,
    .rx_range_list1 = {{
            .startf = kHz(1600), .endf = MHz(30), .modes = CODAN_MODES,
            .low_power = -1, .high_power = -1, CODAN_VFOS, RIG_ANT_1
        },
        RIG_FRNG_END,
    },
    .rx_range_list2 = {RIG_FRNG_END,},
    .tx_range_list1 = {RIG_FRNG_END,},
    .tx_range_list2 = {RIG_FRNG_END,},
    .tuning_steps =  { {CODAN_MODES, 1}, {CODAN_MODES, RIG_TS_ANY},  RIG_TS_END, },
    .filters = {
        {RIG_MODE_SSB, kHz(2.4)},
        {RIG_MODE_SSB, kHz(0.5)},
        {RIG_MODE_SSB, kHz(2.7)},
        {RIG_MODE_SSB, kHz(3.0)},
        RIG_FLT_END,
    },
    .priv = NULL,

    .rig_init =     codan_init,
    .rig_cleanup =  codan_cleanup,

    .set_freq = codan_set_freq,
    .get_freq = codan_get_freq,
    .set_mode = codan_set_mode,
    .get_mode = codan_get_mode,

    .set_ptt =      codan_set_ptt,
    .get_ptt =      codan_get_ptt,
    .hamlib_check_rig_caps = HAMLIB_CHECK_RIG_CAPS
};



DECLARE_INITRIG_BACKEND(codan)
{
    rig_debug(RIG_DEBUG_VERBOSE, "%s: _init called\n", __func__);

    rig_register(&envoy_caps);
    rig_register(&ngs_caps);
    rig_debug(RIG_DEBUG_VERBOSE, "%s: _init back from rig_register\n", __func__);

    return RIG_OK;
}










