/**
 * @file cariboulite_radio.h
 * @author David Michaeli
 * @date March 2023
 * @brief System level radio API
 *
 * Radio device implementation exposing TxRx API
 */
#ifndef __CARIBOULABS_RADIO_H__
#define __CARIBOULABS_RADIO_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "at86rf215/at86rf215.h"


/**
 * @brief Radio channel direction
 */
typedef enum
{
    cariboulite_channel_dir_rx = 0,
    cariboulite_channel_dir_tx = 1,
} cariboulite_channel_dir_en;

typedef enum
{
    cariboulite_channel_s1g = 0,
    cariboulite_channel_6g = 1,
} cariboulite_channel_en;

typedef enum
{
    cariboulite_ext_ref_off     = 0,
    cariboulite_ext_ref_26mhz   = 26,
    cariboulite_ext_ref_32mhz   = 32,
} cariboulite_ext_ref_freq_en;

// Frequency Ranges
#define CARIBOULITE_6G_MIN      (1.0e6)
#define CARIBOULITE_6G_MAX      (6000.0e6)
#define CARIBOULITE_MIN_LO      (85.0e6)
#define CARIBOULITE_MAX_LO      (4200.0e6)
#define CARIBOULITE_2G4_MIN     (2385.0e6)
#define CARIBOULITE_2G4_MAX     (2495.0e6)
#define CARIBOULITE_S1G_MIN1    (377.0e6)
#define CARIBOULITE_S1G_MAX1    (530.0e6)
#define CARIBOULITE_S1G_MIN2    (779.0e6)
#define CARIBOULITE_S1G_MAX2    (1020.0e6)

typedef enum
{
    conversion_dir_none = 0,
    conversion_dir_up = 1,
    conversion_dir_down = 2,
} cariboulite_conversion_dir_en;

// Radio Struct
typedef struct
{
    struct sys_st_t*               		sys;
    cariboulite_channel_dir_en          channel_direction;
    cariboulite_channel_en              type;
    bool                                active;
    bool                                cw_output;
    bool                                lo_output;
 
    // MODEM STATES
    at86rf215_radio_state_cmd_en        state;
    at86rf215_radio_irq_st              interrupts;

    bool                                rx_agc_on;
    int                                 rx_gain_value_db;
    
    at86rf215_radio_rx_bw_en            rx_bw;
    at86rf215_radio_f_cut_en            rx_fcut;
    at86rf215_radio_sample_rate_en      rx_fs;

    int                                 tx_power;
    at86rf215_radio_tx_cut_off_en       tx_bw;
    at86rf215_radio_f_cut_en            tx_fcut;
    at86rf215_radio_sample_rate_en      tx_fs;

    // at86rf215_radio_energy_detection_st rx_energy_detection;
    float                               rx_energy_detection_value;
    float                               rx_rssi;

    // FREQUENCY
    bool                                modem_pll_locked;
    bool                                lo_pll_locked;
    double                              lo_frequency;
    double                              if_frequency;
    double                              actual_rf_frequency;
    double                              requested_rf_frequency;
    double                              rf_frequency_error;

    // SMI STREAMS
    caribou_smi_channel_en              smi_channel_id;

    // OTHERS
    uint8_t                             random_value;
    float                               rx_thermal_noise_floor;
} cariboulite_radio_state_st;

