/*
  xdrv_73_9_lora.ino - LoRa support for Tasmota

  SPDX-FileCopyrightText: 2024 Theo Arends

  SPDX-License-Identifier: GPL-3.0-only
*/

#ifdef USE_SPI
#ifdef USE_SPI_LORA
/*********************************************************************************************\
 * LoRa
\*********************************************************************************************/

#define XDRV_73                 73

/*********************************************************************************************/

void LoraDefaults(void) {
  Lora->settings.frequency = TAS_LORA_FREQUENCY;
  Lora->settings.bandwidth = TAS_LORA_BANDWIDTH;
  Lora->settings.spreading_factor = TAS_LORA_SPREADING_FACTOR;
  Lora->settings.coding_rate = TAS_LORA_CODING_RATE;
  Lora->settings.sync_word = TAS_LORA_SYNC_WORD;
  Lora->settings.output_power = TAS_LORA_OUTPUT_POWER;
  Lora->settings.preamble_length = TAS_LORA_PREAMBLE_LENGTH;
  Lora->settings.current_limit = TAS_LORA_CURRENT_LIMIT;
  Lora->settings.implicit_header = TAS_LORA_HEADER;
  Lora->settings.crc_bytes = TAS_LORA_CRC_BYTES;
}

void LoraWanDefaults(void) {
  Lora->settings.frequency = TAS_LORAWAN_FREQUENCY;
  Lora->settings.bandwidth = TAS_LORAWAN_BANDWIDTH;
  Lora->settings.spreading_factor = TAS_LORAWAN_SPREADING_FACTOR;
  Lora->settings.coding_rate = TAS_LORAWAN_CODING_RATE;
  Lora->settings.sync_word = TAS_LORAWAN_SYNC_WORD;
  Lora->settings.output_power = TAS_LORAWAN_OUTPUT_POWER;
  Lora->settings.preamble_length = TAS_LORAWAN_PREAMBLE_LENGTH;
  Lora->settings.current_limit = TAS_LORAWAN_CURRENT_LIMIT;
  Lora->settings.implicit_header = TAS_LORAWAN_HEADER;
  Lora->settings.crc_bytes = TAS_LORAWAN_CRC_BYTES;
}

void LoraSettings2Json(void) {
  ResponseAppend_P(PSTR("\"" D_JSON_FREQUENCY "\":%1_f"), &Lora->settings.frequency);              // xxx.x MHz
  ResponseAppend_P(PSTR(",\"" D_JSON_BANDWIDTH "\":%1_f"), &Lora->settings.bandwidth);             // xxx.x kHz
  ResponseAppend_P(PSTR(",\"" D_JSON_SPREADING_FACTOR "\":%d"), Lora->settings.spreading_factor);
  ResponseAppend_P(PSTR(",\"" D_JSON_CODINGRATE4 "\":%d"), Lora->settings.coding_rate);
  ResponseAppend_P(PSTR(",\"" D_JSON_SYNCWORD "\":%d"), Lora->settings.sync_word);
  ResponseAppend_P(PSTR(",\"" D_JSON_OUTPUT_POWER "\":%d"), Lora->settings.output_power);          // dBm
  ResponseAppend_P(PSTR(",\"" D_JSON_PREAMBLE_LENGTH "\":%d"), Lora->settings.preamble_length);    // symbols
  ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT_LIMIT "\":%1_f"), &Lora->settings.current_limit);     // xx.x mA (Overcurrent Protection - OCP)
  ResponseAppend_P(PSTR(",\"" D_JSON_IMPLICIT_HEADER "\":%d"), Lora->settings.implicit_header);    // 0 = explicit
  ResponseAppend_P(PSTR(",\"" D_JSON_CRC_BYTES "\":%d"), Lora->settings.crc_bytes);                // bytes
}

