/*  -*-  mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8  -*-  */
/************************************************************************************
 **                                                                                 **
 **                               mcHF QRP Transceiver                              **
 **                             K Atanassov - M0NKA 2014                            **
 **                                                                                 **
 **---------------------------------------------------------------------------------**
 **                                                                                 **
 **  File name:                                                                     **
 **  Description:                                                                   **
 **  Last Modified:                                                                 **
 **  Licence:		GNU GPLv3                                                      **
 ************************************************************************************/

// Common
#include "uhsdr_board.h"
#include "cat_driver.h"
#include "usb_device.h"
#include "usbd_cdc_if.h"

#include <stdio.h>
#include "audio_driver.h"
#include "radio_management.h"
#include "config_storage.h"

uint8_t limit_4bits(uint32_t in)
{
    return in > 15 ? 15 : in;
}
// CAT driver internal structure
typedef struct CatDriver
{
    bool    cat_ptt_active;
    CatInterfaceState state;
    CatInterfaceProtocol protocol;
    uint32_t lastbufferadd_time;

} CatDriver;

// CAT driver state
CatDriver                  cat_driver;


static void CatDriver_CatEnableTX(bool enable)
{
    if (enable)
    {
        if(RadioManagement_IsTxDisabled() == false)
        {
            RadioManagement_Request_TxOn();
            cat_driver.cat_ptt_active = true;
        }
    }
    else
    {
        cat_driver.cat_ptt_active = false;
        RadioManagement_Request_TxOff();
    }
}


/**
 * @brief returns true if the current TX state has been initiated by a CAT PTT command
 */
bool CatDriver_CatPttActive()
{
    return cat_driver.cat_ptt_active;
}

bool CatDriver_CWKeyPressed()
{
    return cdcvcp_ctrllines.dtr != 0;
}

bool CatDriver_PTTKeyPressed()
{
    return ts.enable_ptt_rts? (cdcvcp_ctrllines.rts != 0) : false;
}

bool CatDriver_PTTKeyChangedState()
{
    static bool ptt_key_last_state;

    bool curval = CatDriver_PTTKeyPressed();
    bool retval = ptt_key_last_state != curval;
    ptt_key_last_state = curval;

    return retval;
}


CatInterfaceState CatDriver_GetInterfaceState()
{
    return hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED?CAT_CONNECTED:CAT_DISCONNECTED;
}


#define CAT_BUFFER_SIZE 256
__IO uint8_t cat_buffer[CAT_BUFFER_SIZE];
__IO int32_t cat_head = 0;
__IO int32_t cat_tail = 0;

static uint8_t CatDriver_InterfaceBufferHasData(void)
{
    int32_t len = cat_head - cat_tail;
    return len < 0?len+CAT_BUFFER_SIZE:len;
}



static int cat_buffer_remove(uint8_t* c_ptr)
{
	int ret = 0;

    if (cat_head != cat_tail)
    {
        int c = cat_buffer[cat_tail];
        cat_tail = (cat_tail + 1) % CAT_BUFFER_SIZE;
        *c_ptr = (uint8_t)c;
        ret++;
    }
    return ret;
}

/* no room left in the buffer returns 0 */
int CatDriver_InterfaceBufferAddData(uint8_t c)
{
	int ret = 0;
    int32_t next_head = (cat_head + 1) % CAT_BUFFER_SIZE;

    if (next_head != cat_tail)
    {
        /* there is room */
        cat_buffer[cat_head] = c;
        cat_head = next_head;
        cat_driver.lastbufferadd_time = ts.sysclock;
        ret ++;
    }
    return ret;
}

static void cat_buffer_reset(void)
{
    cat_tail = cat_head;
}

#define CAT_DRIVER_TIMEOUT 30
// defined in increments of 10ms, needs to be longer than the longest running operation
// the mcHF can do. It seems band switching is taking longest time.
// According to the FT817 manual, timeout is 200ms, so we use that plus a little extra and it seems to work

/**
 * Synchronize CAT data and CAT decoder mechanism.
 * As there is a chance that buffers have received erroneous data
 * at least at startup or if the external program stops without completely sending the
 * request data, we expire old data in the request data buffer.
 */
static void cat_driver_sync_data( void)
{
    uint32_t bufsz = CatDriver_InterfaceBufferHasData();
    // we now know how much data is available
    // this MUST BE DONE BEFORE find out how old the data is
    // otherwise a race condition  can occur and we throw away new data.

    if (bufsz)
    {
        if ( ts.sysclock - CAT_DRIVER_TIMEOUT > cat_driver.lastbufferadd_time)
        {
            // if we are here the first bufsz bytes are older than 200ms
            // if in the meantime new bytes arrive, no problem, we keep them
            // since we remove the first bufsz "old data" bytes only from
            // the front of the buffer
            while(bufsz)
            {
                uint8_t c;
                cat_buffer_remove(&c);
                bufsz--;
            }
        }
    }
}

static uint8_t CatDriver_InterfaceBufferGetData(uint8_t* Buf,uint32_t Len)
{
    uint8_t res = 0;
    if (CatDriver_InterfaceBufferHasData() >= Len)
    {
        for  (int i = 0; i < Len; i++)
        {
            cat_buffer_remove(&Buf[i]);
        }
        res = 1;
    }
    return res;
}