/**
 * @brief Initialize a radio device
 *
 * Initialize a radio device allocated and given by the user. The radio device
 * is setup to a known state and the internal variables (in the struct) are initialized.
 * Note: This function shouldn't normally be used by the end-user as the radio module
 *       is initialized by the "driver initializations sequence" in "cariboulite_setup.h"
 *       and is ready to be used as the driver loading finishes
 *
 * @param radio a pre-allocated radio state structure to initialize
 * @param sys a pointer to the system environment containing the relevant
 *            low level drivers and init / close logic.
 * @param type the type of the channel (6g, or s1g)
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_init(cariboulite_radio_state_st* radio, struct sys_st_t *sys, cariboulite_channel_en type);
/**
 * @brief Disposing the radio device
 *
 * Remove and free the resources taken by the radio device. The system returns to
 * its known state.
 * Note: This function shouldn't normally be used by the end-user as the radio module
 *       is disposed by the "driver closing sequence" in "cariboulite_setup.h"
 *       and is supposed to be hidden
 *
 * @param radio a pre-allocated radio state structure
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_dispose(cariboulite_radio_state_st* radio);
/**
 * @brief Synchronizing radio struture contents to the hardware actual state
 *
 * Reading all the relevant modem internal (hardware) states to update and synchronize
 * the software state to them.
 *
 * @param radio a pre-allocated radio state structure
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_sync_information(cariboulite_radio_state_st* radio);
/**
 * @brief Set modem's external refernece ferquency
 *
 * This ext reference is needed for the mixer to lock
 *
 * @param sys a pointer to the system environment containing the relevant
 *            low level drivers and init / close logic.
 * @param ref the used reference source (none, 26MHz, 32MHz, etc.)
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_ext_ref (struct sys_st_t *sys, cariboulite_ext_ref_freq_en ref);

/**
 * @brief Get modem chip state
 *
 * Getting the modem current state
 *
 * @param radio a pre-allocated radio state structure
 * @param state the modem state (pre-allocated pointer), nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_mod_state (cariboulite_radio_state_st* radio, at86rf215_radio_state_cmd_en *state);

/**
 * @brief Get modem irq table
 *
 * Reading the IRQ states table of the modem
 *
 * @param radio a pre-allocated radio state structure
 * @param irq_table a table of interrupts state of the modem (nullable if not needed)
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_mod_intertupts (cariboulite_radio_state_st* radio, at86rf215_radio_irq_st **irq_table);

/**
 * @brief Modem Rx gain control (write)
 *
 * Setting the Rx gain properties of the modem
 *
 * @param radio a pre-allocated radio state structure
 * @param rx_agc_on enable modem automatic gain control
 * @param rx_gain_value_db if AGC is disabled, here we can set the gain manually
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_set_rx_gain_control(cariboulite_radio_state_st* radio, 
                                    bool rx_agc_on,
                                    int rx_gain_value_db);

/**
 * @brief Modem Rx gain control (read)
 *
 * Getting the Rx gain properties of the modem
 *
 * @param radio a pre-allocated radio state structure
 * @param rx_agc_on modem automatic gain control enabled
 * @param rx_gain_value_db when AGC is disabled this is the actual gain control
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_rx_gain_control(cariboulite_radio_state_st* radio,
                                    bool *rx_agc_on,
                                    int *rx_gain_value_db);

/**
 * @brief Modem Rx gain capabilities
 *
 * Reading the modem possible gain limits on Rx
 *
 * @param radio a pre-allocated radio state structure
 * @param rx_min_gain_value_db modem gain limit low [dB]
 * @param rx_max_gain_value_db modem gain limit high [dB]
 * @param rx_gain_value_resolution_db modem gain steps [dB]
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_rx_gain_limits(cariboulite_radio_state_st* radio, 
                                    int *rx_min_gain_value_db,
                                    int *rx_max_gain_value_db,
                                    int *rx_gain_value_resolution_db);

/**
 * @brief Modem set RX analog bandwidth
 *
 * Setting the RX analog bandwidth
 *
 * @param radio a pre-allocated radio state structure
 * @param rx_bw bandwidth value
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_set_rx_bandwidth(cariboulite_radio_state_st* radio, 
                                 at86rf215_radio_rx_bw_en rx_bw);

/**
 * @brief Modem get RX analog bandwidth
 *
 * Getting the RX analog bandwidth
 *
 * @param radio a pre-allocated radio state structure
 * @param rx_bw bandwidth value (pointer - pre-allocated), nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_rx_bandwidth(cariboulite_radio_state_st* radio, 
                                 at86rf215_radio_rx_bw_en *rx_bw);

/**
 * @brief Modem set RX sample cut-off bandwidth
 *
 * Setting the RX channel sample cut-off bandwidth (digital)
 *
 * @param radio a pre-allocated radio state structure
 * @param rx_sample_rate the used rx sample rate
 * @param rx_cutoff digital bandwidth
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_set_rx_samp_cutoff(cariboulite_radio_state_st* radio, 
                                   at86rf215_radio_sample_rate_en rx_sample_rate,
                                   at86rf215_radio_f_cut_en rx_cutoff);

/**
 * @brief Modem get RX sample cut-off bandwidth
 *
 * Getting the RX channel sample cut-off bandwidth (digital)
 *
 * @param radio a pre-allocated radio state structure
 * @param rx_sample_rate the used rx sample rate (pointer - pre-allocated), nullable if not needed
 * @param rx_cutoff digital bandwidth (pointer - pre-allocated), nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_rx_samp_cutoff(cariboulite_radio_state_st* radio, 
                                   at86rf215_radio_sample_rate_en *rx_sample_rate,
                                   at86rf215_radio_f_cut_en *rx_cutoff);

/**
 * @brief Modem set TX power
 *
 * Setting the Modem's output TX power towards the RFFE
 *
 * @param radio a pre-allocated radio state structure
 * @param tx_power_dbm The TX power in dBm
 * @return 0 = success, -1 = failure
 */

