// Copyright (c) Sandeep Mistry. All rights reserved.
// Licensed under the MIT license.

// Modifications and additions copyright 2024 by Mark Qvist & Jacob Eva
// Obviously still under the MIT license.

#ifndef RADIO_H
#define RADIO_H

#include <Arduino.h>
#include <SPI.h>
#include "Interfaces.h"
#include "Boards.h"
#include "src/misc/FIFOBuffer.h"

#define MAX_PKT_LENGTH                255

// TX
#define PA_OUTPUT_RFO_PIN 0
#define PA_OUTPUT_PA_BOOST_PIN 1

// Default LoRa settings
#define PHY_HEADER_LORA_SYMBOLS    20
#define PHY_CRC_LORA_BITS          16
#define LORA_PREAMBLE_SYMBOLS_MIN  18
#define LORA_PREAMBLE_TARGET_MS    24
#define LORA_PREAMBLE_FAST_DELTA   18
#define LORA_FAST_THRESHOLD_BPS    30E3
#define LORA_LIMIT_THRESHOLD_BPS   60E3

// DCD
#define STATUS_INTERVAL_MS 3
#define DCD_SAMPLES 2500
#define UTIL_UPDATE_INTERVAL_MS 1000
#define UTIL_UPDATE_INTERVAL (UTIL_UPDATE_INTERVAL_MS/STATUS_INTERVAL_MS)
#define AIRTIME_LONGTERM 3600
#define AIRTIME_LONGTERM_MS (AIRTIME_LONGTERM*1000)
#define AIRTIME_BINLEN_MS (STATUS_INTERVAL_MS*DCD_SAMPLES)
#define AIRTIME_BINS ((AIRTIME_LONGTERM*1000)/AIRTIME_BINLEN_MS)
#define current_airtime_bin(void) (millis()%AIRTIME_LONGTERM_MS)/AIRTIME_BINLEN_MS


// CSMA Parameters
#define CSMA_SIFS_MS               0
#define CSMA_POST_TX_YIELD_SLOTS   3
#define CSMA_SLOT_MAX_MS           100
#define CSMA_SLOT_MIN_MS           24
#define CSMA_SLOT_MIN_FAST_DELTA   18
#define CSMA_SLOT_SYMBOLS          12
#define CSMA_CW_BANDS              4
#define CSMA_CW_MIN                0
#define CSMA_CW_PER_BAND_WINDOWS   15
#define CSMA_BAND_1_MAX_AIRTIME    7
#define CSMA_BAND_N_MIN_AIRTIME    85
#define CSMA_INFR_THRESHOLD_DB     12

#define LED_ID_TRIG 16

#define NOISE_FLOOR_SAMPLES 64

#define RSSI_OFFSET 157

#define PHY_HEADER_LORA_SYMBOLS 8

#define MODEM_TIMEOUT_MULT 1.5

// Status flags
const uint8_t SIG_DETECT = 0x01;
const uint8_t SIG_SYNCED = 0x02;
const uint8_t RX_ONGOING = 0x04;

// forward declare Utilities.h LED functions
void led_rx_on();
void led_rx_off();
void led_id_on();
void led_id_off();
void led_indicate_airtime_lock();

void kiss_indicate_channel_stats(uint8_t index);
void kiss_indicate_csma_stats(uint8_t index);

#if PLATFORM == PLATFORM_ESP32
// get update_lock for ESP32
extern portMUX_TYPE update_lock;
#endif