static uint8_t CatDriver_InterfaceBufferPutData(uint8_t* Buf,uint32_t Len)
{
    uint8_t res = 0;
    if (CatDriver_GetInterfaceState() == CAT_CONNECTED && Len > 0)
    {
        ;
        res = CDC_Transmit_FS(Buf,Len) == USBD_OK;
    }
    return res;
}


/*

    DUPLEX = ["", "-", "+", "split"]
    # narrow modes has to be at end
    MODES = ["LSB", "USB", "CW", "CWR", "AM", "FM", "DIG", "PKT", "NCW",
             "NCWR", "NFM"]
    TMODES = ["", "Tone", "TSQL", "DTCS"]
    STEPSFM = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
    STEPSAM = [2.5, 5.0, 9.0, 10.0, 12.5, 25.0]
    STEPSSSB = [1.0, 2.5, 5.0]

    # warning ranges has to be in this exact order
    VALID_BANDS = [
                    (100000, 33000000),  0  // USED
                    (33000000, 56000000), 1
                    (76000000, 108000000), 2  // NOT USED
                    (108000000, 137000000), 3 // NOT USED
                    (137000000, 154000000), 4
                    (420000000, 470000000) 5
                    ]
 */
// based on CHIRP ft817.py, gcc needs reversal of allocations inside 8bit
typedef struct {
    uint8_t  mode:3, // LSB, USB, ...
    unknown1:3,
    tag_default:1, // do we show the automatically generate name CH-XXX
    tag_on_off:1; // do we show the stored name?

    uint8_t freq_range:3,
    is_fm_narrow:1,
    is_cwdig_narrow:1,
    is_duplex:1, // is a duplex memory
    duplex:2; // how to interpret the offset with relation to the freq entry
    // 0 -> no offset / second freq
    // 1 -> - RX is freq - offset
    // 2 -> + RX is freq + offset
    // 3 -> split frequency, offset is used to store second frequency
    uint8_t unknown3:4,
    att:1, // attenuator on, NOT USED
    ipo:1, // IPO attenuator on, NOT USED
    unknown2:1,
    skip:1;

    uint8_t  fm_step:3,
        am_step:3,
        ssb_step:2;

    uint8_t tmode:2,
    unknown4:6;

    uint8_t tx_freq_range:3,  // VFO B?
    tx_mode:3, // VFO B?
    unknown5:2;

    uint8_t tone:6,
    unknown_toneflag:1,
    unknown6:1;

    uint8_t  dcs:7,
    unknown7:1;

    uint16_t rit; // ul16
    uint32_t freq; // ul32 -> USED VFO A
    uint32_t offset; // ul32 -> USED, VFO B
    uint8_t  name[8];  // USED
} __attribute__((packed)) ft817_memory_t ;

typedef struct {
    uint8_t  fst:1,
        lock:1,
        nb:1,
        pbt:1,
        unknownb:1,
        dsp:1,
        agc:2;
    uint8_t  vox:1,
        vlt:1,
        bk:1,
        kyr:1,
        unknown5:1,
        cw_paddle:1,
        pwr_meter_mode:2;
    uint8_t  vfob_band_select:4,
        vfoa_band_select:4;
    uint8_t  unknowna;
    uint8_t  backlight:2,
        color:2,
        contrast:4;
    uint8_t  beep_freq:1,
        beep_volume:7;
    uint8_t  arts_beep:2,
        main_step:1,
        cw_id:1,
        scope:1,
        pkt_rate:1,
        resume_scan:2;
    uint8_t  op_filter:2,
        lock_mode:2,
        cw_pitch:4;
    uint8_t  sql_rf_gain:1,
        ars_144:1,
        ars_430:1,
        cw_weight:5;
    uint8_t  cw_delay;
    uint8_t  unknown8:1,
        sidetone:7;
    uint8_t  batt_chg:2,
        cw_speed:6;
    uint8_t  disable_amfm_dial:1,
        vox_gain:7;
    uint8_t  cat_rate:2,
        emergency:1,
        vox_delay:5;
    uint8_t  dig_mode:3,
        mem_group:1,
        unknown9:1,
        apo_time:3;
    uint8_t  dcs_inv:2,
        unknown10:1,
        tot_time:5;
    uint8_t  mic_scan:1,
        ssb_mic:7;
    uint8_t  mic_key:1,
        am_mic:7;
    uint8_t  unknown11:1,
        fm_mic:7;
    uint8_t  unknown12:1,
        dig_mic:7;
    uint8_t  extended_menu:1,
        pkt_mic:7;
    uint8_t  unknown14:1,
        pkt9600_mic:7;
    int16_t dig_shift; // il16
    int16_t dig_disp;  // il16
    int8_t  r_lsb_car;
    int8_t  r_usb_car;
    int8_t  t_lsb_car;
    int8_t  t_usb_car;
    uint8_t  unknown15:2,
        menu_item:6;
    uint8_t  unknown16:4,
        menu_sel:4;
    uint16_t unknown17;
    uint8_t  art:1,
        scn_mode:2,
        dw:1,
        pri:1,
        unknown18:1,
        tx_power:2;
    uint8_t  spl:1,
        unknown:1,
        uhf_antenna:1,
        vhf_antenna:1,
        air_antenna:1,
        bc_antenna:1,
        sixm_antenna:1,
        hf_antenna:1;
} __attribute__((packed)) ft871_settings_t ;

typedef struct {
    uint8_t len;
    uint8_t count;
} ft817_block_t;

