/**
 *  @file LoRaPHY.h
 *
 *  @brief An abstract class providing radio object to children and
 *         provide base for implementing LoRa PHY layer
 *
 *  \code
 *   ______                              _
 *  / _____)             _              | |
 * ( (____  _____ ____ _| |_ _____  ____| |__
 *  \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 *  _____) ) ____| | | || |_| ____( (___| | | |
 * (______/|_____)_|_|_| \__)_____)\____)_| |_|
 *   (C)2013 Semtech
 *  ___ _____ _   ___ _  _____ ___  ___  ___ ___
 * / __|_   _/_\ / __| |/ / __/ _ \| _ \/ __| __|
 * \__ \ | |/ _ \ (__| ' <| _| (_) |   / (__| _|
 * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___|
 * embedded.connectivity.solutions===============
 *
 * \endcode
 *
 * Description: LoRa PHY layer
 *
 * License: Revised BSD License, see LICENSE.TXT file include in the project
 *
 * Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jaeckle ( STACKFORCE )
 *
 * Copyright (c) 2017, Arm Limited and affiliates.
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#ifndef MBED_OS_LORAPHY_BASE_
#define MBED_OS_LORAPHY_BASE_

#include "platform/NonCopyable.h"

#include "system/LoRaWANTimer.h"
#include "LoRaRadio.h"
#include "lora_phy_ds.h"

/** LoRaPHY Class
 * Parent class for LoRa regional PHY implementations
 */
class LoRaPHY : private mbed::NonCopyable<LoRaPHY> {

public:
    virtual ~LoRaPHY();

    /** Initialize LoRaPHY
     *
     *  LoRaMac calls this to initialize LoRaPHY.
     *
     * @param lora_time a pointer to LoRaWANTimeHandler object
     */
    void initialize(LoRaWANTimeHandler *lora_time);

    /** Stores a reference to Radio object.
     *
     * Application is responsible for constructing a 'LoRaRadio' object
     * which is passed down to the PHY layer.
     *
     * @param radio    a reference to radio driver object
     */
    void set_radio_instance(LoRaRadio &radio);

    /** Puts radio in sleep mode.
     *
     * Requests the radio driver to enter sleep mode.
     */
    void put_radio_to_sleep(void);

    /** Puts radio in standby mode.
     *
     * Requests the radio driver to enter standby mode.
     */
    void put_radio_to_standby(void);

    /** Puts radio in receive mode.
     *
     * Requests the radio driver to enter receive mode.
     */
    void handle_receive(void);

    /** Delegates MAC layer request to transmit packet.
     *
     * @param buf    a pointer to the data which needs to be transmitted
     *
     * @param size   size of the data in bytes
     */
    void handle_send(uint8_t *buf, uint8_t size);

    /** Enables/Disables public network mode.
     *
     * Public and private LoRaWAN network constitute different preambles and
     * Net IDs. This API isused to tell the radio which network mode is in use.
     *
     * @param set    true or false
     */
    void setup_public_network_mode(bool set);

    /** Provides a random number from radio.
     *
     * Returns a 32-bit random unsigned integer value based upon RSSI
     * measurements.
     *
     * @return    a 32-bit long random number
     *
     */
    uint32_t get_radio_rng();

    /**
     * @brief calculate_backoff Calculates and applies duty cycle back-off time.
     *                          Explicitly updates the band time-off.
     *
     * @param joined                Set to true, if the node has already joined a network, otherwise false.
     * @param last_tx_was_join_req  Set to true, if the last uplink was a join request.
     * @param dc_enabled            Set to true, if the duty cycle is enabled, otherwise false.
     * @param channel               The current channel index.
     * @param elapsed_time          Elapsed time since the start of the node.
     * @param tx_toa                Time-on-air of the last transmission.
     */
    void calculate_backoff(bool joined, bool last_tx_was_join_req, bool dc_enabled, uint8_t channel,
                           lorawan_time_t elapsed_time, lorawan_time_t tx_toa);

    /**
      * Tests if a channel is on or off in the channel mask
      */
    bool mask_bit_test(const uint16_t *mask, unsigned bit);

    /**
      * Tests if a channel is on or off in the channel mask
      */
    void mask_bit_set(uint16_t *mask, unsigned bit);

    /**
      * Tests if a channel is on or off in the channel mask
      */
    void mask_bit_clear(uint16_t *mask, unsigned bit);