class RadioInterface : public Stream {
public:
    // todo: in the future define _spiModem and _spiSettings from here for inheritence by child classes
    RadioInterface(uint8_t index) : _index(index), _sf(0x07), _radio_locked(false),
    _radio_online(false), _st_airtime_limit(0.0), _lt_airtime_limit(0.0),
    _airtime_lock(false), _airtime(0.0), _longterm_airtime(0.0), _last_packet_cost(0.0),
    _local_channel_util(0.0), _total_channel_util(0.0),
    _longterm_channel_util(0.0), _last_status_update(0),
     _stat_signal_detected(false), _stat_signal_synced(false),_stat_rx_ongoing(false), _last_dcd(0), 
     _dcd_count(0), _dcd(false), _dcd_led(false),
    _dcd_waiting(false), _dcd_sample(0),
    _csma_slot_ms(CSMA_SLOT_MIN_MS),
    _preambleLength(LORA_PREAMBLE_SYMBOLS_MIN), _lora_symbol_time_ms(0.0),
    _lora_preamble_time_ms(0), _lora_header_time_ms(0), _lora_symbol_rate(0.0), _lora_us_per_byte(0.0), _bitrate(0),
     _packet{0}, _onReceive(NULL), _txp(0), _ldro(false), _limit_rate(false), _interference_detected(false), _avoid_interference(true), _difs_ms(CSMA_SIFS_MS + 2 * _csma_slot_ms), _difs_wait_start(0), _cw_wait_start(0), _cw_wait_target(0), _cw_wait_passed(0), _csma_cw(-1), _cw_band(1), _cw_min(0), _cw_max(CSMA_CW_PER_BAND_WINDOWS), _noise_floor_sampled(false), _noise_floor_sample(0), _noise_floor_buffer({0}), _noise_floor(-292), _led_id_filter(0), _preamble_detected_at(0) {};

    virtual void reset() = 0;

    virtual int begin() = 0;
    virtual void end() = 0;

    virtual int beginPacket(int implicitHeader = false) = 0;
    virtual int endPacket() = 0;

    virtual int packetRssi(uint8_t pkt_snr_raw = 0xFF) = 0;
    virtual int currentRssi() = 0;
    virtual uint8_t packetRssiRaw() = 0;
    virtual uint8_t currentRssiRaw() = 0;
    virtual uint8_t packetSnrRaw() = 0;
    virtual float packetSnr() = 0;
    virtual long packetFrequencyError() = 0;

    // from Print
    virtual size_t write(uint8_t byte) = 0;
    virtual size_t write(const uint8_t *buffer, size_t size) = 0;

    // from Stream
    virtual int available() = 0;
    virtual int read() = 0;
    virtual int peek() = 0;
    virtual void flush() = 0;

    virtual void onReceive(void(*callback)(uint8_t, int)) = 0;

    virtual void receive(int size = 0) = 0;
    virtual void standby() = 0;
    virtual void sleep() = 0;