void LoraJson2Settings(JsonParserObject root) {
  Lora->settings.frequency = root.getFloat(PSTR(D_JSON_FREQUENCY), Lora->settings.frequency);
  Lora->settings.bandwidth = root.getFloat(PSTR(D_JSON_BANDWIDTH), Lora->settings.bandwidth);
  Lora->settings.spreading_factor = root.getUInt(PSTR(D_JSON_SPREADING_FACTOR), Lora->settings.spreading_factor);
  Lora->settings.coding_rate = root.getUInt(PSTR(D_JSON_CODINGRATE4), Lora->settings.coding_rate);
  Lora->settings.sync_word = root.getUInt(PSTR(D_JSON_SYNCWORD), Lora->settings.sync_word);
  Lora->settings.output_power = root.getUInt(PSTR(D_JSON_OUTPUT_POWER), Lora->settings.output_power);
  Lora->settings.preamble_length = root.getUInt(PSTR(D_JSON_PREAMBLE_LENGTH), Lora->settings.preamble_length);
  Lora->settings.current_limit = root.getFloat(PSTR(D_JSON_CURRENT_LIMIT), Lora->settings.current_limit);
  Lora->settings.implicit_header = root.getUInt(PSTR(D_JSON_IMPLICIT_HEADER), Lora->settings.implicit_header);
  Lora->settings.crc_bytes = root.getUInt(PSTR(D_JSON_CRC_BYTES), Lora->settings.crc_bytes);
}

/*********************************************************************************************\
 * Driver Settings load and save
\*********************************************************************************************/

#ifdef USE_UFILESYS
#define XDRV_73_KEY           "drvset73"

bool LoraLoadData(void) {
  char key[] = XDRV_73_KEY;
  String json = UfsJsonSettingsRead(key);
  if (json.length() == 0) { return false; }

  // {"Crc":1882268982,"Flags":0,"Frequency":868.1,"Bandwidth":125.0,"SpreadingFactor":7,"CodingRate4":5,"SyncWord":52,"OutputPower":10,"PreambleLength":8,"CurrentLimit":60.0,"ImplicitHeader":0,"CrcBytes":2}
  JsonParser parser((char*)json.c_str());
  JsonParserObject root = parser.getRootObject();
  if (!root) { return false; }

  Lora->settings.crc32 = root.getUInt(PSTR("Crc"), Lora->settings.crc32);
  Lora->settings.flags = root.getUInt(PSTR("Flags"), Lora->settings.flags);
  LoraJson2Settings(root);

#ifdef USE_LORAWAN_BRIDGE
  if (!LoraWanLoadData()) {
    return false;
  }
#endif  // USE_LORAWAN_BRIDGE

  return true;
}

bool LoraSaveData(void) {
  Response_P(PSTR("{\"" XDRV_73_KEY "\":{"
                   "\"Crc\":%u,"
                   "\"Flags\":%u,"),
                   Lora->settings.crc32,
                   Lora->settings.flags);
  LoraSettings2Json();
  ResponseAppend_P(PSTR("}}"));

  if (!UfsJsonSettingsWrite(ResponseData())) {
    return false;
  }
#ifdef USE_LORAWAN_BRIDGE
  if (!LoraWanSaveData()) {
    return false;
  }
#endif  // USE_LORAWAN_BRIDGE
  return true;
}

void LoraDeleteData(void) {
  char key[] = XDRV_73_KEY;
  UfsJsonSettingsDelete(key);  // Use defaults
#ifdef USE_LORAWAN_BRIDGE
  LoraWanDeleteData();
#endif  // USE_LORAWAN_BRIDGE
}
#endif  // USE_UFILESYS

/*********************************************************************************************/

void LoraSettingsLoad(bool erase) {
  // Called from FUNC_PRE_INIT (erase = 0) once at restart
  // Called from FUNC_RESET_SETTINGS (erase = 1) after command reset 4, 5, or 6

  // *** Start init default values in case key is not found ***
  memset(&Lora->settings, 0x00, sizeof(LoraSettings_t));
  // Init any other parameter in struct LoraSettings
  LoraDefaults();
  // *** End Init default values ***

#ifndef USE_UFILESYS
  AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: Lora use defaults as file system not enabled"));
#else
  // Try to load key
  if (erase) {
    LoraDeleteData();
  }
  else if (LoraLoadData()) {
    AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: Lora loaded from file"));
  }
  else {
    // File system not ready: No flash space reserved for file system
    AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("CFG: Lora use defaults as file system not ready or key not found"));
  }
#endif  // USE_UFILESYS
}

void LoraSettingsSave(void) {
  // Called from FUNC_SAVE_SETTINGS every SaveData second and at restart
#ifdef USE_UFILESYS
  uint32_t crc32 = GetCfgCrc32((uint8_t*)&Lora->settings +4, sizeof(LoraSettings_t) -4);  // Skip crc32
  if (crc32 != Lora->settings.crc32) {
    Lora->settings.crc32 = crc32;
    if (LoraSaveData()) {
      AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: Lora saved to file"));
    } else {
      // File system not ready: No flash space reserved for file system
      AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("CFG: Lora ERROR File system not ready or unable to save file"));
    }
  }