// FT817 (not ND!)
const ft817_block_t cloneblock_len[] =
{
        {2,1},      //0  -> 2
        {40,1},     // -> 42
        {208,1},    // -> 250
        {182,1},    // -> 432
        {208,1},    // -> 640
        {182,1},    // -> 822
        {198,1},    // -> 1020
        {53,1},     // -> 1073
        {130,40},   // -> 6273 (+5200)
        {118,1},    // -> 6391
        {118,1}     // -> 6509
};

/*
@0x4:
    ft817_settings_t settings;
@0x2A: -> @42 -> block [2]
        struct mem_struct vfoa[15]; // block[2+3]
        struct mem_struct vfob[15]; // block[4+5]
        struct mem_struct home[4]; //  block[6]...
        struct mem_struct qmb;
        struct mem_struct mtqmb;
        struct mem_struct mtune;   //  ...block[6]

@0x3FD: @1021               // block[7]+1
        uint8_t visible[25];
        uint8_t pmsvisible;

@0x417: @1047
        uint8_t filled[25];      // block[7]+27
        uint8_t pmsfilled;

@0x431: @1073
        struct mem_struct memory[200]; block[9]-block[48]
        struct mem_struct pms[2]; block[49]

@0x18cf: @6351
        uint8_t callsign[7];

@0x1979: @6521
        struct mem_struct sixtymeterchannels[5]; // not FT817, only ND US

*/

typedef enum {
    CLONEOUT_INIT = 0,
    CLONEOUT_BLOCK_SEND,
    CLONEOUT_BLOCK_ACK_WAIT,
    CLONEOUT_BLOCK_ACK_NACK,
    CLONEOUT_DONE

} ft817_clone_out_st;
// #define DEBUG_FT817
typedef enum {
    CLONEIN_INIT = 0,
    CLONEIN_BLOCK_RECV,
    CLONEIN_BLOCK_RECV_START,
    CLONEIN_FINAL_PROCESSING,
    CLONEIN_DONE

} ft817_clone_in_st;


typedef enum {
    CAT_INIT = 0,
    CAT_CAT,
    CAT_CLONEOUT,
    CAT_CLONEIN
} ft817_cat_st;


struct FT817
{
    uint8_t req[5];
    ft817_cat_st state;
    ft817_clone_out_st cloneout_state;
    ft817_clone_in_st clonein_state;
// #define DEBUG_FT817
#ifdef DEBUG_FT817
#define FT817_MAX_CMD 100
    uint8_t reqs[FT817_MAX_CMD*5];
    uint32_t cmd_cntr;
#endif
};

#include "ui_driver.h"
#include "uhsdr_board.h"