    virtual bool preInit() = 0;
    virtual int8_t getTxPower() = 0;
    virtual void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN) = 0;
    virtual uint32_t getFrequency() = 0;
    virtual void setFrequency(uint32_t frequency) = 0;
    virtual void setSpreadingFactor(int sf) = 0;
    virtual uint8_t getSpreadingFactor() = 0;
    virtual uint32_t getSignalBandwidth() = 0;
    virtual void setSignalBandwidth(uint32_t sbw) = 0;
    virtual void setCodingRate4(int denominator) = 0;
    virtual uint8_t getCodingRate4() = 0;
    virtual void setPreambleLength(long length) = 0;
    virtual bool dcd() = 0;
    virtual void enableCrc() = 0;
    virtual void disableCrc() = 0;
    virtual void enableTCXO() = 0;
    virtual void disableTCXO() = 0;

    virtual uint8_t random() = 0;

    virtual void setSPIFrequency(uint32_t frequency) = 0;

    void updateBitrate() {
		if (!_radio_online) { _bitrate = 0; }
		else {
			_lora_symbol_rate = (float)getSignalBandwidth()/(float)(pow(2, _sf));
			_lora_symbol_time_ms = (1.0/_lora_symbol_rate)*1000.0;
			_bitrate = (uint32_t)(_sf * ( (4.0/(float)getCodingRate4()) / ((float)(pow(2, _sf))/((float)getSignalBandwidth()/1000.0)) ) * 1000.0);
			_lora_us_per_byte = 1000000.0/((float)_bitrate/8.0);
			
			bool fast_rate   = _bitrate > LORA_FAST_THRESHOLD_BPS;
			_limit_rate  = _bitrate > LORA_LIMIT_THRESHOLD_BPS;

			int csma_slot_min_ms = CSMA_SLOT_MIN_MS;
			float lora_preamble_target_ms = LORA_PREAMBLE_TARGET_MS;
			if (fast_rate) { csma_slot_min_ms        -= CSMA_SLOT_MIN_FAST_DELTA;
											 lora_preamble_target_ms -= LORA_PREAMBLE_FAST_DELTA; }
			
			_csma_slot_ms = _lora_symbol_time_ms*CSMA_SLOT_SYMBOLS;
			if (_csma_slot_ms > CSMA_SLOT_MAX_MS) { _csma_slot_ms = CSMA_SLOT_MAX_MS; }
			if (_csma_slot_ms < CSMA_SLOT_MIN_MS) { _csma_slot_ms = csma_slot_min_ms; }
			_difs_ms = CSMA_SIFS_MS + 2*_csma_slot_ms;
			
			float target_preamble_symbols = lora_preamble_target_ms/_lora_symbol_time_ms;
			if (target_preamble_symbols < LORA_PREAMBLE_SYMBOLS_MIN) { target_preamble_symbols = LORA_PREAMBLE_SYMBOLS_MIN; }
			else { target_preamble_symbols = (ceil)(target_preamble_symbols); }
			
            setPreambleLength(target_preamble_symbols);
			_lora_preamble_time_ms = (ceil)(_preambleLength * _lora_symbol_time_ms);
			_lora_header_time_ms   = (ceil)(PHY_HEADER_LORA_SYMBOLS * _lora_symbol_time_ms);

		}
    }
    virtual void handleDio0Rise() = 0;
    virtual bool getPacketValidity() = 0;
    uint32_t getBitrate() { return _bitrate; };
    uint8_t getIndex() { return _index; };
    void setRadioLock(bool lock) { _radio_locked = lock; };
    bool getRadioLock() { return _radio_locked; };
    void setRadioOnline(bool online) { _radio_online = online; };
    bool getRadioOnline() { return _radio_online; };
    void setSTALock(float at) { _st_airtime_limit = at; };
    float getSTALock() { return _st_airtime_limit; };
    void setLTALock(float at) { _lt_airtime_limit = at; };
    float getLTALock() { return _lt_airtime_limit; };
    bool calculateALock() {
      _airtime_lock = false;
      if (_st_airtime_limit != 0.0 && _airtime >= _st_airtime_limit) {
          _airtime_lock = true;
      }
      if (_lt_airtime_limit != 0.0 && _longterm_airtime >= _lt_airtime_limit) {
          _airtime_lock = true;
      }
      return _airtime_lock;
    };
    void updateAirtime() {
        uint16_t cb = current_airtime_bin();
        uint16_t pb = cb-1; if (cb-1 < 0) { pb = AIRTIME_BINS-1; }
        uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; }
        _airtime_bins[nb] = 0;
        _airtime = (float)(_airtime_bins[cb]+_airtime_bins[pb])/(2.0*AIRTIME_BINLEN_MS);
    
        uint32_t longterm_airtime_sum = 0;
        for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) {
          longterm_airtime_sum += _airtime_bins[bin];
        }
        _longterm_airtime = (float)longterm_airtime_sum/(float)AIRTIME_LONGTERM_MS;
    
        float longterm_channel_util_sum = 0.0;
        for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) {
          longterm_channel_util_sum += _longterm_bins[bin];
        }
        _longterm_channel_util = (float)longterm_channel_util_sum/(float)AIRTIME_BINS;
    
        updateCSMAParameters();
        kiss_indicate_channel_stats(_index);
    };
    float getAirtime(uint16_t written) { 
        float lora_symbols = 0;
        float packet_cost_ms = 0.0;

        if (interfaces[_index] == SX1276 || interfaces[_index] == SX1278) { 
            lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*_sf + 8 + PHY_HEADER_LORA_SYMBOLS);
            lora_symbols /=                          4*(_sf-2*_ldro);
            lora_symbols *= getCodingRate4();
            lora_symbols += _preambleLength + 0.25 + 8;
            packet_cost_ms += lora_symbols * _lora_symbol_time_ms;
        }
        else if (interfaces[_index] == SX1262 || interfaces[_index] == SX1280) {
            if (_sf < 7) {
                lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*_sf + PHY_HEADER_LORA_SYMBOLS);
                lora_symbols /=                              4*_sf;
                lora_symbols *= getCodingRate4();
                lora_symbols += _preambleLength + 2.25 + 8;
                packet_cost_ms += lora_symbols * _lora_symbol_time_ms;

            } else {
                lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*_sf + 8 + PHY_HEADER_LORA_SYMBOLS);
                lora_symbols /=                         4*(_sf-2*_ldro);
                lora_symbols *= getCodingRate4();
                lora_symbols += _preambleLength + 0.25 + 8;
                packet_cost_ms += lora_symbols * _lora_symbol_time_ms;
            }
        }
        _last_packet_cost = packet_cost_ms;
        return packet_cost_ms;
    }
    void addAirtime() {
        uint16_t cb = current_airtime_bin();
        uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; }
        _airtime_bins[cb] += _last_packet_cost;
        _airtime_bins[nb] = 0;
    };
    void updateModemStatus() {
      #if MCU_VARIANT == MCU_ESP32
        portENTER_CRITICAL(&update_lock);
      #elif MCU_VARIANT == MCU_NRF52
        portENTER_CRITICAL();
      #endif

      bool carrier_detected = dcd();
      int current_rssi = currentRssi();
      _last_status_update = millis();

      #if MCU_VARIANT == MCU_ESP32
        portEXIT_CRITICAL(&update_lock);
      #elif MCU_VARIANT == MCU_NRF52
        portEXIT_CRITICAL();
      #endif

      _interference_detected = !carrier_detected && (current_rssi > (_noise_floor+CSMA_INFR_THRESHOLD_DB));
      if (_interference_detected) { if (_led_id_filter < LED_ID_TRIG) { _led_id_filter += 1; } }
      else                       { if (_led_id_filter > 0) {_led_id_filter -= 1; } }

      if (carrier_detected) { _dcd = true; } else { _dcd = false; }

      _dcd_led = _dcd;
      if (_dcd_led) { led_rx_on(); }
      else {
        if (_interference_detected) {
          if (_led_id_filter >= LED_ID_TRIG && _noise_floor_sampled) { led_id_on(); }
        } else {
          if (_airtime_lock) { led_indicate_airtime_lock(); }
          else              { led_rx_off(); led_id_off(); }
        }
      }
    }
    void updateNoiseFloor() {
        int current_rssi = currentRssi();
        if (!_dcd) {
            if (!_noise_floor_sampled || current_rssi < _noise_floor + CSMA_INFR_THRESHOLD_DB) {
                _noise_floor_buffer[_noise_floor_sample] = current_rssi;
                _noise_floor_sample = _noise_floor_sample+1;
                if (_noise_floor_sample >= NOISE_FLOOR_SAMPLES) {
                    _noise_floor_sample %= NOISE_FLOOR_SAMPLES;
                    _noise_floor_sampled = true;
                }

                if (_noise_floor_sampled) {
                    _noise_floor = 0;
                    for (int ni = 0; ni < NOISE_FLOOR_SAMPLES; ni++) { _noise_floor += _noise_floor_buffer[ni]; }
                    _noise_floor /= NOISE_FLOOR_SAMPLES;
                }
            }
        }
    }
    void checkModemStatus() {
      if (millis()-_last_status_update >= STATUS_INTERVAL_MS) {
        updateModemStatus();
        updateNoiseFloor();

        _util_samples[_dcd_sample] = _dcd;
        _dcd_sample = (_dcd_sample+1)%DCD_SAMPLES;
        if (_dcd_sample % UTIL_UPDATE_INTERVAL == 0) {
            int util_count = 0;
            for (int ui = 0; ui < DCD_SAMPLES; ui++) {
                if (_util_samples[ui]) util_count++;
            }
            _local_channel_util = (float)util_count / (float)DCD_SAMPLES;
            _total_channel_util = _local_channel_util + _airtime;
            if (_total_channel_util > 1.0) _total_channel_util = 1.0;

            int16_t cb = current_airtime_bin();
            uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; }
            if (_total_channel_util > _longterm_bins[cb]) _longterm_bins[cb] = _total_channel_util;
            _longterm_bins[nb] = 0.0;

            updateAirtime();
        }
      }
    };
    void updateCSMAParameters() {
        int airtime_pct = (int)(_airtime*100);
        int new_cw_band = _cw_band;

        if (airtime_pct <= CSMA_BAND_1_MAX_AIRTIME) { new_cw_band = 1; }
        else {
            int at = airtime_pct + CSMA_BAND_1_MAX_AIRTIME;
            new_cw_band = map(at, CSMA_BAND_1_MAX_AIRTIME, CSMA_BAND_N_MIN_AIRTIME, 2, CSMA_CW_BANDS);
        }

        if (new_cw_band > CSMA_CW_BANDS) { new_cw_band = CSMA_CW_BANDS; }
        if (new_cw_band != _cw_band) { 
            _cw_band = (uint8_t)(new_cw_band);
            _cw_min  = (_cw_band-1) * CSMA_CW_PER_BAND_WINDOWS;
            _cw_max  = (_cw_band) * CSMA_CW_PER_BAND_WINDOWS - 1;
            kiss_indicate_csma_stats(_index);
        }
    }
    void setDCD(bool dcd) { _dcd = dcd; };
    bool getDCD() { return _dcd; };
    void setDCDWaiting(bool dcd_waiting) { _dcd_waiting = dcd_waiting; };
    bool getDCDWaiting() { return _dcd_waiting; };
    float getAirtime() { return _airtime; };
    float getLongtermAirtime() { return _longterm_airtime; };
    float getTotalChannelUtil() { return _total_channel_util; };
    float getLongtermChannelUtil() { return _longterm_channel_util; };
    void setCSMASlotMS(int slot_size) { _csma_slot_ms = slot_size; };
    int getCSMASlotMS() { return _csma_slot_ms; };
    float getSymbolTime() { return _lora_symbol_time_ms; };
    float getSymbolRate() { return _lora_symbol_rate; };
    long getPreambleLength() { return _preambleLength; };
    void setAvdInterference(bool cfg) { _avoid_interference = cfg; };
    bool getAvdInterference() { return _avoid_interference; };
    bool getInterference() { return _interference_detected; };
    int getNoiseFloor() { return _noise_floor; };
    unsigned long getDifsMS() { return _difs_ms; };
    uint8_t getCWBand() { return _cw_band; };
    uint8_t getCWMin() { return _cw_min; };
    uint8_t getCWMax() { return _cw_max; };
    uint8_t getCW() { return _csma_cw; };
    void setCW(uint8_t cw) { _csma_cw = cw; };
    void setCWWaitTarget(unsigned long target) { _cw_wait_target = target; };
    unsigned long getCWWaitTarget() { return _cw_wait_target; };
    unsigned long getDifsWaitStart() { return _difs_wait_start; };
    void setDifsWaitStart(unsigned long start) { _difs_wait_start = start; };
    unsigned long getCWWaitStart() { return _cw_wait_start; };
    void setCWWaitStart(unsigned long start) { _cw_wait_start = start; };
    void addCWWaitPassed(unsigned long start) { _cw_wait_passed += start; };
    void resetCWWaitPassed() { _cw_wait_passed = 0; };
    bool getCWWaitStatus() { return _cw_wait_passed < _cw_wait_target; };
    bool getLimitRate() { return _limit_rate; };
