/*  -*-  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                                                      **
 ************************************************************************************/
#include "ui_driver.h"
#include "freedv_uhsdr.h"
#include "ui_lcd_layouts.h"
#include "profiling.h"
#include "ui_lcd_hy28.h"
#include "radio_management.h"

#if defined(USE_FREEDV) || defined(USE_ALTERNATE_NR)

MultiModeBuffer_t __MCHF_SPECIALMEM mmb;
#endif

#ifdef USE_FREEDV

#include "freedv_api.h"

freedv_conf_t freedv_conf;

struct freedv *f_FREEDV;


RingBuffer_DefineExtMem(fdv_demod_rb,sizeof(mmb.fdv_demod_buff)/sizeof(fdv_demod_rb_item_t), mmb.fdv_demod_buff)
RingBuffer_DefineExtMem(fdv_iq_rb,sizeof(mmb.fdv_iq_buff)/sizeof(fdv_iq_rb_item_t), mmb.fdv_iq_buff)

#define FDV_AUDIO_MEM_SIZE ((FDV_BUFFER_SIZE*2)+IQ_BLOCK_SIZE)
__MCHF_SPECIALMEM fdv_audio_rb_item_t fdv_audio_rb_mem[FDV_AUDIO_MEM_SIZE];
RingBuffer_DefineExtMem(fdv_audio_rb, FDV_AUDIO_MEM_SIZE, fdv_audio_rb_mem)

typedef struct {
    int32_t start;
    int32_t offset;
    int32_t count;
} flex_buffer;

static uint16_t freedv_display_x_offset;

/**
 * Returns the internal UHSDR  configuration value in the value range for display and use with the freedv_api
 * @param freedv_conf_p
 * @return SNR in the range of -100 to 99
 */

int32_t FreeDV_Get_Squelch_SNR(freedv_conf_t* freedv_conf_p)
{
    return (int32_t)(freedv_conf_p->squelch_snr_thresh) + FDV_SQUELCH_OFFSET;
}

/**
 * Sets the internal UHSDR  configuration value in the permitted value range
 * @param freedv_conf_p
 * @param squelch_snr  SNR in the range of -100 to 99 , -100 represents OFF
 */
void FreeDV_Set_Squelch_SNR(freedv_conf_t* freedv_conf_p, int8_t squelch_snr)
{
    int32_t internal_snr = squelch_snr - FDV_SQUELCH_OFFSET;
    if (internal_snr > FDV_SQUELCH_MAX)
    {
        internal_snr = FDV_SQUELCH_MAX;
    }
    else if (internal_snr < FDV_SQUELCH_OFF)
    {
        internal_snr = FDV_SQUELCH_OFF;
    }

    freedv_conf_p->squelch_snr_thresh = internal_snr;
}

int FreeDV_Is_Squelch_Enable(freedv_conf_t* freedv_conf_p)
{
    return freedv_conf_p->squelch_snr_thresh != FDV_SQUELCH_OFF;
}

void FreeDV_Squelch_Update(freedv_conf_t* freedv_conf_p)
{
    UNUSED(freedv_conf_p);
    freedv_set_squelch_en(f_FREEDV, FreeDV_Is_Squelch_Enable(&freedv_conf));
    freedv_set_snr_squelch_thresh(f_FREEDV, FreeDV_Get_Squelch_SNR(&freedv_conf));
}

static void FreeDv_DisplayBer(void)
{
    int ber = 0;
    char ber_string[12];



    ber = 1000*freedv_get_total_bit_errors(f_FREEDV)/freedv_get_total_bits(f_FREEDV);
    snprintf(ber_string,12,"0.%03d",ber);  //calculate and display the bit error rate
    UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x + freedv_display_x_offset,ts.Layout->FREEDV_BER.y, ber_string,Yellow,Black, ts.Layout->FREEDV_FONT);

}