int cariboulite_radio_set_tx_power(cariboulite_radio_state_st* radio, 
                             int tx_power_dbm);

/**
 * @brief Modem get TX power
 *
 * Getting the Modem's output TX power towards the RFFE
 *
 * @param radio a pre-allocated radio state structure
 * @param tx_power_dbm The TX power in dBm (pointer - pre-allocated), nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_tx_power(cariboulite_radio_state_st* radio, 
                             int *tx_power_dbm);

/**
 * @brief Modem set Tx analog Bandwidth
 *
 * Setting the Modem's Tx analog bandwidth
 *
 * @param radio a pre-allocated radio state structure
 * @param tx_bw bandwidth (according to enumeration)
 * @return 0 = success, -1 = failure
 */

int cariboulite_radio_set_tx_bandwidth(cariboulite_radio_state_st* radio, 
                                 at86rf215_radio_tx_cut_off_en tx_bw);

/**
 * @brief Modem get Tx analog Bandwidth
 *
 * Getting the current modem's Tx analog bandwidth
 *
 * @param radio a pre-allocated radio state structure
 * @param tx_bw bandwidth (according to enumeration) - pointer, pre-allocated or nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_tx_bandwidth(cariboulite_radio_state_st* radio, 
                                 at86rf215_radio_tx_cut_off_en *tx_bw);

/**
 * @brief Modem set Tx digital bandwidth
 *
 * Setting the modem's Tx digital cut-off bandwidth
 *
 * @param radio a pre-allocated radio state structure
 * @param tx_sample_rate Tx sample rate
 * @param tx_cutoff digital cut-off bandwidth
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_set_tx_samp_cutoff(cariboulite_radio_state_st* radio, 
                                   at86rf215_radio_sample_rate_en tx_sample_rate,
                                   at86rf215_radio_f_cut_en tx_cutoff);

/**
 * @brief Modem get Tx digital bandwidth
 *
 * Getting the current modem's Tx digital cut-off bandwidth
 *
 * @param radio a pre-allocated radio state structure
 * @param tx_sample_rate Tx sample rate, pointer, pre-allocated or nullable if not needed
 * @param tx_cutoff digital cut-off bandwidth, pointer, pre-allocated or nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_tx_samp_cutoff(cariboulite_radio_state_st* radio, 
                                   at86rf215_radio_sample_rate_en *tx_sample_rate,
                                   at86rf215_radio_f_cut_en *tx_cutoff);


/**
 * @brief Modem get current RSSI
 *
 * Getting the current receive signal strength indication
 *
 * @param radio a pre-allocated radio state structure
 * @param rssi_dbm the RSSI measurement in dBm
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_rssi(cariboulite_radio_state_st* radio, float *rssi_dbm);
/**
 * @brief Modem get current Energy Detection Value
 *
 * Getting the Rx energy detection value
 *
 * @param radio a pre-allocated radio state structure
 * @param energy_det_val the energy value measurement in dBm
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_energy_det(cariboulite_radio_state_st* radio, float *energy_det_val);
/**
 * @brief Modem get a random value
 *
 * The modem has an internal true random noise generation capability. This function
 * returnes the current stored value
 *
 * @param radio a pre-allocated radio state structure
 * @param uint8_t random value, pointer, pre-allocated or nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_rand_val(cariboulite_radio_state_st* radio, uint8_t *rnd);

bool cariboulite_radio_wait_mixer_lock(cariboulite_radio_state_st* radio, int retries);
/**
 * @brief Wait for all PLLs to lock
 *
 * In the modem this function waits for a safe completion of frequency switch. Until
 * the modem indicates that the frequency was correctly set, the frequency switch should
 * not be takes as granted.
 *
 * @param radio a pre-allocated radio state structure
 * @param retries number of retries (typically up ot 5)
 * @return "0" = didn't lock during the period or "1" - locked successfully
 */