#endif  // USE_UFILESYS
}

/*********************************************************************************************/

bool LoraSend(uint8_t* data, uint32_t len, bool invert) {
  uint32_t lora_time = millis();         // Time is important for LoRaWan RX windows
  bool result = Lora->Send(data, len, invert);
  AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Send (%u) '%*_H', Invert %d, Time %d"),
    lora_time, len, data, invert, TimePassedSince(lora_time));
  return result;
}

void LoraInput(void) {
  if (!Lora->Available()) { return; }

  char data[TAS_LORA_MAX_PACKET_LENGTH] = { 0 };
  int packet_size = Lora->Receive(data);
  if (!packet_size) { return; }
  AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Rcvd (%u) '%*_H', RSSI %1_f, SNR %1_f"),
    Lora->receive_time, packet_size, data, &Lora->rssi, &Lora->snr);

#ifdef USE_LORAWAN_BRIDGE
  if (bitRead(Lora->settings.flags, TAS_LORA_FLAG_BRIDGE_ENABLED)) {
    if (LoraWanInput((uint8_t*)data, packet_size)) {
      return;
    }
  }
#endif  // USE_LORAWAN_BRIDGE
  Lora->receive_time = 0;

  char *rcvd_data = data;
  if (TAS_LORA_REMOTE_COMMAND == data[0]) {
    char *payload = data +1;             // Skip TAS_LORA_REMOTE_COMMAND
    char *command_part;
    char *topic_part = strtok_r(payload, " ", &command_part);
    if (topic_part && command_part) {
      if (!strcasecmp(topic_part, SettingsText(SET_MQTT_TOPIC))) {  // Is it mine
        if (bitRead(Lora->settings.flags, TAS_LORA_COMMAND_ENABLED)) {
          ExecuteCommand(command_part, SRC_REMOTE);
          return;
        }
      } else {
        *--command_part = ' ';           // Restore strtok_r '/0'
      }
    }
    rcvd_data++;                         // Remove TAS_LORA_REMOTE_COMMAND header
    packet_size--;                       // Adjust packet size
  }

  bool raw = Lora->raw;
  // Set raw mode if zeroes within data
  for (uint32_t i = 0; i < packet_size; i++) {
    if (0 == rcvd_data[i]) {
      raw = true;
      break;
    }
  }
  bool assume_json = (!raw && (rcvd_data[0] == '{'));
  Response_P(PSTR("{\"LoRaReceived\":"));
  if (assume_json) {
    ResponseAppend_P(rcvd_data);
  } else {
    ResponseAppend_P(PSTR("\""));
    if (raw) {
      ResponseAppend_P(PSTR("%*_H"), packet_size, rcvd_data);
    } else {
      ResponseAppend_P(EscapeJSONString(rcvd_data).c_str());
    }
    ResponseAppend_P(PSTR("\""));
  }
  ResponseAppend_P(PSTR(",\"RSSI\":%1_f,\"SNR\":%1_f}"), &Lora->rssi, &Lora->snr);

  MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR("LoRaReceived"));
}

void LoraInit(void) {
  if ((SPI_MOSI_MISO == TasmotaGlobal.spi_enabled) &&
      (PinUsed(GPIO_LORA_CS)) && (PinUsed(GPIO_LORA_RST))) {
#ifdef ESP8266
    SPI.begin();
#endif // ESP8266
#ifdef ESP32
    SPI.begin(Pin(GPIO_SPI_CLK), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_MOSI), -1);
//    SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
#endif // ESP32

    Lora = (Lora_t*)calloc(sizeof(Lora_t), 1);    // Need calloc to reset registers to 0/false
    if (nullptr == Lora) { return; }
    LoraSettingsLoad(0);
#ifdef USE_LORAWAN_BRIDGE
    LoraWanInit();
#endif  // USE_LORAWAN_BRIDGE

    bool present = false;
    char hardware[20];
    if (false) {
      // Need this as following `else if`s may not be present
    }
#ifdef USE_LORA_SX127X
    else if (PinUsed(GPIO_LORA_DI0)) {
      // SX1276, RFM95W
      if (LoraSx127xInit()) {
        Lora->Config = &LoraSx127xConfig;
        Lora->Available = &LoraSx127xAvailable;
        Lora->Receive = &LoraSx127xReceive;
        Lora->Send = &LoraSx127xSend;
        strcpy_P(hardware, PSTR("SX127x"));
        present = true;
      }
    }
#endif  // USE_LORA_SX127X
#ifdef USE_LORA_SX126X
    else if (PinUsed(GPIO_LORA_DI1) && PinUsed(GPIO_LORA_BUSY)) {
      // SX1262, LilyGoT3S3
      if (LoraSx126xInit()) {
        Lora->Config = &LoraSx126xConfig;
        Lora->Available = &LoraSx126xAvailable;
        Lora->Receive = &LoraSx126xReceive;
        Lora->Send = &LoraSx126xSend;
        strcpy_P(hardware, PSTR("SX126x"));
        present = true;
      }
    }
#endif  // USE_LORA_SX126X

    if (present) {
      AddLog(LOG_LEVEL_INFO, PSTR("LOR: %s initialized"), hardware);
    } else {
      free(Lora);
      Lora = nullptr;
    }
  }
}