static void FreeDv_DisplaySnr(void)
{
    static float SNR = 1;
    float SNR_est;
    char SNR_string[12];
    int sync;

    freedv_get_modem_stats(f_FREEDV,&sync,&SNR_est);

    SNR = 0.95*SNR + 0.05 * SNR_est; //some averaging to keep it more calm
    int32_t SNR_Int = roundf(SNR);

    if (SNR_Int > 99)
    {
        SNR_Int = 99;
    }
    else if (SNR_Int < -99)
    {
        SNR_Int = -99;
    }

    uint32_t clr_fg;
    if (FreeDV_Is_Squelch_Enable(&freedv_conf))
    {
        clr_fg = SNR_Int >= FreeDV_Get_Squelch_SNR(&freedv_conf)? Green:Red;
    }
    else
    {
        clr_fg = Yellow;
    }

    snprintf(SNR_string,12,"%-2ld",SNR_Int);  //Display the current SNR and round it up to the next int
    UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x + freedv_display_x_offset, ts.Layout->FREEDV_SNR.y ,SNR_string,clr_fg,Black, ts.Layout->FREEDV_FONT);
}

void FreeDv_DisplayClear()
{
//    UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y,"            ",Yellow,Black,ts.Layout->FREEDV_FONT);
//   UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y,"            ",Yellow,Black,ts.Layout->FREEDV_FONT);
    UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y,"      ",Yellow,Black,ts.Layout->FREEDV_FONT);		//SNR=00
    UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y,"           ",Yellow,Black,ts.Layout->FREEDV_FONT);		//BER=0.000	(max 9 chars)
    UiDriver_TextMsgClear();
}

void FreeDv_DisplayPrepare()
{
	UiDriver_TextMsgClear();
	freedv_display_x_offset = UiLcdHy28_TextWidth("SNR=", ts.Layout->FREEDV_FONT);
    UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y,"SNR=",Yellow,Black, ts.Layout->FREEDV_FONT);
    UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y,"BER=",Yellow,Black, ts.Layout->FREEDV_FONT);
}

void FreeDv_DisplayUpdate()
{
    FreeDv_DisplayBer();
    FreeDv_DisplaySnr();
    UiDriver_TextMsgDisplay();
}