// FT817 Emulation
#if 0
// list of commands supported by hamlib
static const yaesu_cmd_set_t ncmd[] =
{
    { 1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, /* lock on */
    { 1, { 0x00, 0x00, 0x00, 0x00, 0x80 } }, /* lock off */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x08 } }, /* ptt on */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x88 } }, /* ptt off */
    +{ 0, { 0x00, 0x00, 0x00, 0x00, 0x01 } }, /* set freq */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main LSB */
    +{ 1, { 0x01, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main USB */
    +{ 1, { 0x02, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main CW */
    +{ 1, { 0x03, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main CWR */
    +{ 1, { 0x04, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main AM */
    +{ 1, { 0x08, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main FM */
    +{ 1, { 0x88, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main FM-N */
    +{ 1, { 0x0a, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main DIG */
    +{ 1, { 0x0c, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main PKT */
    { 1, { 0x00, 0x00, 0x00, 0x00, 0x05 } }, /* clar on */
    { 1, { 0x00, 0x00, 0x00, 0x00, 0x85 } }, /* clar off */
    { 0, { 0x00, 0x00, 0x00, 0x00, 0xf5 } }, /* set clar freq */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x81 } }, /* toggle vfo a/b */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x02 } }, /* split on */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x82 } }, /* split off */
    { 1, { 0x09, 0x00, 0x00, 0x00, 0x09 } }, /* set RPT shift MINUS */
    { 1, { 0x49, 0x00, 0x00, 0x00, 0x09 } }, /* set RPT shift PLUS */
    { 1, { 0x89, 0x00, 0x00, 0x00, 0x09 } }, /* set RPT shift SIMPLEX */
    { 0, { 0x00, 0x00, 0x00, 0x00, 0xf9 } }, /* set RPT offset freq */
    { 1, { 0x0a, 0x00, 0x00, 0x00, 0x0a } }, /* set DCS on */
    { 1, { 0x2a, 0x00, 0x00, 0x00, 0x0a } }, /* set CTCSS on */
    { 1, { 0x4a, 0x00, 0x00, 0x00, 0x0a } }, /* set CTCSS encoder on */
    { 1, { 0x8a, 0x00, 0x00, 0x00, 0x0a } }, /* set CTCSS/DCS off */
    { 0, { 0x00, 0x00, 0x00, 0x00, 0x0b } }, /* set CTCSS tone */
    { 0, { 0x00, 0x00, 0x00, 0x00, 0x0c } }, /* set DCS code */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0xe7 } }, /* get RX status  */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0xf7 } }, /* get TX status  */
    { 1, { 0x00, 0x00, 0x00, 0x00, 0x03 } }, /* get FREQ and MODE status */
    { 1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, /* pwr wakeup sequence */
    +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x0f } }, /* pwr on */
    { 1, { 0x00, 0x00, 0x00, 0x00, 0x8f } }, /* pwr off */
    ?{ 0, { 0x00, 0x00, 0x00, 0x00, 0xbb } }, /* eeprom read */
}




#endif

typedef enum {
    FT817EE_INVALID = 0,
    FT817EE_DATA,
    FT817EE_FUNC,
    FT817EE_STOP
} ft817_ee_entry_t;

typedef struct {
    ft817_ee_entry_t type;
    uint16_t start;
    uint16_t end;
    union {
    bool (*funcPtr)(bool read, uint16_t addr, uint8_t* data_p);
    uint8_t value;
    } content;
} ft817_eeprom_emul_t;


// here we defined the eeprom emulation
// for some tools we may want to support either reading a fixed value
// or even reading/writing actual information from/to mcHF
// all other addresses will indicate failure

bool CatDriver_Ft817_EEPROM_RW_Func(bool readEEPROM, uint16_t addr, uint8_t* data_p)
{
    bool retval = false;
    if (readEEPROM == false)
    {
        switch(addr)
        {

        case 0x55:
        {
            uint8_t vfo_selection = data_p[0] & 0x81;
            if (
                    (vfo_selection == 0x81 && is_vfo_b() == false)
                    ||
                    (vfo_selection == 0x80 && is_vfo_b() == true)
            )
            {
                // we have to switch active vfo
                UiAction_ToggleVfoAB();
                retval = true;
            }
            break;
        }
        }
    }
    else
    {
        switch(addr)
        {
        /*
        HRD:
        case 0x56:

        case 0x57:
        case 0x58:

        case 0x5F:
        case 0x60:

        case 0x80

        case 0x7B:
         */
        case 0x55:
            // Used by: HRD
            // Bit 0: 0 -> VFO A, 1 -> VFO B
            // Bit 5: 0 -> Memory, 1 -> MTUNE
            // Bit 6: Unknown
            // Bit 7: 0 -> Memory Mode, 1 -> VFO
            *data_p = is_vfo_b()? 0x81:0x80;

            // TODO: If memmode gets implmented, we need more complex handling here
            retval = true;
            break;
        case 0x65:
            // lower bits not relevant bits 5-7
            // 000 -> RTTY
            // 001 -> PSK31-L
            // 010 -> PSK31-U
            // 011 -> USER-L
            // 100 -> USER-U
            // we always report USER-U/USER-L independent from currently selected digital mode
            // on the UHSDR TRX
            *data_p = RadioManagement_LSBActive(DEMOD_DIGI) ? 0b01100000 : 0b10000000;
            retval = true;
            break;
        case 0x79:
        {
            // Used by: HRD
            // Bit 0 - 1: TX Power 00 - Hi -> 11: L1 (lowest)
            // Bit 2: Unknown
            // Bit 3: PRI On?
            // Bit 4: DW On?
            // Bit 5-6: Scan Mode: 00 No, 10 Scan Up, 11 Scan Down
            // Bit 7: ART On?
            uint8_t powerlevel = 0;
            switch (ts.power_level)
            {
            case PA_LEVEL_MINIMAL:
                powerlevel = 3;
                break;
            case PA_LEVEL_LOW:
                powerlevel = 2;
                break;
            case PA_LEVEL_MEDIUM:
                powerlevel = 1;
                break;
            default:
                // we report 5W and FULL as High
                // TODO: or shall we merge two other power settings?
                powerlevel = 0;
                break;
            }
            *data_p = powerlevel;
            retval = true;
            break;
        }
        case 0x7A:
        {
            // Used by: HRD, N1MM
            // Bit 0 - 5: Antenna Select   HF/6m/FM Broadcast/Airband/2m/70cm
            // Bit 6: Unknown
            // Bit 7 indicates split mode
            *data_p = is_splitmode() ? 0x80:00;
            retval = true;
            break;
        }
        }
    }
    return retval;
}


// WSJT-X: Wants to read from 0x64, no write
static const ft817_eeprom_emul_t ft817_eeprom[] =
{
        { .type = FT817EE_DATA,     .start = 0,     .end = 3,       .content.value = 0      }, // checksum
        { .type = FT817EE_DATA,     .start = 4,     .end = 4,       .content.value = 0xD8   }, // radio config
        { .type = FT817EE_DATA,     .start = 5,     .end = 5,       .content.value = 0xBF   }, // radio config
        { .type = FT817EE_DATA,     .start = 0x54,  .end = 0x54,    .content.value = 0xFF }, // this is just to make HAMLIB happy when reading 0x55 by requesting 0x54
// (and ignoring this value). This might break things if an application really wants to read a useful value from 0x54
        { .type = FT817EE_FUNC,     .start = 0x55,  .end = 0x55,    .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // VOX Delay and other stuff, fixed value, required by WSJT-X
        { .type = FT817EE_DATA,     .start = 0x64,  .end = 0x64,    .content.value = 0x00 }, // VOX Delay and other stuff, fixed value, required by WSJT-X
        { .type = FT817EE_FUNC,     .start = 0x65,  .end = 0x65,    .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // APO and Dig Mode setting, fixed value, required by WSJT-X
        { .type = FT817EE_FUNC,     .start = 0x79,  .end = 0x79,    .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // VOX Delay and other stuff, fixed value, required by WSJT-X
        { .type = FT817EE_FUNC,     .start = 0x7A,  .end = 0x7A,    .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // VOX Delay and other stuff, fixed value, required by WSJT-X
        { .type = FT817EE_DATA,     .start = 0x7B,  .end = 0x7B,    .content.value = 0x00 }, // Chg related, not supported

        { .type = FT817EE_STOP }
};

static bool CatDriver_Ft817_EEPROM_Write(uint16_t addr,uint8_t* data_p)
{
    bool retval = false;

    for(int idx = 0; ft817_eeprom[idx].type != FT817EE_STOP && addr >= ft817_eeprom[idx].start;idx++)
    {
        if (ft817_eeprom[idx].start <= addr && addr <= ft817_eeprom[idx].end)
        {
            switch(ft817_eeprom[idx].type)
            {
            case FT817EE_FUNC:
                retval = (*ft817_eeprom[idx].content.funcPtr)(false, addr,data_p);
                break;
            default:
                break;
            }
            break;
        }
    }

    return retval;
}

static bool CatDriver_Ft817_EEPROM_Read(uint16_t addr,uint8_t* data_p)
{
    bool retval = false;

    for(int idx = 0; ft817_eeprom[idx].type != FT817EE_STOP && addr >= ft817_eeprom[idx].start;idx++)
    {
        if (ft817_eeprom[idx].start <= addr && addr <= ft817_eeprom[idx].end)
        {
            switch(ft817_eeprom[idx].type)
            {
            case FT817EE_DATA:
                *data_p = ft817_eeprom[idx].content.value;
                retval = true;
                break;
            case FT817EE_FUNC:
                retval = (*ft817_eeprom[idx].content.funcPtr)(true, addr,data_p);
                break;
            default:
                break;
            }

            break;
        }
    }

    return retval;
}


typedef enum
{
    FT817_SET_FREQ      = 0x01,
    FT817_GET_FREQ      = 0x03,
    FT817_SPLIT_ON      = 0x02,
    FT817_SPLIT_OFF     = 0x82,
    FT817_PTT_ON        = 0x08,
    FT817_PTT_OFF       = 0x88,
    FT817_MODE_SET      = 0x07,
    FT817_PWR_ON        = 0x0f,
    FT817_TOGGLE_VFO    = 0x81,
    FT817_A7            = 0xa7,
    FT817_EEPROM_READ   = 0xbb,
    FT817_EEPROM_WRITE  = 0xbc,
    FT817_READ_TX_STATE = 0xbd,
    FT817_READ_RX_STATE = 0xe7,
    FT817_PTT_STATE     = 0xf7,
    FT817_NOOP          = 0xff,

    UHSDR_ID            = 0x42, // this command is not known to the FT817 so we can use this to identify a UHSDR
} Ft817_CatCmd_t;

struct FT817 ft817;


uint8_t CatDriver_Clone_Checksum(uint8_t* buf, size_t len)
{
    uint8_t retval = 0;
    for (int idx = 0; idx < len; idx++)
    {
        retval += buf[idx];
    }
    return retval;
}

void CatDriver_BlockPrepare(uint8_t num, uint8_t idx, uint8_t rpt, uint8_t* buf, size_t maxlen)
{
    buf[0] = num;
    if (num == 7)
    {
        buf[2] = 0x1; // Memory[0] visible;
        buf[2+26] = 0x1; // Memory[0] filled;
    }
    if (num == 8)
    {
        ft817_memory_t* mem = (ft817_memory_t*)&buf[1];
        mem->freq=__builtin_bswap32(700100); // need to convert big / little endian!!
        mem->tag_on_off = 1;
        mem->freq_range = 0;
        mem->am_step = 1;
        mem->ssb_step = 1;
        mem->tone = 0x08;
        memcpy(&mem->name[0],"A1234567",8);
    }
    buf[cloneblock_len[idx ].len+1] = CatDriver_Clone_Checksum(&buf[1],cloneblock_len[idx].len);
}

bool CatDriver_BlockRecv(uint8_t num, uint8_t idx, uint8_t rpt, uint8_t* buf, size_t len)
{
    bool retval = false;

    if (buf[0] == num  && (buf[len - 1]  == CatDriver_Clone_Checksum(&buf[1],len-2)))
    {
        if (num == 8)
        {
            // we simply set the dial frequency here just for the show!
            ft817_memory_t* mem = (ft817_memory_t*)&buf[1];
            df.tune_new = __builtin_bswap32(mem[0].freq) * 10;
        }
        retval = true;
    }

    return retval;
}

#define CLONE_CMD_ACK (0x06)

void CatDriver_CloneSendAck()
{
    uint8_t cmd_ack = CLONE_CMD_ACK;
    CatDriver_InterfaceBufferPutData(&cmd_ack,1);
}

void CatDriver_BlockSend(uint8_t* buf, size_t len)
{
    CatDriver_InterfaceBufferPutData(buf,len);
}


bool CatDriver_BlockAck()
{
    bool retval = false;

    while(CatDriver_InterfaceBufferHasData())
    {
//        uint8_t c;
//        CatDriver_InterfaceBufferGetData(&c,1);
//        if (c == CLONE_CMD_ACK)
        uint8_t c = 0;
        if (CatDriver_InterfaceBufferGetData(&c,1))
        {
//            retval = true;
            retval =  c == CLONE_CMD_ACK;
            break;
        }
    }
    return retval;
}


static void CatDriver_HandleCloneOut(void)
{
    static uint16_t blockIdx = 0;
    static uint16_t blockRpt = 0;
    static uint16_t blockNum = 0;
    static uint8_t buf[256];
    static uint32_t last_sysclk;

    switch (ft817.cloneout_state)
    {
    case CLONEOUT_INIT:
    {
        blockIdx = 0;
        blockRpt = 0;
        blockNum = 0;
        ft817.cloneout_state = CLONEOUT_BLOCK_SEND;
        break;
    }
    case CLONEOUT_BLOCK_SEND:
    {
        CatDriver_BlockPrepare(blockNum,blockIdx,blockRpt,buf,256);
        CatDriver_BlockSend(buf,cloneblock_len[blockIdx].len+2);
        // +2 since we added blocknum and checksum, each 8bit.
        ft817.cloneout_state = CLONEOUT_BLOCK_ACK_WAIT;
        last_sysclk = ts.sysclock + 100;
        break;
    }
    case CLONEOUT_BLOCK_ACK_WAIT:
    {
        if (CatDriver_BlockAck())
        {
            blockNum++;
            blockRpt++;
            if (blockRpt == cloneblock_len[blockIdx].count)
            {
               blockIdx++;
               blockRpt = 0;
            }

            if (blockIdx == 11)
            {
                ft817.cloneout_state = CLONEOUT_DONE;
            }
            else
            {
                ft817.cloneout_state = CLONEOUT_BLOCK_SEND;
            }
        }
        else
        {
            if (last_sysclk < ts.sysclock)
            {
                // after a while we will give up
                ft817.cloneout_state = CLONEOUT_BLOCK_ACK_NACK;
            }
        }
        break;
    }
    case CLONEOUT_BLOCK_ACK_NACK:
    {
        ft817.cloneout_state = CLONEOUT_DONE;
        break;
    }
    case CLONEOUT_DONE:
    {
        // go back to normal CAT MODE and prepare for next round
        ft817.cloneout_state = CLONEOUT_INIT;
        ft817.state = CAT_INIT;
        break;
    }
    }

}

static void CatDriver_HandleCloneIn(void)
{
    static uint16_t blockIdx = 0;
    static uint16_t blockRpt = 0;
    static uint16_t blockNum = 0;
    static uint8_t buf[256];
    static uint8_t blockWant = 0;
    static uint32_t last_sysclk;

    switch (ft817.clonein_state)
    {
    case CLONEIN_INIT:
    {
        blockIdx = 0;
        blockRpt = 0;
        blockNum = 0;
        ft817.clonein_state = CLONEIN_BLOCK_RECV_START;
        break;
    }
    case CLONEIN_BLOCK_RECV_START:
    {
        last_sysclk = ts.sysclock + 600; // that is 6s, enough to start the CAT clone transmit on the PC
        blockWant = cloneblock_len[blockIdx].len + 2; // two more for blocknum and checksum
        ft817.clonein_state = CLONEIN_BLOCK_RECV;
        break;
    }
    case CLONEIN_BLOCK_RECV:
    {
        // we can ask for the full amount since our buffer will be able to hold all of the packets contents
        if (CatDriver_InterfaceBufferGetData(buf,blockWant))
        {
            // analyse block
            if (CatDriver_BlockRecv(blockNum,blockIdx,blockRpt,buf,blockWant))
            {
                // now continue
                CatDriver_CloneSendAck();
                blockNum++;
                blockRpt++;
                if (blockRpt == cloneblock_len[blockIdx].count)
                {
                    blockIdx++;
                    blockRpt = 0;
                }
                if (blockIdx == 11)
                {
                    // we are done receiving, so now lets do the final data processing
                    ft817.clonein_state = CLONEIN_FINAL_PROCESSING;
                }
                else
                {
                    ft817.clonein_state = CLONEIN_BLOCK_RECV_START;
                }

            }
            else
            {
                ft817.clonein_state = CLONEIN_DONE;
            }
        }
        else if (last_sysclk <= ts.sysclock)
        {
            // timeout
            ft817.clonein_state = CLONEIN_DONE;
        }
        break;
    }
    case CLONEIN_FINAL_PROCESSING:
    {
        // TODO: Now that all infos have been received, do the processing of it
        // TBW

        // once done, get back to normal CAT operation
        ft817.clonein_state = CLONEIN_DONE;
        break;
    }
    case CLONEIN_DONE:
    {
        // go back to normal CAT MODE and prepare for next round
        ft817.clonein_state = CLONEIN_INIT;
        ft817.state = CAT_INIT;
        break;
    }
    }

}


bool CatDriver_CloneOutStart()
{
    bool retval = false;
    if (ft817.state == CAT_CAT)
    {
        retval = true;
        ft817.state = CAT_CLONEOUT;
        ft817.cloneout_state = CLONEOUT_INIT;
    }
    return retval;
}

bool CatDriver_CloneInStart()
{
    bool retval = false;
    if (ft817.state == CAT_CAT)
    {
        retval = true;
        ft817.state = CAT_CLONEIN;
        ft817.clonein_state = CLONEIN_INIT;
    }
    return retval;
}


static void CatDriver_HandleCommands(void)
{
    uint8_t bc = 0;
    uint8_t resp[32];

    cat_driver_sync_data();

    while (CatDriver_InterfaceBufferGetData(ft817.req,5))
    {
#ifdef DEBUG_FT817
        int debug_idx;
        for (debug_idx = 0; debug_idx < 5 && ft817.cmd_cntr < FT817_MAX_CMD; debug_idx++ )
        {
            ft817.reqs[ft817.cmd_cntr*5+debug_idx] = ft817.req[debug_idx];
        }
        ft817.cmd_cntr++;
#endif

        switch((Ft817_CatCmd_t)ft817.req[4])
        {
        case FT817_SET_FREQ:
        {
            ulong f = 0;
            ulong fdelta;

            if(ts.xlat == 0)
            {
                fdelta = (ts.tx_audio_source == TX_AUDIO_DIGIQ)?AudioDriver_GetTranslateFreq():0;
                // If we are in DIGITAL IQ Output mode, use real tune frequency frequency instead
                // translated RX frequency
            }
            else
            {
                fdelta = 0;
            }

            int fidx;
            for (fidx = 0; fidx < 4; fidx++)
            {
                f *= 100;
                f +=  (ft817.req[fidx] >> 4) * 10 + (ft817.req[fidx] & 0x0f);
            }
            f *= 10;
            df.tune_new = f - fdelta;

            resp[0] = 0;
            bc = 1;
            if(ts.flags1 & FLAGS1_CAT_IN_SANDBOX)           // if running in sandbox store active band
            {
                ts.cat_band_index = ts.band->band_mode;
            }
        }
        break;

        case FT817_GET_FREQ:
        {
            ulong fdelta;

            if(ts.xlat == 0)
            {
                fdelta = (ts.tx_audio_source == TX_AUDIO_DIGIQ)?AudioDriver_GetTranslateFreq():0;
                // If we are in DIGITAL IQ Output mode, send real tune frequency frequency instead
                // translated RX frequency
            }
            else
            {
                fdelta = 0;
            }

            ulong f = (df.tune_new + fdelta  + 5)/ 10;
            ulong fbcd = 0;
            int fidx;
            for (fidx = 0; fidx < 8; fidx++)
            {
                fbcd >>= 4;
                fbcd |= (f % 10) << 28;
                f = f / 10;
            }

            resp[0] = (uint8_t)(fbcd >> 24);
            resp[1] = (uint8_t)(fbcd >> 16);
            resp[2] = (uint8_t)(fbcd >> 8);
            resp[3] = (uint8_t)fbcd;
        }
        switch(ts.dmod_mode)
        {
        case DEMOD_LSB:
            resp[4] = 0;
            break;
        case DEMOD_USB:
            resp[4] = 1;
            break;
        case DEMOD_CW:
            resp[4] = 2 + (ts.cw_lsb==true?1:0);
            break;
            // return 3 if CW in LSB aka CW-R
        case DEMOD_SAM:
        case DEMOD_AM:
            resp[4] = 4;
            break;
        case DEMOD_FM:
            resp[4] = 8;
            break;
        default:
            resp[4] = 1;
        }
        bc = 5;
        break;
        case FT817_MODE_SET:
        {
            resp[0] = 0; // ACK
            bc = 1;

            uint8_t new_mode = ts.dmod_mode;
            bool new_cwlsb = ts.cw_lsb;
            uint32_t new_fmdev5khz = RadioManagement_FmDevIs5khz();
            switch (ft817.req[0])
            {
            case 0: // LSB
                new_mode = DEMOD_LSB;
                break;
            case 1: // USB
                new_mode = DEMOD_USB;
                break;
            case 2: // CW
                new_cwlsb = false;
                new_mode = DEMOD_CW;
                break;
            case 3: // CW-R
                new_cwlsb = true;
                new_mode = DEMOD_CW;
                break;
            case 4: // AM
                new_mode = DEMOD_AM;
                break;
            case 8: // FM
                new_fmdev5khz = true;
                new_mode = DEMOD_FM;
                break;
            case 0x88: // FM-N
                new_fmdev5khz = false;
                new_mode = DEMOD_FM;
                break;
            case 0x0a: // DIG - SSB, side band controlled by some menu configuration in ft817, we use USB here
                new_mode = DEMOD_USB;
                break;
            case 0x0c: // PKT - FM, 9k6
                new_mode = DEMOD_FM;
                break;
            default:
                resp[0] = 0xFF; // NACK
            }
            if  (new_mode != ts.dmod_mode || new_cwlsb != ts.cw_lsb || new_fmdev5khz != RadioManagement_FmDevIs5khz() )
            {
                if(ts.flags1 & FLAGS1_CAT_IN_SANDBOX)           // if running in sandbox store active band
                {
                    ts.cat_band_index = ts.band->band_mode;
                }
                RadioManagement_FmDevSet5khz(new_fmdev5khz);
                ts.cw_lsb = new_cwlsb;
                RadioManagement_SetDemodMode(new_mode);
                UiDriver_UpdateDisplayAfterParamChange();
            }
        }
        break;
        case FT817_PTT_ON:
            resp[0] = cat_driver.cat_ptt_active?0xF0:0x00;
            /* 0xF0 if PTT was already on */

            CatDriver_CatEnableTX(true);

            bc = 1;
            break;
        case FT817_PWR_ON:
            resp[0] = 0;
            bc = 1;
            break;
        case FT817_TOGGLE_VFO:
            UiAction_ToggleVfoAB();
            resp[0] = 0;
            bc = 1;
            break;
        case FT817_SPLIT_ON:
            UiDriver_SetSplitMode(true);
            resp[0] = 0;
            bc = 1;
            break;
        case FT817_SPLIT_OFF:
            UiDriver_SetSplitMode(false);
            resp[0] = 0;
            bc = 1;
            break;
        case FT817_PTT_OFF:
            resp[0] = cat_driver.cat_ptt_active?0x00:0xF0; /* 0xF0 if PTT was already off */
            bc = 1;
            CatDriver_CatEnableTX(false);
            break;
        case FT817_A7: /* A7 */
            resp[0]=0xA7;
            resp[1]=0x02;
            resp[2]=0x00;
            resp[3]=0x04;
            resp[4]=0x67;
            resp[5]=0xD8;
            resp[6]=0xBF;
            resp[7]=0xD8;
            resp[8]=0xBF;
            bc = 9;
            break;
        case FT817_EEPROM_READ:
            // with a full simulation, we would return from 0 to 0x1925 two bytes: data@addr + data@(addr+1)
            // and above 0x1925 only 1 byte. So we call our function and see what it returns.
        {
            resp[0] = 0;
            resp[1] = 0;
            uint16_t ee_addr = (ft817.req[0] << 8) | ft817.req[1];
            if (CatDriver_Ft817_EEPROM_Read(ee_addr,&resp[0]) && ee_addr < 0x1925)
            {
                // please note: in case of second addr being invalid
                // we still return success and two bytes, second data byte is 0 in this case
                CatDriver_Ft817_EEPROM_Read(ee_addr+1,&resp[1]);
                bc = 2;
            }
            // we now check if we are supposed to return config values
            else if (ee_addr > 0x7FFF)
            {
                ConfigStorage_ReadVariable(ee_addr & 0x7FFF, (uint16_t*)&resp[0]);
                bc = 2;
            }
            else
            {
                bc = 1;
            }
            break;
        }
        case FT817_EEPROM_WRITE:
        {
            // no op in most cases
            resp[0] = 0;
            bc = 1;
            uint16_t ee_addr = (ft817.req[0] << 8) | ft817.req[1];
            if (ee_addr < 0x1925)
            {
                CatDriver_Ft817_EEPROM_Write(ee_addr,&ft817.req[2]);
                CatDriver_Ft817_EEPROM_Write(ee_addr+1,&ft817.req[3]);
            }
            else if (ee_addr > 0x7FFF)
            {
                uint16_t ee_value16 = (ft817.req[3] << 8) | ft817.req[2];
                ConfigStorage_WriteVariable(ee_addr & 0x7FFF, ee_value16);
            }

            break;
        }
        case FT817_READ_TX_STATE:
            if(RadioManagement_IsTxDisabled()||(ts.txrx_mode != TRX_MODE_TX))
            {
                resp[0] = 0;
                bc = 1;
            }
            else
            {
                // PWR / SWR
                resp[0] =(limit_4bits(roundf(swrm.fwd_pwr))<<4)+limit_4bits(roundf(swrm.vswr_dampened));
                // ALC / MOD
                resp[1] = (limit_4bits(0)<<4)+limit_4bits(0);
                bc = 2;
            }

            break;
        case FT817_READ_RX_STATE: /* E7 */
            resp[0] = (uint8_t)roundf(sm.s_count*0.5);   //S-Meter signal
            bc = 1;
            break;
        case FT817_PTT_STATE:
        {
            uint8_t tx_state = limit_4bits(roundf(swrm.fwd_pwr));
            tx_state |= is_splitmode() ? 0x20 : 0x00;
//            tx_state |= ts.txrx_mode == TRX_MODE_TX ? 0x00 : 0x80;
            tx_state |= swrm.vswr_dampened > 3.0 ? 0x40 : 0x00;

//            resp[0]= ts.txrx_mode == TRX_MODE_TX ? tx_state : 0x80;
            // the only correct response when in rx mode is 0xff, confirmed
            // with a real FT-817
            resp[0]= ts.txrx_mode == TRX_MODE_TX ? tx_state : 0xff;
            if(RadioManagement_IsTxDisabled())
            {
                resp[0] = 0xFF;
            }
            bc = 1;
            break;
        }
        case FT817_NOOP: /* FF sent out by HRD */
            break;
            // default:
            // while (1);

        case UHSDR_ID: /* used to identify the UHSDR EXTEND CAT SUPPORT */
            resp[0] = 'U';
            resp[1] = 'H';
            resp[2] = 'S';
            resp[3] = 'D';
            resp[4] = 'R';
            bc = 5;
            break;
            // default:
            // while (1);

        }
        CatDriver_InterfaceBufferPutData(resp,bc);
        /* Return data back */
    }
}

void CatDriver_HandleProtocol()
{

    if (CatDriver_GetInterfaceState() == CAT_DISCONNECTED)
    {
        if (ft817.state != CAT_INIT)
        {
            cat_buffer_reset();
            ft817.state = CAT_INIT;
        }
    }
    else
    {
        switch(ft817.state)
        {
        case CAT_CLONEOUT:
            CatDriver_HandleCloneOut();
            break;
        case CAT_CLONEIN:
            CatDriver_HandleCloneIn();
            break;
        case CAT_INIT:
            ft817.cloneout_state = CLONEOUT_INIT;
            ft817.clonein_state = CLONEIN_INIT;
            ft817.state = CAT_CAT;
            /* fall through */  // this is for the compiler, the following comment is for Eclipse
            /* no break */
        case CAT_CAT:
            CatDriver_HandleCommands();
            break;
        }
    }

#if 1
    // TODO: factor out
    // handle emulated serial PTT Pin Handling
    if (CatDriver_PTTKeyChangedState())
    {
        if (CatDriver_PTTKeyPressed())
        {
            if (CatDriver_CatPttActive() == false)
            {
                CatDriver_CatEnableTX(true);
            }
        }
        else
        {
            if (CatDriver_CatPttActive() == true)
            {
                CatDriver_CatEnableTX(false);
            }
        }
    }
#endif
}
