/*
 *  Hamlib Uniden backend - uniden_digital backend
 *  Copyright (c) 2001-2008 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 "serial.h"
#include "misc.h"

#include "uniden_digital.h"


/*
 * Uniden Digital backend should work for:
 * BCD996T as well as the BCD396T.  Some protocols available for the
 * BCD996T may not be available for the BCD396T or modified to work.
 *
 * Protocol information can be found here:
 * http://www.uniden.com/files/BCD396T_Protocol.pdf
 * http://www.uniden.com/files/BCD996T_Protocol.pdf
 *
 * There are undocumented commands such as firmware_dump and
 * firmware_load.  These commands are defined within DSctl code.
 *
 * There are two methods of retrieving the next memory location
 * (aka frequency bank).  Use either the "Get Next Location" or
 * use the address returned from one of the commands.  If you decide
 * the latter method, the order is slightly confusing but, I have it
 * well documented within DSctl.  The latter method is also as much
 * as 30% faster then using the Uniden software or "Get Next
 * Location" command.
 */

#if 0
// deactivated temporarily because uniden_id_string_list is nowhere used
static const struct { rig_model_t model; const char *id; }
uniden_id_string_list[] =
{
    { RIG_MODEL_BCD396T,  "BCD396T" },
    { RIG_MODEL_BCD996T,  "BCD996T" },
    { RIG_MODEL_NONE, NULL },   /* end marker */
};
#endif

/* EOM is not consistant with this BCD996T!
 * Some commands  return newline while others carriage return.
 * Some commands return nothing special for End of Line char. */
#define EOM "\r"  /* end of message */

/* I'm still getting clipped output from buffers  being too small elsewhere! */
#define BUFSZ 256  /* Wild guess, 64 was too small. */

/**
 * uniden_transaction
 * uniden_digital_transaction
 * Assumes rig!=NULL rig->state!=NULL rig->caps!=NULL
 *
 * cmdstr - Command to be sent to the rig. Cmdstr can also be NULL, indicating
 *          that only a reply is needed (nothing will be send).
 * replystr - Reply prefix to be expected. Replystr can also be NULL, indicating
 *          that the prefix is either the cmdstr prefix or OK.
 * data - Buffer for reply string.  Can be NULL, indicating that no reply is
 *        is needed and will return with RIG_OK after command was sent.
 * datasize - in: Size of buffer. It is the caller's responsibily to provide
 *            a large enough buffer for all possible replies for a command.
 *            out: location where to store number of bytes read.
 *
 * returns:
 *   RIG_OK  -  if no error occured.
 *   RIG_EIO  -  if an I/O error occured while sending/receiving data.
 *   RIG_ETIMEOUT  -  if timeout expires without any characters received.
 *   RIG_REJECTED  -  if a negative acknowledge was received or command not
 *                    recognized by rig.
 */