protected:
    virtual void explicitHeaderMode() = 0;
    virtual void implicitHeaderMode() = 0;

    uint8_t _index;
    uint32_t _bitrate;
    int8_t _txp;
    uint8_t _sf;
    bool _radio_locked;
    bool _radio_online;
    float _st_airtime_limit;
    float _lt_airtime_limit;
    bool _airtime_lock;
    uint16_t _airtime_bins[AIRTIME_BINS] = {0};
    uint16_t _longterm_bins[AIRTIME_BINS] = {0};
    float _airtime;
    float _longterm_airtime;
    float _last_packet_cost;
    float _local_channel_util;
    float _total_channel_util;
    float _longterm_channel_util;
    uint32_t _last_status_update;
    bool _stat_signal_detected;
    bool _stat_signal_synced;
    bool _stat_rx_ongoing;
    uint32_t _last_dcd;
    uint16_t _dcd_count;
    bool _dcd;
    bool _dcd_led;
    bool _dcd_waiting;
	bool _util_samples[DCD_SAMPLES] = {false};
	int _dcd_sample;
    long _preambleLength;
    float _lora_symbol_time_ms;
    float _lora_symbol_rate;
    float _lora_us_per_byte;
    long _lora_preamble_time_ms;
    long _lora_header_time_ms;
    bool _ldro;
    bool _limit_rate;
    bool _interference_detected;
    bool _avoid_interference;
    int _csma_slot_ms;
    unsigned long _difs_ms;
    unsigned long _difs_wait_start;
    unsigned long _cw_wait_start;
    unsigned long _cw_wait_target;
    unsigned long _cw_wait_passed;
    int _csma_cw;
    uint8_t _cw_band;
    uint8_t _cw_min;
    uint8_t _cw_max;
    bool _noise_floor_sampled;
    int _noise_floor_sample;
    int _noise_floor_buffer[NOISE_FLOOR_SAMPLES];
    int _noise_floor;
    uint8_t _led_id_filter;
    unsigned long _preamble_detected_at;

    uint8_t _packet[255];
    void (*_onReceive)(uint8_t, int);
};