bool cariboulite_radio_wait_modem_lock(cariboulite_radio_state_st* radio, int retries);
/**
 * @brief Set modem frequency
 *
 * Setting the current frequency of the modem. The resulting frequency
 * may be slightly different according to the internal PLL granularity
 * and the resulting frequency is written back to the given pointer.
 * The user may set the "break before make" to stich off the activation
 * bit (reset Rx or Tx) and change the frequency in a cold state
 *
 * @param radio a pre-allocated radio state structure
 * @param break_before_make break the current activity and only then set the frequency
 * @param freq the frequency in Hz, a pointer that carries the frequency to set and 
 *             returns back the actual set frequency after the operation is done
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_set_frequency(cariboulite_radio_state_st* radio, 
									bool break_before_make,
									double *freq);

/**
 * @brief Get current actual frequency
 *
 * Returns the current modem frequecny (which can be slightly different
 * for the one set by the "set frequency" function)
 *
 * @param radio a pre-allocated radio state structure
 * @param freq the frequnecy in Hz, pointer, pre-allocated or nullable if not needed
 * @param lo in case of the high band channel, the LO of the mixer (otherwise unused)
 * @param i_f in case of the high band channel, this is the IF frequency (otherwise unused)
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_frequency(cariboulite_radio_state_st* radio, 
                                	double *freq, double *lo, double* i_f);

/**
 * @brief Activate the channel in a certain state
 *
 * Activates the Tx / Rx channel according to the "dir" parameter (either Tx or Rx). 
 * If we want to de-activate the channel, "dir" doesn't matter
 *
 * @param radio a pre-allocated radio state structure
 * @param dir The channel activation direction
 * @param active either true for activation or false for deactivation
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_activate_channel(cariboulite_radio_state_st* radio,
                                            cariboulite_channel_dir_en dir,
                                			bool active);

/**
 * @brief Set up a CW output upon activation
 *
 * When we set the CW output, whenever the channel will be activated in Tx
 * direction, the modem shall output a CW signal instead of the regular sample
 * output. The CW frequency will be according to the set frequnecy
 *
 * @param radio a pre-allocated radio state structure
 * @param lo_out setting the LO frequency output directly from the mixer (if 6G)
 * @param cw_out activate (true) or deactivate (false) CW - the source is the modem
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_set_cw_outputs(cariboulite_radio_state_st* radio, 
                               			bool lo_out, bool cw_out);

/**
 * @brief Get the current CW output state
 *
 * Checks whether the CW signal output is on
 *
 * @param radio a pre-allocated radio state structure
 * @param lo_out the LO frequency existence from the mixer
 * @param cw_out activate (true) or deactivate (false), pointer, pre-allocated, 
 *               nullable if not needed
 * @return 0 = success, -1 = failure
 */
int cariboulite_radio_get_cw_outputs(cariboulite_radio_state_st* radio, 
                               			bool *lo_out, bool *cw_out);
/**
 * @brief Get the SMI native buffer length (samples)
 *
 * This function gets the internal MTU size (nominal transfer size)
 * in samples.
 *
 * @param radio a pre-allocated radio state structure
 * @return the native mtu size
 */
size_t cariboulite_radio_get_native_mtu_size_samples(cariboulite_radio_state_st* radio);

/**
 * @brief Read samples
 *
 * Read the SMI driver and push the samples into the specified buffer of samples.
 * The number of samples to be read is "length". If needed, a pre-allocated metadata buffer
 * can be transfered to the function. It will hold the sample-matching metadata such as sync
 * bits.
 *
 * @param radio a pre-allocated radio state structure
 * @param buffer a pre-allocated buffer of native samples (complex i/q int16)
 * @param metadata a pre-allocated metadata buffer
 * @param length the number of I/Q samples to read
 * @return the number of samples read
 */
int cariboulite_radio_read_samples(cariboulite_radio_state_st* radio,
                            caribou_smi_sample_complex_int16* buffer,
                            caribou_smi_sample_meta* metadata,
                            size_t length);
                            
/**
 * @brief Write samples
 *
 * Write to the SMI driver a certain number of samples.
 *
 * @param radio a pre-allocated radio state structure
 * @param buffer a pre-allocated buffer of native samples (complex i/q int16)
 * @param length the number of I/Q samples to write
 * @return the number of samples written
 */
int cariboulite_radio_write_samples(cariboulite_radio_state_st* radio,
                            caribou_smi_sample_complex_int16* buffer,
                            size_t length);  

/**
 * @brief Get Native Chunk (MTU)
 *
 * Gets the SMI IO native chunk size (MTU) in units of samples (4 bytes each)
 *
 * @param radio a pre-allocated radio state structure
 * @return the number of samples in an native sized chunk
 */
size_t cariboulite_radio_get_native_mtu_size_samples(cariboulite_radio_state_st* radio);

#ifdef __cplusplus
}
#endif

#endif // __CARIBOULABS_RADIO_H__