int
uniden_digital_transaction(RIG *rig, const char *cmdstr, int cmd_len,
                           const char *replystr,
                           char *data, size_t *datasize)
{
    struct rig_state *rs;
    int retval;
    int retry_read = 0;
    char replybuf[BUFSZ];
    size_t reply_len = BUFSZ;

    rs = &rig->state;
    rs->hold_decode = 1;

transaction_write:

    serial_flush(&rs->rigport);

    if (cmdstr)
    {
        retval = write_block(&rs->rigport, cmdstr, strlen(cmdstr));

        if (retval != RIG_OK)
        {
            goto transaction_quit;
        }
    }

    /* Always read the reply to known if it went OK */
    if (!data)
    {
        data = replybuf;
    }

    if (!datasize)
    {
        datasize = &reply_len;
    }

    memset(data, 0, *datasize);
    retval = read_string(&rs->rigport, data, *datasize, EOM, strlen(EOM));

    if (retval < 0)
    {
        if (retry_read++ < rig->state.rigport.retry)
        {
            goto transaction_write;
        }

        goto transaction_quit;
    }
    else
    {
        *datasize = retval;
    }

    /* Check that command termination is correct
     * FIXME: Sometimes the BCD996T DOES NOT return  a
     * consistant carriage return or newline.
     * ie: STS command will not return either "\r" or "\n"! */
    /*if (strchr(EOM, data[strlen(data)-1])==NULL) {
        rig_debug(RIG_DEBUG_ERR, "%s: Command is not correctly terminated '%s'\n", __func__, data);
        if (retry_read++ < rig->state.rigport.retry)
            goto transaction_write;
        retval = -RIG_EPROTO;
        goto transaction_quit;
    }*/

    if (strcmp(data, "OK"EOM))
    {
        /* everything is fine */
        retval = RIG_OK;
        goto transaction_quit;
    }

    /*  Any syntax returning NG indicates a VALID Command but not entered
     *  in the right mode or using the correct parameters. ERR indicates
     *  an INVALID Command.
     */
    if (strcmp(data, "NG"EOM))
    {
        /* Invalid command */
        rig_debug(RIG_DEBUG_VERBOSE,
                  "%s: Command Format Error / Value Error for '%s'\n", __func__, cmdstr);
        retval = -RIG_EPROTO;
        goto transaction_quit;
    }

    if (strcmp(data, "ERR"EOM))
    {
        /*  Command format error */
        rig_debug(RIG_DEBUG_VERBOSE,
                  "%s: The Command is Invalid at this Time for '%s'\n", __func__, cmdstr);
        retval = -RIG_EINVAL;
        goto transaction_quit;
    }

    if (strcmp(data, "FER"EOM))
    {
        /*  Framing error */
        rig_debug(RIG_DEBUG_VERBOSE, "%s: Framing Error for '%s'\n", __func__, cmdstr);
        retval = -RIG_EINVAL;
        goto transaction_quit;
    }

    if (strcmp(data, "ORER"EOM))
    {
        /*  Overrun error */
        rig_debug(RIG_DEBUG_VERBOSE, "%s: Overrun Error for '%s'\n", __func__, cmdstr);
        retval = -RIG_EINVAL;
        goto transaction_quit;
    }

#define CONFIG_STRIP_CMDTRM 1
#ifdef CONFIG_STRIP_CMDTRM

    if (strlen(data) > 0)
    {
        data[strlen(data) - 1] = '\0';    /* not very elegant, but should work. */
    }
    else
    {
        data[0] = '\0';
    }

#endif

    /* Special case for SQuelch */
    /*if (!memcmp(cmdstr,"SQ",2) && (replystr[0] == '-' || replystr[0] == '+')) {
        retval = RIG_OK;
        goto transaction_quit;
    }*/

    /* Command prefix if no replystr supplied */
    if (!replystr)
    {
        replystr = cmdstr;
    }

    /*
     * Check that received the correct reply. The first two characters
     * should be the same as command.
     */
    if (replystr && replystr[0] && (data[0] != replystr[0] ||
                                    (replystr[1] && data[1] != replystr[1])))
    {
        /*
         * TODO: When RIG_TRN is enabled, we can pass the string
         *       to the decoder for callback. That way we don't ignore
         *       any commands.
         */
        rig_debug(RIG_DEBUG_ERR, "%s: Unexpected reply '%s'\n", __func__, data);

        if (retry_read++ < rig->state.rigport.retry)
        {
            goto transaction_write;
        }

        retval =  -RIG_EPROTO;
        goto transaction_quit;
    }

    retval = RIG_OK;
transaction_quit:
    rs->hold_decode = 0;
    return retval;
}


/*
 * uniden_get_info
 * Assumes rig!=NULL
 */