class sx126x : public RadioInterface {
public:
  sx126x(uint8_t index, SPIClass* spi, bool tcxo, bool dio2_as_rf_switch, int ss, int sclk, int mosi, int miso, int reset, int
          dio0, int busy, int rxen);

  void reset();

  int begin();
  void end();

  int beginPacket(int implicitHeader = false);
  int endPacket();

  int packetRssi(uint8_t pkt_snr_raw = 0xFF);
  int currentRssi();
  uint8_t packetRssiRaw();
  uint8_t currentRssiRaw();
  uint8_t packetSnrRaw();
  float packetSnr();
  long packetFrequencyError();

  // from Print
  size_t write(uint8_t byte);
  size_t write(const uint8_t *buffer, size_t size);

  // from Stream
  int available();
  int read();
  int peek();
  void flush();

  void onReceive(void(*callback)(uint8_t, int));

  void receive(int size = 0);
  void standby();
  void sleep();

  bool preInit();
  int8_t getTxPower();
  void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN);
  uint32_t getFrequency();
  void setFrequency(uint32_t frequency);
  void setSpreadingFactor(int sf);
  uint8_t getSpreadingFactor();
  uint32_t getSignalBandwidth();
  void setSignalBandwidth(uint32_t sbw);
  void setCodingRate4(int denominator);
  uint8_t getCodingRate4();
  void setPreambleLength(long length);
  bool dcd();
  void enableCrc();
  void disableCrc();
  void enableTCXO();
  void disableTCXO();


  byte random();

  void setSPIFrequency(uint32_t frequency);

  void dumpRegisters(Stream& out);

  void handleDio0Rise();