void FreeDv_HandleFreeDv()
{

    // Freedv DL2FW
    // static FDV_Out_Buffer FDV_TX_out_im_buff;
    static bool tx_was_here = false;
    static bool rx_was_here = true;


     // we are always called, so check if there is FreeDV active
    if (ts.digital_mode == DigitalMode_FreeDV)
    {
        if ((ts.txrx_mode == TRX_MODE_TX)
                && RingBuffer_GetData(&fdv_audio_rb) >= freedv_get_n_speech_samples(f_FREEDV)
                && RingBuffer_GetRoom(&fdv_iq_rb) >= freedv_get_n_nom_modem_samples(f_FREEDV))
        {
            // ...and if we are transmitting and samples from dv_tx_processor are ready
            if (tx_was_here == false)
            {
                RingBuffer_ClearGetTail(&fdv_audio_rb);
                RingBuffer_ClearPutHead(&fdv_iq_rb);
                tx_was_here = true;
                rx_was_here = false;
            }
            COMP    iq_buffer[freedv_get_n_nom_modem_samples(f_FREEDV)];
            int16_t audio_buffer[freedv_get_n_speech_samples(f_FREEDV)];

            RingBuffer_GetSamples(&fdv_audio_rb, &audio_buffer, freedv_get_n_speech_samples(f_FREEDV));

            profileTimedEventStart(7);
            freedv_comptx(f_FREEDV,
                    iq_buffer,
                    audio_buffer); // start the encoding process
            profileTimedEventStop(7);

            for (int idx = 0; idx < freedv_get_n_nom_modem_samples(f_FREEDV); idx++)
            {
                fdv_iq_rb_item_t sample;
                sample.real = iq_buffer[idx].real;
                sample.imag = iq_buffer[idx].imag;

                RingBuffer_PutSamples(&fdv_iq_rb, &sample, 1);
            }

        }
        else if ((ts.txrx_mode == TRX_MODE_RX))
        {


            if (rx_was_here == false)
            {
                RingBuffer_ClearGetTail(&fdv_demod_rb);
                RingBuffer_ClearPutHead(&fdv_audio_rb);

                freedv_set_total_bit_errors(f_FREEDV,0);  //reset ber calculation after coming from TX
                freedv_set_total_bits(f_FREEDV,0);

                rx_was_here = true; // this is used to clear buffers when going into TX
                tx_was_here = false;
            }

            // these buffers are large enough to hold the requested/provided amount of data for freedv_comprx
            // these are larger than the FDV_BUFFER_SIZE since some more bytes may be asked for.
#ifdef USE_SIMPLE_FREEDV_FILTERS
            #define     input_rb fdv_iq_rb
            #define     input_rb_item_t fdv_iq_rb_item_t
            #define     FREEDV_RX_FUNC freedv_comprx
            #define     FREEDV_RX_INPUT_T COMP
#else
            #define     input_rb fdv_demod_rb
            #define     input_rb_item_t fdv_demod_rb_item_t
            #define     FREEDV_RX_FUNC freedv_rx
            #define     FREEDV_RX_INPUT_T int16_t
#endif
            FREEDV_RX_INPUT_T input_buffer[freedv_get_n_max_modem_samples(f_FREEDV)];
            int16_t audio_buffer[freedv_get_n_max_modem_samples(f_FREEDV)];

            // while makes this highest prio
            // if may give more responsiveness but can cause interrupted reception
            while (RingBuffer_GetData(&input_rb) >= freedv_nin(f_FREEDV)
                    && RingBuffer_GetRoom(&fdv_audio_rb) >= freedv_nin(f_FREEDV))
            {
                // MchfBoard_GreenLed(LED_STATE_OFF);
                 // if we arrive here the rx_buffer is full enough and will be consumed now.
#ifdef USE_SIMPLE_FREEDV_FILTERS
                for (int idx = 0; idx < freedv_nin(f_FREEDV); idx++ )
                {
                    input_rb_item_t sample;
                    RingBuffer_GetSamples(&input_rb, &sample, 1);
                    input_buffer[idx].real = sample.real;
                    input_buffer[idx].imag = sample.imag;
                }
#else
                RingBuffer_GetSamples(&input_rb, &input_buffer, freedv_nin(f_FREEDV));
#endif

                int count = FREEDV_RX_FUNC(f_FREEDV, audio_buffer, input_buffer); // run the decoding process

                if (freedv_get_sync(f_FREEDV) != 0)
                {
                    RingBuffer_PutSamples(&fdv_audio_rb, &audio_buffer, count);
                }
            }
        }
    }
    else
    {
        if (tx_was_here == true || rx_was_here == true)
        {
            tx_was_here = false;
            rx_was_here = false;
            RingBuffer_ClearGetTail(&fdv_iq_rb);
            RingBuffer_ClearGetTail(&fdv_audio_rb);
        }
    }
}


// FreeDV txt test - will be out of here
typedef struct {
    char  tx_str[80];
    char *ptx_str;
} my_callback_state_t;

static my_callback_state_t  my_cb_state;

static char my_get_next_tx_char(void *callback_state) {
    my_callback_state_t* pstate = (my_callback_state_t*)callback_state;

    char  c = *pstate->ptx_str++;

    if (*pstate->ptx_str == 0) {
        pstate->ptx_str = pstate->tx_str;
    }

    return c;
}


static void my_put_next_rx_char(void *callback_state, char ch)
{
    UNUSED(callback_state);
    UiDriver_TextMsgPutChar(ch);
}