const char *uniden_digital_get_info(RIG *rig)
{
    static char infobuf[BUFSZ];
    size_t info_len = BUFSZ / 2, mdlinfo_len = BUFSZ / 2;
    int ret;


    /* GET CURRENT STATUS -- STS */
    ret = uniden_digital_transaction(rig, "STS" EOM, 3, NULL, infobuf, &info_len);

    /* NOTE FOR ME: Check Buffer Size with what we got returned in info_len.
     * Don't know the max length of return on these units, so check frequently!
     * Use five v's (-vvvvv) to activate output. */
    rig_debug(RIG_DEBUG_VERBOSE, "%s: DEBUG BUFSZ'%i'\n", __func__, BUFSZ);
    rig_debug(RIG_DEBUG_VERBOSE, "%s: DEBUG info_len'%i'\n", __func__,
              (int)info_len);

    if (ret != RIG_OK)
    {
        return NULL;
    }

    /* Example output:
     * STS,011000,          XXX     ,,Fa
     *
     * STS command returns 3 lines including system, truck, freq info
     *
     * XXX indicates the BCD996T returns some non-printable ascii chars
     * within it's comma separated fields. See pg 30-32 of BCD996T_Protocol.pdf.
     * These chars cause abnomalies on stdout! */

    /* FIXME: Strip or replace non-printable chars return from STS command!
     * (Below is a snip from DSctl utils.c file)
     *
     * For example, search each character.  For each sequence of chars found, replace with normal printable ascii [KEY]
     * if (strstr(bcd_chars, "\213") != 0)
     *   strncat(tmp_line, "[FUNCTION KEY] ", 15);
     *
     * if (strstr(bcd_chars, "\215\216\217\220") != 0)
     *   strncat(tmp_line, "[HOLD] ", 7); */

    if (info_len < 4)
    {
        return NULL;
    }

    if (info_len >= BUFSZ)
    {
        rig_debug(RIG_DEBUG_VERBOSE, "%s: DEBUG Max BUFSZ Reached: info_len  = '%i'\n",
                  __func__, (int)info_len);
        info_len = BUFSZ - 1;
    }

    infobuf[info_len] = '\0';

    /* VR not on every rig <- This doesn't belong here for the newer BCD* units*/
    /* VR1.00 */
    /*ret = uniden_digital_transaction (rig, "VR" EOM, 2, NULL, infobuf+info_len, &vrinfo_len);
    if (ret == RIG_OK)
    {*/
    /* overwrite "VR" */
    /* FIXME: need to filter \r or it screws w/ stdout */
    /*infobuf[info_len] = '\n';
    infobuf[info_len+1] = ' ';
    }
    else
    {
    infobuf[info_len] = '\0';
    }*/


    /* GET MODEL INFO -- MDL */
    ret = uniden_digital_transaction(rig, "MDL" EOM, 3, NULL, infobuf + info_len,
                                     &mdlinfo_len);

    if (ret == RIG_OK)
    {
        infobuf[info_len] = '\n';
        infobuf[info_len + 1] = ' ';
    }
    else
    {
        infobuf[info_len] = '\0';
    }

    /* GET FIRMWARE VERSION -- VER */
    ret = uniden_digital_transaction(rig, "VER" EOM, 3, NULL, infobuf + info_len,
                                     &mdlinfo_len);

    if (ret == RIG_OK)
    {
        infobuf[info_len] = '\n';
        infobuf[info_len + 1] = ' ';
    }
    else
    {
        infobuf[info_len] = '\0';
    }


    /* skip beginning "STS, " */
    /* FIXME: What about clipping the above two other  MDL & VER Commands? */
    return infobuf + 4;
}


/*
 * skeleton
 */
int uniden_digital_set_freq(RIG *rig, vfo_t vfo, freq_t freq)
{
#if 0
    char freqbuf[BUFSZ];
    size_t freq_len = BUFSZ;

    /* freq in hundreds of Hz */
    freq /= 100;

    /* exactly 8 digits */
    freq_len = sprintf(freqbuf, "RF%08u" EOM, (unsigned)freq);

    return uniden_transaction(rig, freqbuf, freq_len, NULL, NULL, NULL);
#else
    return -RIG_ENIMPL;
#endif
}

/*
 * skeleton
 */
int uniden_digital_get_freq(RIG *rig, vfo_t vfo, freq_t *freq)
{
#if 0
    char freqbuf[BUFSZ];
    size_t freq_len = BUFSZ;
    int ret;

    ret = uniden_transaction(rig, "RF" EOM, 3, NULL, freqbuf, &freq_len);

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

    if (freq_len < 10)
    {
        return -RIG_EPROTO;
    }

    sscanf(freqbuf + 2, "%"SCNfreq, freq);
    /* returned freq in hundreds of Hz */
    *freq *= 100;

    return RIG_OK;
#else
    return -RIG_ENIMPL;
#endif
}