private:
  void writeBuffer(const uint8_t* buffer, size_t size);
  void readBuffer(uint8_t* buffer, size_t size);
  void loraMode();
  void rxAntEnable();
  void setPacketParams(uint32_t preamble, uint8_t headermode, uint8_t length, uint8_t crc);
  void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro);
  void setSyncWord(uint16_t sw);
  void waitOnBusy();
  void executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size);
  void executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size);
  void explicitHeaderMode();
  void implicitHeaderMode();


  uint8_t readRegister(uint16_t address);
  void writeRegister(uint16_t address, uint8_t value);
  uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value);

  static void onDio0Rise();

  void handleLowDataRate();
  void optimizeModemSensitivity();

  void calibrate(void);
  void calibrate_image(uint32_t frequency);
  bool getPacketValidity();

private:
  SPISettings _spiSettings;
  SPIClass* _spiModem;
  int _ss;
  int _sclk;
  int _mosi;
  int _miso;
  int _reset;
  int _dio0;
  int _rxen;
  int _busy;
  uint32_t _frequency;
  uint8_t _bw;
  uint8_t _cr;
  int _packetIndex;
  int _implicitHeaderMode;
  int _payloadLength;
  int _crcMode;
  int _fifo_tx_addr_ptr;
  int _fifo_rx_addr_ptr;
  bool _preinit_done;
  bool _tcxo;
  bool _dio2_as_rf_switch;
};

class sx127x : public RadioInterface {
public:
  sx127x(uint8_t index, SPIClass* spi, int ss, int sclk, int mosi, int miso, int reset, int dio0, int busy);

  void reset();

  int begin();
  void end();

  int beginPacket(int implicitHeader = false);
  int endPacket();

  int packetRssi(uint8_t pkt_snr_raw = 0xFF);
  int packetRssi();
  int currentRssi();
  uint8_t packetRssiRaw();
  uint8_t currentRssiRaw();
  uint8_t packetSnrRaw();
  float packetSnr();
  long packetFrequencyError();

  // from Print
  size_t write(uint8_t byte);
  size_t write(const uint8_t *buffer, size_t size);

  // from Stream
  int available();
  int read();
  int peek();
  void flush();

  void onReceive(void(*callback)(uint8_t, int));

  void receive(int size = 0);
  void standby();
  void sleep();