freedv_mode_desc_t freedv_modes[] =
{
        { "1600", "FD1600", FREEDV_MODE_1600 },
#ifdef USE_FREEDV_700D
        { "700D", "FD700D", FREEDV_MODE_700D },
#endif
};

const uint8_t freedv_modes_num = sizeof(freedv_modes)/sizeof(freedv_modes[0]);


// FreeDV txt test - will be out of here
void  FreeDV_Init()
{
    // Freedv Test DL2FW

     // move this to ui_configuration;
     FreeDV_Set_Squelch_SNR(&freedv_conf,-2);
     freedv_conf.mode = 0; // 0 = 1600, 1 = 700D

     FreeDV_SetMode(freedv_conf.mode, true);

}

/**
 *
 * @return true if mode was activated, false if old mode is still active
 */
int32_t FreeDV_SetMode(uint8_t fdv_mode, int32_t firstTime)
{
    bool retval = true;

    // turn off rx audio processing to be able to disable and reenable FreeDV processing
    ads.af_disabled++;

    // normal init starts
    if (fdv_mode >= freedv_modes_num)
    {
        fdv_mode = 0;
    }

    // we don't switch if TX is currently active
    if (firstTime == false)
    {
        retval = (ts.txrx_mode == TRX_MODE_RX);
        if (retval == true && f_FREEDV != NULL && freedv_conf.mode != fdv_mode)
        {
            freedv_close(f_FREEDV);
            f_FREEDV = NULL;
        }
    }

    // only if all went well AND we have not yet initialized the mode,
    // we are going to run the mode, so if same mode is being set, nothing happens
    // really
    if (retval && f_FREEDV == NULL)
    {
        f_FREEDV = freedv_open(freedv_modes[fdv_mode].freedv_id);

        retval = f_FREEDV != NULL;
        if (retval)
        {
            sprintf(my_cb_state.tx_str, ts.special_functions_enabled == 1 ? FREEDV_TX_DF8OE_MESSAGE : FREEDV_TX_MESSAGE);

            my_cb_state.ptx_str = my_cb_state.tx_str;
            freedv_set_callback_txt(f_FREEDV, &my_put_next_rx_char, &my_get_next_tx_char, &my_cb_state);

            if (FreeDV_Is_Squelch_Enable(&freedv_conf))
            {
                freedv_set_squelch_en(f_FREEDV,1);
                freedv_set_snr_squelch_thresh(f_FREEDV, FreeDV_Get_Squelch_SNR(&freedv_conf));
            }
            else
            {
                freedv_set_squelch_en(f_FREEDV,0);
            }

            freedv_set_tx_bpf(f_FREEDV, 0);
        }
    }

    if (retval)
    {
        freedv_conf.mode = fdv_mode;
    }

    // turn on rx audio processing, FreeDV is ready for work
    ads.af_disabled--;

    return retval;
}
int32_t FreeDV_Iq_Get_FrameLen()
{
    return freedv_get_n_nom_modem_samples(f_FREEDV);
}

int32_t FreeDV_Audio_Get_FrameLen()
{
    return freedv_get_n_speech_samples(f_FREEDV);
}


#ifdef DEBUG_FREEDV
void FreeDV_Test()
{
    ts.digital_mode = DigitalMode_FreeDV;
    ts.txrx_mode = TRX_MODE_RX;
    while (1)
    {
        for (int idx = 0; idx < FREEDV_TEST_BUFFER_FRAME_SIZE*FREEDV_TEST_BUFFER_FRAME_COUNT; idx++)
        {
            int16_t sample[2];
            sample[0] = test_buffer[idx].real;
            sample[1] = test_buffer[idx].imag;

            RingBuffer_PutSamples(&fdv_iq_rb,&sample,2);
            FreeDv_HandleFreeDv();
            RingBuffer_GetSamples(&fdv_audio_rb,sample,1);
        }
    }

}
#endif

#endif
