#include "DebugStats.h"

#if RNS_DEBUG_INSTRUMENTATION
#include <esp_heap_caps.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#endif

static String debug_board = "unknown";
static const char* debug_role = "unknown";
static uint32_t debug_failure_window_until = 0;
static uint32_t debug_next_mem_ms = 0;
#if RNS_DEBUG_INSTRUMENTATION
static SemaphoreHandle_t debug_serial_mutex = nullptr;
static char debug_log_buffer[512];

static SemaphoreHandle_t serial_mutex() {
  if (!debug_serial_mutex) {
    debug_serial_mutex = xSemaphoreCreateMutex();
  }
  return debug_serial_mutex;
}
#endif

void DebugStats::set_board(const String& board) {
  debug_board = board.length() ? board : String("unknown");
}

void DebugStats::set_role(const char* role) {
  debug_role = role ? role : "unknown";
}

const char* DebugStats::board() {
  return debug_board.c_str();
}

const char* DebugStats::role() {
  return debug_role;
}

uint32_t DebugStats::crc32(const uint8_t* data, size_t len) {
  uint32_t crc = 0xFFFFFFFFUL;
  for (size_t i = 0; i < len; ++i) {
    crc ^= data[i];
    for (uint8_t bit = 0; bit < 8; ++bit) {
      crc = (crc >> 1) ^ (0xEDB88320UL & (0UL - (crc & 1UL)));
    }
  }
  return ~crc;
}

String DebugStats::edge_hex(const uint8_t* data, size_t len, bool first) {
  char out[9];
  out[0] = '\0';
  if (!data || len == 0) {
    return String("none");
  }
  size_t edge_len = len < 4 ? len : 4;
  size_t start = first ? 0 : len - edge_len;
  char* cursor = out;
  for (size_t i = 0; i < edge_len; ++i) {
    snprintf(cursor, 3, "%02X", data[start + i]);
    cursor += 2;
  }
  *cursor = '\0';
  return String(out);
}

uint32_t DebugStats::bytes_crc32(const RNS::Bytes& bytes) {
  return crc32(bytes.data(), bytes.size());
}

String DebugStats::bytes_first4(const RNS::Bytes& bytes) {
  return edge_hex(bytes.data(), bytes.size(), true);
}

String DebugStats::bytes_last4(const RNS::Bytes& bytes) {
  return edge_hex(bytes.data(), bytes.size(), false);
}

bool DebugStats::should_log_packet(uint32_t seq) {
#if RNS_DEBUG_VERBOSE
  (void)seq;
  return true;
#else
  uint32_t now = millis();
  return seq <= 25 || (seq % 25) == 0 || (int32_t)(debug_failure_window_until - now) > 0;
#endif
}

void DebugStats::note_failure() {
  debug_failure_window_until = millis() + 2000;
}

void DebugStats::vlogf(const char* format, va_list args) {
#if RNS_DEBUG_INSTRUMENTATION
  SemaphoreHandle_t mutex = serial_mutex();
  if (mutex) {
    xSemaphoreTake(mutex, pdMS_TO_TICKS(50));
  }
  vsnprintf(debug_log_buffer, sizeof(debug_log_buffer), format, args);
  Serial.print(debug_log_buffer);
  if (mutex) {
    xSemaphoreGive(mutex);
  }
#else
  (void)format;
  (void)args;
#endif
}

void DebugStats::logf(const char* format, ...) {
  va_list args;
  va_start(args, format);
  vlogf(format, args);
  va_end(args);
}

void DebugStats::println(const char* line) {
#if RNS_DEBUG_INSTRUMENTATION
  SemaphoreHandle_t mutex = serial_mutex();
  if (mutex) {
    xSemaphoreTake(mutex, pdMS_TO_TICKS(50));
  }
  Serial.println(line ? line : "");
  if (mutex) {
    xSemaphoreGive(mutex);
  }
#else
  (void)line;
#endif
}

void DebugStats::print_mem(const char* event) {
#if RNS_DEBUG_INSTRUMENTATION
  UBaseType_t stack_words = uxTaskGetStackHighWaterMark(NULL);
  logf("RNSMEM ms=%lu board=%s role=%s event=%s task=%s free_heap=%u min_heap=%u largest_block=%u free_psram=%u min_psram=%u stack_hw_words=%u stack_hw_bytes=%u\r\n",
       (unsigned long)millis(),
       board(),
       role(),
       event ? event : "periodic",
       pcTaskGetName(NULL),
       (unsigned)heap_caps_get_free_size(MALLOC_CAP_8BIT),
       (unsigned)heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT),
       (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_8BIT),
       (unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
       (unsigned)heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
       (unsigned)stack_words,
       (unsigned)(stack_words * sizeof(StackType_t)));
#else
  (void)event;
#endif
}

void DebugStats::maybe_print_periodic() {
#if RNS_DEBUG_INSTRUMENTATION
  uint32_t now = millis();
  if (debug_next_mem_ms == 0 || (int32_t)(now - debug_next_mem_ms) >= 0) {
    debug_next_mem_ms = now + 1000;
    print_mem("periodic");
  }
#endif
}

extern "C" const char* rns_debug_board_name() {
  return DebugStats::board();
}

extern "C" const char* rns_debug_role_name() {
  return DebugStats::role();
}
