#include "TBeamSupremeBleInterface.h"

#include <Arduino.h>
#include <Destination.h>
#include <Log.h>
#include <Packet.h>
#include <Reticulum.h>
#include <Transport.h>
#include <Type.h>
#include <Utilities/OS.h>
#include <microStore/Adapters/UniversalFileSystem.h>
#include <microStore/FileSystem.h>

#ifndef NODE_LABEL
#define NODE_LABEL "?"
#endif

static RNS::Reticulum reticulum({RNS::Type::NONE});
static RNS::Interface ble_interface({RNS::Type::NONE});
static RNS::Destination inbound_destination({RNS::Type::NONE});
static RNS::Destination outbound_destination({RNS::Type::NONE});
static TBeamSupremeBleInterface* ble_impl = nullptr;

static void print_config() {
  Serial.printf("Node=%s\r\n", NODE_LABEL);
  Serial.printf("BLE role=%s service=37145b00-442d-4a94-917f-8f42c5da28e3\r\n",
                ble_impl ? ble_impl->role_name() : "?");
}

static void on_packet(const RNS::Bytes& data, const RNS::Packet& packet) {
  (void)packet;
  Serial.printf("RX RNS BLE: %s\r\n", data.toString().c_str());
}

static void setup_reticulum() {
  microStore::FileSystem filesystem{microStore::Adapters::UniversalFileSystem()};
  filesystem.init();
  RNS::Utilities::OS::register_filesystem(filesystem);

  ble_impl = new TBeamSupremeBleInterface();
  ble_interface = ble_impl;
  ble_interface.mode(RNS::Type::Interface::MODE_GATEWAY);
  RNS::Transport::register_interface(ble_interface);
  ble_interface.start();

  reticulum = RNS::Reticulum();
  reticulum.transport_enabled(false);
  reticulum.probe_destination_enabled(false);
  reticulum.start();

  inbound_destination = RNS::Destination({RNS::Type::NONE},
                                         RNS::Type::Destination::IN,
                                         RNS::Type::Destination::PLAIN,
                                         "microreticulum",
                                         "pingpong");
  inbound_destination.set_packet_callback(on_packet);

  outbound_destination = RNS::Destination({RNS::Type::NONE},
                                          RNS::Type::Destination::OUT,
                                          RNS::Type::Destination::PLAIN,
                                          "microreticulum",
                                          "pingpong");
}

void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 5000) {
    delay(100);
  }
  delay(250);

  Serial.println();
  RNS::loglevel(RNS::LOG_NOTICE);
  setup_reticulum();
  Serial.println("Exercise 301: microReticulum BLE ping-pong");
  print_config();
  Serial.println("microReticulum ready");
}

void loop() {
  reticulum.loop();

  static uint32_t next_tx_ms = 0;
  static uint32_t next_wait_log_ms = 0;
  static uint32_t iter = 0;
  uint32_t now = millis();

  if (next_tx_ms == 0) {
    uint32_t offset = (NODE_LABEL[0] == 'A') ? 500 : 1500;
    next_tx_ms = now + offset;
  }

  if ((int32_t)(now - next_tx_ms) >= 0) {
    next_tx_ms = now + 2000;
    if (ble_impl && !ble_impl->connected()) {
      if (next_wait_log_ms == 0 || (int32_t)(now - next_wait_log_ms) >= 0) {
        next_wait_log_ms = now + 10000;
        Serial.printf("BLE %s waiting for peer\r\n", ble_impl->role_name());
      }
      return;
    }

    String message = String(NODE_LABEL) + " says hi. iter=" + String(iter++);
    Serial.printf("TX RNS BLE: %s\r\n", message.c_str());

    RNS::Packet packet(outbound_destination,
                       RNS::bytesFromString(message.c_str()),
                       RNS::Type::Packet::DATA,
                       RNS::Type::Packet::CONTEXT_NONE,
                       RNS::Type::Transport::BROADCAST,
                       RNS::Type::Packet::HEADER_1,
                       {RNS::Bytes::NONE},
                       false);
    packet.send();
  }

  delay(5);
}

int _write(int file, char* ptr, int len) {
  (void)file;
  int wrote = Serial.write(ptr, len);
  Serial.flush();
  return wrote;
}