    /** Entertain a new channel request MAC command.
     *
     * MAC command subsystem processes the new channel request coming form
     * the network server and then MAC layer asks the PHY layer to entertain
     * the request.
     *
     * @param channel_id The channel ID.
     * @param new_channel A pointer to the new channel's parameters.
     *
     * @return bit mask, according to the LoRaWAN spec 1.0.2.
     */
    virtual uint8_t request_new_channel(int8_t channel_id, channel_params_t *new_channel);

    /** Process PHY layer state after a successful transmission.
     * @brief set_last_tx_done Updates times of the last transmission for the particular channel and
     *                         band upon which last transmission took place.
     * @param channel The channel in use.
     * @param joined Boolean telling if node has joined the network.
     * @param last_tx_done_time The last TX done time.
     */
    virtual void set_last_tx_done(uint8_t channel, bool joined, lorawan_time_t last_tx_done_time);

    /** Enables default channels only.
     *
     * Falls back to a channel mask where only default channels are enabled, all
     * other channels are disabled.
     */
    virtual void restore_default_channels();

    /** Processes the incoming CF-list.
     *
     * Handles the payload containing CF-list and enables channels defined
     * therein.
     *
     * @param payload Payload to process.
     * @param size Size of the payload.
     *
     */
    virtual void apply_cf_list(const uint8_t *payload, uint8_t size);

    /** Calculates the next datarate to set, when ADR is on or off.
     *
     * @param restore_channel_mask    A boolean set restore channel mask in case
     *                                of failure.
     *
     * @param dr_out                  The calculated datarate for the next TX.
     *
     * @param tx_power_out            The TX power for the next TX.
     *
     * @param adr_ack_counter         The calculated ADR acknowledgement counter.
     *
     * @return True, if an ADR request should be performed.
     */
    bool get_next_ADR(bool restore_channel_mask, int8_t &dr_out,
                      int8_t &tx_power_out, uint32_t &adr_ack_counter);

    /** Configure radio reception.
     *
     * @param [in] config    A pointer to the RX configuration.
     *
     * @return True, if the configuration was applied successfully.
     */
    virtual bool rx_config(rx_config_params_t *config);

    /** Computing Receive Windows
     *
     * The algorithm tries to calculate the length of receive windows (i.e.,
     * the minimum time it should remain to acquire a lock on the Preamble
     * for synchronization) and the error offset which compensates for the system
     * timing errors. Basic idea behind the algorithm is to optimize for the
     * reception of last 'min_rx_symbols' symbols out of transmitted Premable
     * symbols. The algorithm compensates for the clock drifts, tick granularity
     * and system wake up time (from sleep state) by opening the window early for
     * the lower SFs. For higher SFs, the symbol time is large enough that we can
     * afford to open late (hence the positive offset).
     * The table below shows the calculated values for SF7 to SF12 with 125 kHz
     * bandwidth.
     *
     * +----+-----+----------+---------+-------------------------+----------------------+-------------------------+
     * | SF | BW (kHz) | rx_error (ms) | wake_up (ms) | min_rx_symbols | window_timeout(symb) | window_offset(ms) |
     * +----+-----+----------+---------+-------------------------+----------------------+-------------------------+
     * |  7 |      125 |             5 |            5 |              5 |                   18 |                -7 |
     * |  8 |      125 |             5 |            5 |              5 |                   10 |                -4 |
     * |  9 |      125 |             5 |            5 |              5 |                    6 |                 2 |
     * | 10 |      125 |             5 |            5 |              5 |                    6 |                14 |
     * | 11 |      125 |             5 |            5 |              5 |                    6 |                39 |
     * | 12 |      125 |             5 |            5 |              5 |                    6 |                88 |
     * +----+-----+----------+---------+-------------------------+----------------------+-------------------------+
     *
     * For example for SF7, the receive window will open at downlink start time
     * plus the offset calculated and will remain open for the length window_timeout.
     *
     *             Symbol time = 1.024 ms
     *             Downlink start: T = Tx + 1s (+/- 20 us)
     *                               |
     *                               |
     *                               |
     *                               |
     *                               |
     *                               +---+---+---+---+---+---+---+---+
     *                               |       8 Preamble Symbols      |
     *                               +---+---+---+---+---+---+---+---+
     *   | RX Window start time = T +/- Offset
     *   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     *   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
     *   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     *
     * Similarly for SF12:
     *
     *             Symbol time = 32.768 ms
     *             Downlink start: T = Tx + 1s (+/- 20 us)
     *                |
     *                |
     *                |
     *                |
     *                |
     *                +---+---+---+---+---+---+---+---+
     *                |       8 Preamble Symbols      |
     *                +---+---+---+---+---+---+---+---+
     *                           | RX Window start time = T +/- Offset
     *                           +---+---+---+---+---+---+
     *                           |   |   |   |   |   |   |
     *                           +---+---+---+---+---+---+
     */
    /*!
     * Computes the RX window timeout and offset.
     *
     * @param [in] datarate         The RX window datarate index to be used.
     *
     * @param [in] min_rx_symbols   The minimum number of symbols required to
     *                              detect an RX frame.
     *
     * @param [in] rx_error         The maximum timing error of the receiver
     *                              in milliseconds. The receiver will turn on
     *                              in a [-rxError : +rxError] ms interval around
     *                              RxOffset.
     *
     * @param [out] rx_conf_params  Pointer to the structure that needs to be
     *                              filled with receive window parameters.
     *
     */
    virtual void compute_rx_win_params(int8_t datarate, uint8_t min_rx_symbols,
                                       uint32_t rx_error,
                                       rx_config_params_t *rx_conf_params);

