#include "TBeamSupremeLoRaInterface.h"

#include <Cryptography/Random.h>
#include <Log.h>

#include <string.h>

#ifndef LORA_CS
#error "LORA_CS not defined"
#endif
#ifndef LORA_DIO1
#error "LORA_DIO1 not defined"
#endif
#ifndef LORA_RESET
#error "LORA_RESET not defined"
#endif
#ifndef LORA_BUSY
#error "LORA_BUSY not defined"
#endif

using namespace RNS;

TBeamSupremeLoRaInterface::TBeamSupremeLoRaInterface(const char* name) : InterfaceImpl(name) {
  _IN = true;
  _OUT = true;
  _bitrate = (double)LORA_SF * ((4.0 / LORA_CR) / (pow(2, LORA_SF) / LORA_BW_KHZ)) * 1000.0;
  _HW_MTU = 508;
}

TBeamSupremeLoRaInterface::~TBeamSupremeLoRaInterface() {
  stop();
  delete _radio;
  delete _module;
}

bool TBeamSupremeLoRaInterface::start() {
  _online = false;
  INFO("LoRa initializing for T-Beam Supreme...");

  SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
  _module = new Module(LORA_CS, LORA_DIO1, LORA_RESET, LORA_BUSY, SPI);
  _radio = new SX1262(_module);

  int state = _radio->begin(
      LORA_FREQ_MHZ,
      LORA_BW_KHZ,
      LORA_SF,
      LORA_CR,
      LORA_SYNC_WORD,
      LORA_TX_POWER_DBM);

  if (state != RADIOLIB_ERR_NONE) {
    ERRORF("LoRa init failed, code %d", state);
    return false;
  }

  state = _radio->startReceive();
  if (state != RADIOLIB_ERR_NONE) {
    ERRORF("LoRa startReceive failed, code %d", state);
    return false;
  }

  _online = true;
  INFO("LoRa init succeeded.");
  return true;
}

void TBeamSupremeLoRaInterface::stop() {
  if (_radio) {
    _radio->standby();
  }
  _online = false;
}

void TBeamSupremeLoRaInterface::loop() {
  if (!_online || !_radio) {
    return;
  }

  if (!_radio->checkIrq(RADIOLIB_IRQ_RX_DONE)) {
    return;
  }

  int len = _radio->getPacketLength();
  uint8_t rx_buf[255];
  int state = _radio->readData(rx_buf, len);

  if (state == RADIOLIB_ERR_NONE && len > 1) {
    _last_rssi = _radio->getRSSI();
    _last_snr = _radio->getSNR();

    uint8_t header = rx_buf[0];
    uint8_t seq = packet_sequence(header);

    if (is_split_packet(header)) {
      if (_rx_seq == SEQ_UNSET || _rx_seq != seq) {
        _rx_seq = seq;
        _rx_buffer.clear();
        _rx_buffer.append(rx_buf + 1, len - 1);
      } else {
        _rx_buffer.append(rx_buf + 1, len - 1);
        _rx_seq = SEQ_UNSET;
        on_incoming(_rx_buffer);
      }
    } else {
      if (_rx_seq != SEQ_UNSET) {
        _rx_buffer.clear();
        _rx_seq = SEQ_UNSET;
      }
      _rx_buffer.clear();
      _rx_buffer.append(rx_buf + 1, len - 1);
      on_incoming(_rx_buffer);
    }
  } else if (state != RADIOLIB_ERR_NONE) {
    DEBUGF("LoRa readData failed, code %d", state);
  }

  _radio->startReceive();
}

void TBeamSupremeLoRaInterface::send_outgoing(const Bytes& data) {
  if (!_online || !_radio) {
    return;
  }

  try {
    uint8_t tx_buf[255];
    uint8_t rand_nibble = (uint8_t)(Cryptography::randomnum(256)) & 0xF0;

    if ((int)data.size() <= LORA_MAX_PAYLOAD) {
      tx_buf[0] = rand_nibble;
      memcpy(tx_buf + 1, data.data(), data.size());
      int state = _radio->transmit(tx_buf, 1 + data.size());
      if (state != RADIOLIB_ERR_NONE) {
        ERRORF("LoRa transmit failed, code %d", state);
      }
    } else {
      uint8_t seq = (_tx_seq_ctr++) & HEADER_SEQ_MASK;
      uint8_t split_header = rand_nibble | HEADER_SPLIT | seq;

      tx_buf[0] = split_header;
      memcpy(tx_buf + 1, data.data(), LORA_MAX_PAYLOAD);
      int state = _radio->transmit(tx_buf, 1 + LORA_MAX_PAYLOAD);
      if (state != RADIOLIB_ERR_NONE) {
        ERRORF("LoRa transmit part 1 failed, code %d", state);
      }

      size_t remainder = data.size() - LORA_MAX_PAYLOAD;
      tx_buf[0] = split_header;
      memcpy(tx_buf + 1, data.data() + LORA_MAX_PAYLOAD, remainder);
      state = _radio->transmit(tx_buf, 1 + remainder);
      if (state != RADIOLIB_ERR_NONE) {
        ERRORF("LoRa transmit part 2 failed, code %d", state);
      }
    }

    _radio->startReceive();
    InterfaceImpl::handle_outgoing(data);
  } catch (const std::exception& e) {
    ERRORF("LoRa transmit exception: %s", e.what());
  }
}

void TBeamSupremeLoRaInterface::on_incoming(const Bytes& data) {
  InterfaceImpl::handle_incoming(data);
}