/*********************************************************************************************\
 * Commands
\*********************************************************************************************/

#define D_CMND_LORASEND       "Send"
#define D_CMND_LORACONFIG     "Config"
#define D_CMND_LORACOMMAND    "Command"
#define D_CMND_LORAOPTION     "Option"

const char kLoraCommands[] PROGMEM = "LoRa|"  // Prefix
  D_CMND_LORASEND "|" D_CMND_LORACONFIG "|" D_CMND_LORACOMMAND "|" D_CMND_LORAOPTION;

void (* const LoraCommand[])(void) PROGMEM = {
  &CmndLoraSend, &CmndLoraConfig, &CmndLoraCommand, &CmndLoraOption };

void CmndLoraOption(void) {
  // LoraOption1 1 - Enable LoRaWanBridge
  // LoraOption2 1 - Enable LoRaWanBridge Join
  // LoraOption3 1 - Enable LoRaWanBridge decoding
  // LoraOption4 1 - Enable LoRaCommand reception
  if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 8)) {
    uint32_t pindex = XdrvMailbox.index -1;
    if (XdrvMailbox.payload >= 0) {
      bitWrite(Lora->settings.flags, pindex, XdrvMailbox.payload);
    }
    ResponseCmndIdxChar(GetStateText(bitRead(Lora->settings.flags, pindex)));
  }
}

void CmndLoraCommand(void) {
  // LoRaCommand <topic_of_lora_receiver> <command>
  // LoRaCommand lorareceiver power 2
  // LoRaCommand lorareceiver publish cmnd/anytopic/power 2
  // LoRaCommand lorareceiver LoRaCommand thisreceiver status
  if (XdrvMailbox.data_len > 0) {
    char data[TAS_LORA_MAX_PACKET_LENGTH] = { 0 };
    XdrvMailbox.data_len++;       // Add Signal CmndLoraCommand to lora receiver
    uint32_t len = (XdrvMailbox.data_len < TAS_LORA_MAX_PACKET_LENGTH -1) ? XdrvMailbox.data_len : TAS_LORA_MAX_PACKET_LENGTH -2;
    data[0] = TAS_LORA_REMOTE_COMMAND;
    strlcpy(data +1, XdrvMailbox.data, len);
    LoraSend((uint8_t*)data, len, false);
    ResponseCmndDone();
  }
}