    /** Configure radio transmission.
     *
     * @param [in]  tx_config    Structure containing tx parameters.
     *
     * @param [out] tx_power     The TX power which will be set.
     *
     * @param [out] tx_toa       The time-on-air of the frame.
     *
     * @return True, if the configuration was applied successfully.
     */
    virtual bool tx_config(tx_config_params_t *tx_config, int8_t *tx_power,
                           lorawan_time_t *tx_toa);

    /** Processes a Link ADR Request.
     *
     * @param [in]  params          A pointer ADR request parameters.
     *
     * @param [out] dr_out          The datarate applied.
     *
     * @param [out] tx_power_out    The TX power applied.
     *
     * @param [out] nb_rep_out      The number of repetitions to apply.
     *
     * @param [out] nb_bytes_parsed The number of bytes parsed.
     *
     * @return The status of the operation, according to the LoRaMAC specification.
     */
    virtual uint8_t link_ADR_request(adr_req_params_t *params,
                                     int8_t *dr_out, int8_t *tx_power_out,
                                     uint8_t *nb_rep_out,
                                     uint8_t *nb_bytes_parsed);

    /** Accept or rejects RxParamSetupReq MAC command
     *
     * The function processes a RX parameter setup request in response to
     * server MAC command for RX setup.
     *
     * @param [in] params    A pointer to rx parameter setup request.
     *
     * @return The status of the operation, according to the LoRaWAN specification.
     */
    virtual uint8_t accept_rx_param_setup_req(rx_param_setup_req_t *params);

    /**
     * @brief accept_tx_param_setup_req Makes decision whether to accept or reject TxParamSetupReq MAC command.
     *
     * @param ul_dwell_time The uplink dwell time.
     * @param dl_dwell_time The downlink dwell time.
     *
     * @return True to let the MAC know that the request is
     *         accepted and MAC can apply TX parameters received
     *         form Network Server. Otherwise false is returned.
     */
    virtual bool accept_tx_param_setup_req(uint8_t ul_dwell_time, uint8_t dl_dwell_time);

    /** Processes a DlChannelReq MAC command.
     *
     * @param channel_id The channel ID to add the frequency.
     * @param rx1_frequency The alternative frequency for the Rx1 window.
     *
     * @return The status of the operation, according to the LoRaWAN specification.
     */
    virtual uint8_t dl_channel_request(uint8_t channel_id, uint32_t rx1_frequency);

    /** Alternates the datarate of the channel for the join request.
     *
     * @param nb_trials    Number of trials to be made on one given data rate.
     *
     * @return             The datarate to apply .
     */
    virtual int8_t get_alternate_DR(uint8_t nb_trials);

    /** Searches and sets the next available channel.
     *
     * If there are multiple channels found available, one of them is selected
     * randomly.
     *
     * @param [in]  nextChanParams Parameters for the next channel.
     *
     * @param [out] channel The next channel to use for TX.
     *
     * @param [out] time The time to wait for the next transmission according to the duty cycle.
     *
     * @param [out] aggregatedTimeOff Updates the aggregated time off.
     *
     * @return Function status [1: OK, 0: Unable to find a channel on the current datarate].
     */
    virtual lorawan_status_t set_next_channel(channel_selection_params_t *nextChanParams,
                                              uint8_t *channel, lorawan_time_t *time,
                                              lorawan_time_t *aggregatedTimeOff);

    /** Adds a channel to the channel list.
     *
     * Verifies the channel parameters and if everything is found legitimate,
     * adds that particular channel to the channel list and updates the channel
     * mask.
     *
     * @param [in] new_channel A pointer to the parameters for the new channel.
     * @param [in] id          Channel ID
     *
     * @return LORAWAN_STATUS_OK if everything goes fine, negative error code
     *         otherwise.
     */
    virtual lorawan_status_t add_channel(const channel_params_t *new_channel, uint8_t id);

