#define U8X8_USE_PINS
#include <driver/uart.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <string.h>
#include <u8g2.h>

#include "sx127x_driver.h"
#include "u8g2_esp32_hal.h"

const char *TAG = "uas-ugv";

// U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, 16, 15, 4);
u8g2_t u8g2;

// SPIClass lora_spi(VSPI);

#define LORA_SCK 5
#define LORA_MISO 19
#define LORA_MOSI 27
#define LORA_CS 18
#define LORA_RST 14
#define LORA_IRQ 26

#define LORA_FREQ (433E6)
#define LORA_BUF_LEN 64

#define OLED_H 64
#define OLED_W 128

struct Packet {
  int    rssi;
  double snr;
  size_t buffer_len;
  char   buffer[LORA_BUF_LEN];
};

uint16_t packet_num;

sx127x_t *lora;

void loraOnReceive(int packetSize);
void loraTask(void *params);

TaskHandle_t lora_task_hndl;
// packets recieved (type Packet)
QueueHandle_t lora_packet_recv_queue;
// packet lengths from the recieve isr (type int)
QueueHandle_t lora_packet_isr_queue;

struct Packet packet;

void setup_oled(void) {
  u8g2_esp32_hal_t u8g2_hal_config = {
      .scl   = 15,
      .sda   = 4,
      .reset = 16,
  };
  u8g2_esp32_hal_init(u8g2_hal_config);

  u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb,
                                         u8g2_esp32_gpio_and_delay_cb);
  u8g2_InitDisplay(&u8g2);
  u8g2_ClearDisplay(&u8g2);
  u8g2_SetPowerSave(&u8g2, false);
}

void setup(void) {
  ESP_LOGI(TAG, "setup");

  setup_oled();

  lora_packet_recv_queue = xQueueCreate(4, sizeof(struct Packet));
  lora_packet_isr_queue  = xQueueCreate(4, sizeof(int));
  configASSERT(lora_packet_recv_queue != 0);
  configASSERT(lora_packet_isr_queue != 0);

  sx127x_config_t lora_config  = SX127X_CONFIG_DEFAULT;
  lora_config.tx_power         = 17;
  lora_config.spreading_factor = 11;
  lora_config.signal_bandwidth = 125E3;
  lora_config.sync_word        = 0x34;
  lora_config.crc              = SX127X_CRC_ENABLED;

  esp_err_t ret = sx127x_init(&lora_config, &lora);
  if (ret != ESP_OK) {
    const char *err_name = esp_err_to_name(ret);
    ESP_LOGE(TAG, "LoRa init failed: %s", err_name);
  } else {
    ESP_LOGI(TAG, "LoRa initialized");
    ret = sx127x_start(lora);
    if (ret != ESP_OK) {
      ESP_LOGI(TAG, "LoRa start failed: %d", ret);
    }
  }
  // LoRa.onReceive(loraOnReceive);
  // LoRa.receive(0);

  packet_num = 0;

  xTaskCreate(loraTask, "loraTask", 1024 * 10, NULL, 2, &lora_task_hndl);
  memset(&packet, 0, sizeof(struct Packet));
}

#define XO 10

void loraOnReceive(int packetSize) {
  if (packetSize == 0) return;
  ESP_LOGV(TAG, "loraOnReceive");
  xQueueSendFromISR(lora_packet_isr_queue, &packetSize, NULL);
}

void loraTask(void *params) {
  char          tx_buf[20];
  const size_t  tx_buf_len = (sizeof(tx_buf) / sizeof(tx_buf[0]));
  int           packet_len;
  TickType_t    send_period  = pdMS_TO_TICKS(1000);
  TickType_t    current_time = xTaskGetTickCount();
  TickType_t    next_send    = current_time + send_period;
  struct Packet recvd_packet;
  while (true) {
    TickType_t delay_ticks = next_send - current_time;
    BaseType_t didReceive =
        xQueueReceive(lora_packet_isr_queue, &packet_len, delay_ticks);
    if (didReceive) {
      int packetSize =
          (packet_len > LORA_BUF_LEN - 1) ? (LORA_BUF_LEN - 1) : (packet_len);
      // LoRa.setTimeout(50);
      // LoRa.readBytes(recvd_packet.buffer, packetSize);
      recvd_packet.buffer_len             = packetSize;
      recvd_packet.buffer[packetSize - 1] = '\0';
      // recvd_packet.rssi = LoRa.packetRssi();
      // recvd_packet.snr = LoRa.packetSnr();

      xQueueSend(lora_packet_recv_queue, &recvd_packet, 10);
    }
    current_time = xTaskGetTickCount();
    if (current_time >= next_send) {
      int written_bytes =
          snprintf(tx_buf, tx_buf_len, "hello world %d", packet_num);
      if (written_bytes < 0) {
        ESP_LOGE(TAG, "snprintf error: %d", written_bytes);
        continue;
      }
      packet_num++;
      esp_err_t ret = sx127x_send_packet(lora, tx_buf, written_bytes,
                                         0);  // 0 means error if queue full
      if (ret != ESP_OK) {
        ESP_LOGE(TAG, "error sending packet: %d", ret);
      } else {
        ESP_LOGI(TAG, "lora wrote %d bytes", written_bytes);
      }

      current_time = xTaskGetTickCount();
      next_send    = current_time + send_period;
    }
  }
}

void loop(void) {
  ESP_LOGI(TAG, "loop");
  u8g2_FirstPage(&u8g2);
  xQueueReceive(lora_packet_recv_queue, &packet, 10);
  do {
    u8g2_DrawRFrame(&u8g2, 0, 0, OLED_W, OLED_H, 4);

    multi_heap_info_t heap_info;
    heap_caps_get_info(&heap_info, MALLOC_CAP_DEFAULT);
    u8g2_SetFont(&u8g2, u8g2_font_4x6_mf);
    u8g2_DrawStr(&u8g2, 4, 8, "Hello World!");
    char buf[32];
    memset(buf, 0, 32);
    snprintf(buf, 32, "heap allc/free %d/%d", heap_info.total_allocated_bytes,
             heap_info.total_free_bytes);
    u8g2_DrawStr(&u8g2, 4, 8 + 8, buf);

    if (packet.buffer_len) {
      ESP_LOGI(TAG, "lora received packet (len %d, rssi: %d, snr: %f): %s\n",
               packet.buffer_len, packet.rssi, packet.snr, packet.buffer);
      u8g2_SetFont(&u8g2, u8g2_font_4x6_mf);
      snprintf(buf, 40, "lora pkt(rssi: %d, snr: %f)", packet.rssi, packet.snr);
      u8g2_DrawStr(&u8g2, 4, 8 + 8 + 8, buf);
      snprintf(buf, 40, "%s", packet.buffer);
      u8g2_DrawStr(&u8g2, 4, 8 + 8 + 8 + 8, buf);
    }
  } while (u8g2_NextPage(&u8g2));
  vTaskDelay(pdMS_TO_TICKS(1000));
}

void loopTask(void *pvUser) {
  setup();
  while (1) {
    loop();
  }
}

void app_main() {
  xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
}