void CmndLoraSend(void) {
  // LoRaSend "Hello Tiger"     - Send "Hello Tiger\n"
  // LoRaSend                   - Set to text decoding
  // LoRaSend1 "Hello Tiger"    - Send "Hello Tiger\n"
  // LoRaSend2 "Hello Tiger"    - Send "Hello Tiger"
  // LoRaSend3 "Hello Tiger"    - Send "Hello Tiger\f"
  // LoRaSend4                  - Set to binary decoding
  // LoRaSend4 "Hello Tiger"    - Send "Hello Tiger" and set to binary decoding
  // LoRaSend5 "AA004566"       - Send "AA004566" as hex values
  // LoRaSend6 "72,101,108,108" - Send decimals as hex values
  // LoRaSend15 "AA004566"      - Send "AA004566" as hex values with invert IQ
  bool invert = false;
  if (XdrvMailbox.index > 9) { 
    XdrvMailbox.index -= 10;
    invert = true;
  }
  if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
    Lora->raw = (XdrvMailbox.index > 3);                                    // Global flag set even without data
    if (XdrvMailbox.data_len > 0) {
      char data[TAS_LORA_MAX_PACKET_LENGTH] = { 0 };
      uint32_t len = (XdrvMailbox.data_len < TAS_LORA_MAX_PACKET_LENGTH -1) ? XdrvMailbox.data_len : TAS_LORA_MAX_PACKET_LENGTH -2;
#ifdef USE_LORA_DEBUG
//      AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Len %d, Send %*_H"), len, len, XdrvMailbox.data);
#endif
      if (1 == XdrvMailbox.index) {                                         // "Hello Tiger\n"
        memcpy(data, XdrvMailbox.data, len);
        data[len++] = '\n';
      }
      else if ((2 == XdrvMailbox.index) || (4 == XdrvMailbox.index)) {      // "Hello Tiger" or "A0"
        memcpy(data, XdrvMailbox.data, len);
      }
      else if (3 == XdrvMailbox.index) {                                    // "Hello\f"
        Unescape(XdrvMailbox.data, &len);
        memcpy(data, XdrvMailbox.data, len);
      }
      else if (5 == XdrvMailbox.index) {                                    // "AA004566" as hex values
        char *p;
        char stemp[3];

        char *codes = RemoveSpace(XdrvMailbox.data);
        int size = strlen(XdrvMailbox.data);

        len = 0;
        while (size > 1) {
          strlcpy(stemp, codes, sizeof(stemp));
          data[len++] = strtol(stemp, &p, 16);
          if (len > TAS_LORA_MAX_PACKET_LENGTH -2) { break; }
          size -= 2;
          codes += 2;
        }
      }
      else if (6 == XdrvMailbox.index) {                                    // "72,101,108,108"
        char *p;
        uint8_t code;
        char *values = XdrvMailbox.data;
        len = 0;
        for (char* str = strtok_r(values, ",", &p); str; str = strtok_r(nullptr, ",", &p)) {
          data[len++] = (uint8_t)atoi(str);
          if (len > TAS_LORA_MAX_PACKET_LENGTH -2) { break; }
        }
      }
      else {
        len = 0;
      }
      if (len) {
        LoraSend((uint8_t*)data, len, invert);
      }
      ResponseCmndDone();
    }
  }
}

void CmndLoraConfig(void) {
  // LoRaConfig                                       - Show all parameters
  // LoRaConfig 1                                     - Set default parameters
  // LoRaConfig 2                                     - Set default LoRaWan bridge parameters
  // LoRaConfig {"Frequency":868.0,"Bandwidth":125.0} - Enter float parameters
  // LoRaConfig {"SyncWord":18}                       - Enter decimal parameter (=0x12)
  if (XdrvMailbox.data_len > 0) {
    if (XdrvMailbox.payload == 1) {
      LoraDefaults();
      Lora->Config();
    }
    else if (XdrvMailbox.payload == 2) {
      LoraWanDefaults();
      Lora->Config();
    }
    else {
      JsonParser parser(XdrvMailbox.data);
      JsonParserObject root = parser.getRootObject();
      if (root) { 
        LoraJson2Settings(root);
        Lora->Config();
      }
    }
  }
  ResponseCmnd();  // {"LoRaConfig":
  ResponseAppend_P(PSTR("{"));
  LoraSettings2Json();
  ResponseAppend_P(PSTR("}}"));
}

/*********************************************************************************************\
 * Interface
\*********************************************************************************************/

bool Xdrv73(uint32_t function) {
  bool result = false;

  if (FUNC_INIT == function) {
    LoraInit();
  }
  else if (Lora) {
    switch (function) {
      case FUNC_LOOP:
      case FUNC_SLEEP_LOOP:
        LoraInput();
        break;
      case FUNC_RESET_SETTINGS:
        LoraSettingsLoad(1);
        break;
      case FUNC_SAVE_SETTINGS:
        LoraSettingsSave();
        break;
      case FUNC_COMMAND:
        result = DecodeCommand(kLoraCommands, LoraCommand);
#ifdef USE_LORAWAN_BRIDGE
        if (!result) {
          result = DecodeCommand(kLoraWanCommands, LoraWanCommand);
        }
#endif  // USE_LORAWAN_BRIDGE
        break;
      case FUNC_ACTIVE:
        result = true;
        break;
    }
  }
  return result;
}

#endif  // USE_SPI_LORA
#endif  // USE_SPI