    /** Removes a channel from the channel list.
     *
     * @param [in] channel_id Index of the channel to be removed
     *
     * @return True, if the channel was removed successfully.
     */
    virtual bool remove_channel(uint8_t channel_id);

    /** Puts the radio into continuous wave mode.
     *
     * @param [in] continuous_wave   A pointer to the function parameters.
     *
     * @param [in] frequency         Frequency to transmit at
     */
    virtual void set_tx_cont_mode(cw_mode_params_t *continuous_wave,
                                  uint32_t frequency = 0);

    /** Computes new data rate according to the given offset
     *
     * @param [in] dr The current datarate.
     *
     * @param [in] dr_offset The offset to be applied.
     *
     * @return     The computed datarate.
     */
    virtual uint8_t apply_DR_offset(int8_t dr, int8_t dr_offset);

    /**
     * @brief reset_to_default_values resets some parameters to default values
     * @param params Pointer to MAC protocol parameters which will be reset
     * @param init If true, most of the values will be modified
     */
    void reset_to_default_values(loramac_protocol_params *params, bool init = false);

public:
    /**
     * @brief get_next_lower_tx_datarate Gets the next lower datarate
     * @param datarate Current TX datarate
     * @return Lower datarate than given one or minimum if lower cannot be found anymore
     */
    int8_t get_next_lower_tx_datarate(int8_t datarate);

    /**
     * @brief get_minimum_rx_datarate Gets the minimum RX datarate supported by a device
     * @return Minimum RX datarate
     */
    uint8_t get_minimum_rx_datarate();

    /**
     * @brief get_minimum_tx_datarate Gets the minimum TX datarate supported by a device
     * @return Minimum TX datarate
     */
    uint8_t get_minimum_tx_datarate();

    /**
     * @brief get_default_tx_datarate Gets the default TX datarate
     * @return default TX datarate
     */
    uint8_t get_default_tx_datarate();

    /**
     * @brief get_default_max_tx_datarate Gets the maximum achievable data rate for
     *        LoRa modulation. This will always be the highest data rate achievable with
     *        LoRa as defined in the regional specifications.
     * @return Maximum achievable data rate with LoRa modulation.
     */
    uint8_t get_default_max_tx_datarate();

    /**
     * @brief get_default_tx_power Gets the default TX power
     * @return Default TX power
     */
    uint8_t get_default_tx_power();

    /**
     * @brief get_max_payload Gets maximum amount in bytes which device can send
     * @param datarate A datarate to use
     * @param use_repeater If true repeater table is used, otherwise payloads table is used
     * @return Maximum number of bytes for payload
     */
    uint8_t get_max_payload(uint8_t datarate, bool use_repeater = false);

    /**
     * @brief get_maximum_frame_counter_gap Gets maximum frame counter gap
     * @return Maximum frame counter gap
     */
    uint16_t get_maximum_frame_counter_gap();

    /**
     * @brief get_ack_timeout Gets timeout value for ACK to be received
     * @return ACK timeout
     */
    uint32_t get_ack_timeout();

    /**
     * @brief get_default_rx2_frequency Gets default RX2 frequency
     * @return RX2 frequency
     */
    uint32_t get_default_rx2_frequency();

    /**
     * @brief get_default_rx2_datarate Gets default RX2 datarate
     * @return RX2 datarate
     */
    uint8_t get_default_rx2_datarate();

    /**
     * @brief get_channel_mask Gets the channel mask
     * @param get_default If true the default mask is returned, otherwise the current mask is returned
     * @return A channel mask
     */
    uint16_t *get_channel_mask(bool get_default = false);

    /**
     * @brief get_max_nb_channels Gets maximum number of channels supported
     * @return Number of channels
     */
    uint8_t get_max_nb_channels();

    /**
     * @brief get_phy_channels Gets PHY channels
     * @return PHY channels
     */
    channel_params_t *get_phy_channels();

    /**
     * @brief is_custom_channel_plan_supported Checks if custom channel plan is supported
     * @return True if custom channel plan is supported, false otherwise
     */
    bool is_custom_channel_plan_supported();

    /**
     * @brief get_rx_time_on_air(...) calculates the time the received spent on air
     * @return time spent on air in milliseconds
     */
    uint32_t get_rx_time_on_air(uint8_t modem, uint16_t pkt_len);

public: //Verifiers

    /**
     * @brief verify_rx_datarate Verifies that given RX datarate is valid
     * @param datarate Datarate to check
     * @return true if given datarate is valid, false otherwise
     */
    bool verify_rx_datarate(uint8_t datarate);