  bool preInit();
  int8_t getTxPower();
  void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN);
  uint32_t getFrequency();
  void setFrequency(uint32_t frequency);
  void setSpreadingFactor(int sf);
  uint8_t getSpreadingFactor();
  uint32_t getSignalBandwidth();
  void setSignalBandwidth(uint32_t sbw);
  void setCodingRate4(int denominator);
  uint8_t getCodingRate4();
  void setPreambleLength(long length);
  bool dcd();
  void enableCrc();
  void disableCrc();
  void enableTCXO();
  void disableTCXO();

  byte random();

  void setSPIFrequency(uint32_t frequency);

  void handleDio0Rise();
  bool getPacketValidity();
private:
  void setSyncWord(uint8_t sw);
  void explicitHeaderMode();
  void implicitHeaderMode();


  uint8_t readRegister(uint8_t address);
  void writeRegister(uint8_t address, uint8_t value);
  uint8_t singleTransfer(uint8_t address, uint8_t value);

  static void onDio0Rise();

  void handleLowDataRate();
  void optimizeModemSensitivity();

private:
  SPISettings _spiSettings;
  SPIClass* _spiModem;
  int _ss;
  int _sclk;
  int _mosi;
  int _miso;
  int _reset;
  int _dio0;
  int _busy;
  uint32_t _frequency;
  int _packetIndex;
  int _implicitHeaderMode;
  bool _preinit_done;
  uint8_t _cr;
  uint32_t _bw;
};

class sx128x : public RadioInterface {
public:
  sx128x(uint8_t index, SPIClass* spi, bool tcxo, int ss, int sclk, int mosi, int miso, int reset, int dio0, int busy, int rxen, int txen);

  void reset();

  int begin();
  void end();

  int beginPacket(int implicitHeader = false);
  int endPacket();

  int packetRssi(uint8_t pkt_snr_raw = 0xFF);
  int currentRssi();
  uint8_t packetRssiRaw();
  uint8_t currentRssiRaw();
  uint8_t packetSnrRaw();
  float packetSnr();
  long packetFrequencyError();

  // from Print
  size_t write(uint8_t byte);
  size_t write(const uint8_t *buffer, size_t size);

  // from Stream
  int available();
  int read();
  int peek();
  void flush();

  void onReceive(void(*callback)(uint8_t, int));

  void receive(int size = 0);
  void standby();
  void sleep();

  bool preInit();
  int8_t getTxPower();
  void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN);
  uint32_t getFrequency();
  void setFrequency(uint32_t frequency);
  void setSpreadingFactor(int sf);
  uint8_t getSpreadingFactor();
  uint32_t getSignalBandwidth();
  void setSignalBandwidth(uint32_t sbw);
  void setCodingRate4(int denominator);
  uint8_t getCodingRate4();
  void setPreambleLength(long length);
  bool dcd();
  void enableCrc();
  void disableCrc();
  void enableTCXO();
  void disableTCXO();

  byte random();

  void setSPIFrequency(uint32_t frequency);

  void dumpRegisters(Stream& out);

  void handleDio0Rise();

  bool getPacketValidity();

private:
  void writeBuffer(const uint8_t* buffer, size_t size);
  void readBuffer(uint8_t* buffer, size_t size);
  void txAntEnable();
  void rxAntEnable();
  void loraMode();
  void waitOnBusy();
  void executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size);
  void executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size);
  void setPacketParams(uint32_t preamble, uint8_t headermode, uint8_t length, uint8_t crc);
  void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr);
  void setSyncWord(int sw);
  void explicitHeaderMode();
  void implicitHeaderMode();


  uint8_t readRegister(uint16_t address);
  void writeRegister(uint16_t address, uint8_t value);
  uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value);

  static void onDio0Rise();

  void handleLowDataRate();
  void optimizeModemSensitivity();

private:
  SPISettings _spiSettings;
  SPIClass* _spiModem;
  int _ss;
  int _sclk;
  int _mosi;
  int _miso;
  int _reset;
  int _dio0;
  int _rxen;
  int _txen;
  int _busy;
  int _modem;
  uint32_t _frequency;
  uint8_t _bw;
  uint8_t _cr;
  int _packetIndex;
  int _implicitHeaderMode;
  int _payloadLength;
  int _crcMode;
  int _fifo_tx_addr_ptr;
  int _fifo_rx_addr_ptr;
  bool _preinit_done;
  int _rxPacketLength;
  bool _tcxo;
  uint8_t _preamble_e;
  uint8_t _preamble_m;
  uint32_t _last_preamble;
};
#endif