    /**
     * @brief verify_tx_datarate Verifies that given TX datarate is valid
     * @param datarate Datarate to check
     * @param use_default If true validation is done against default value
     * @return true if given datarate is valid, false otherwise
     */
    bool verify_tx_datarate(uint8_t datarate, bool use_default = false);

    /**
     * @brief verify_tx_power Verifies that given TX power is valid
     * @param tx_power Power to check
     * @return True if valid, false otherwise
     */
    bool verify_tx_power(uint8_t tx_power);

    /**
     * @brief verify_duty_cycle Verifies that given cycle is valid
     * @param cycle Cycle to check
     * @return True if valid, false otherwise
     */
    bool verify_duty_cycle(bool cycle);

    /**
     * @brief verify_nb_join_trials Verifies that given number of trials is valid
     * @param nb_join_trials Number to check
     * @return True if valid, false otherwise
     */
    bool verify_nb_join_trials(uint8_t nb_join_trials);

protected:
    LoRaPHY();

    /**
     * Looks up corresponding band for a frequency. Returns -1 if not in any band.
     */
    int lookup_band_for_frequency(uint32_t freq) const;

    /**
     * Verifies, if a frequency is within a given band.
     */
    virtual bool verify_frequency_for_band(uint32_t freq, uint8_t band) const;

    /**
     * Verifies, if a value is in a given range.
     */
    bool val_in_range(int8_t value, int8_t min, int8_t max);

    /**
     * Verifies, if a datarate is available on an active channel.
     */
    bool verify_channel_DR(uint16_t *channelsMask, int8_t dr);

    /**
     * Disables a channel in a given channels mask.
     */
    bool disable_channel(uint16_t *channel_mask, uint8_t id, uint8_t max_channels);

    /**
     * Counts number of bits on in a given mask
     */
    uint8_t count_bits(uint16_t mask, uint8_t nb_bits);

    /**
     * Counts the number of active channels in a given channels mask.
     */
    uint8_t num_active_channels(uint16_t *channel_mask, uint8_t start_idx,
                                uint8_t stop_idx);

    /**
     * Copy channel masks.
     */
    void copy_channel_mask(uint16_t *dest_mask, uint16_t *src_mask, uint8_t len);

    /**
     * Updates the time-offs of the bands.
     */
    lorawan_time_t update_band_timeoff(bool joined, bool dutyCycle, band_t *bands,
                                       uint8_t nb_bands);

    /**
     * Parses the parameter of an LinkAdrRequest.
     */
    uint8_t parse_link_ADR_req(const uint8_t *payload, uint8_t payload_size,
                               link_adr_params_t *adr_params);

    /**
     * Verifies and updates the datarate, the TX power and the number of repetitions
     * of a LinkAdrRequest.
     */
    uint8_t verify_link_ADR_req(verify_adr_params_t *verify_params, int8_t *dr,
                                int8_t *tx_pow, uint8_t *nb_rep);

    /**
     * Computes the RX window timeout and the RX window offset.
     */
    void get_rx_window_params(float t_symbol, uint8_t min_rx_symbols,
                              float rx_error, float wakeup_time,
                              uint32_t *window_length, uint32_t *window_length_ms,
                              int32_t *window_offset,
                              uint8_t phy_dr);

    /**
     * Computes the txPower, based on the max EIRP and the antenna gain.
     */
    int8_t compute_tx_power(int8_t txPowerIndex, float maxEirp, float antennaGain);

    /**
     * Provides a random number in the range provided.
     */
    int32_t get_random(int32_t min, int32_t max);

    /**
     * Get next lower data rate
     */
    int8_t get_next_lower_dr(int8_t dr, int8_t min_dr);

    /**
     * Get channel bandwidth depending upon data rate table index
     */
    uint8_t get_bandwidth(uint8_t dr_index);

    uint8_t enabled_channel_count(uint8_t datarate,
                                  const uint16_t *mask, uint8_t *enabledChannels,
                                  uint8_t *delayTx);

    bool is_datarate_supported(const int8_t datarate) const;

private:

    /**
     * Computes the symbol time for LoRa modulation.
     */
    float compute_symb_timeout_lora(uint8_t phy_dr, uint32_t bandwidth);

    /**
     * Computes the symbol time for FSK modulation.
     */
    float compute_symb_timeout_fsk(uint8_t phy_dr);

protected:
    LoRaRadio *_radio;
    LoRaWANTimeHandler *_lora_time;
    loraphy_params_t phy_params;
};

#endif /* MBED_OS_LORAPHY_BASE_ */
