#include "Receiver.h"
#include "Config.h"
#include <string.h>
#if !OPTICAL_USE_RMT_DMA
#include <esp_cpu.h>
#include <esp32-hal-cpu.h>
#endif

uint32_t PulseReceiver::tickHz() const {
#if OPTICAL_USE_RMT_DMA
  return captureResolutionHz_;
#else
  return cpuTickHz_;
#endif
}

uint32_t PulseReceiver::plannedTickHz(uint32_t expectedHz, float expectedDutyPct) const {
#if OPTICAL_USE_RMT_DMA
  if (!expectedHz || expectedDutyPct <= 0.0f || expectedDutyPct >= 100.0f)
    return CAPTURE_RESOLUTION_OPTIONS_HZ[0];
  uint32_t dutyX100 = static_cast<uint32_t>(expectedDutyPct * 100.0f + 0.5f);
  if (dutyX100 < 5000U) dutyX100 = 10000U - dutyX100;
  for (int i = static_cast<int>(countOf(CAPTURE_RESOLUTION_OPTIONS_HZ)) - 1; i >= 0; --i) {
    const uint32_t resolution = CAPTURE_RESOLUTION_OPTIONS_HZ[i];
    const uint64_t levelTicksX100 = static_cast<uint64_t>(resolution) * dutyX100;
    const uint64_t limitX100 = static_cast<uint64_t>(expectedHz) * 10000ULL * RMT_MAX_LEVEL_TICKS;
    if (levelTicksX100 <= limitX100) return resolution;
  }
  return CAPTURE_RESOLUTION_OPTIONS_HZ[0];
#else
  (void)expectedHz; (void)expectedDutyPct;
  return cpuTickHz_;
#endif
}

bool PulseReceiver::begin() {
#if OPTICAL_USE_RMT_DMA
  queue_ = xQueueCreate(RMT_QUEUE_BLOCKS, sizeof(SymbolBlock));
  return queue_ && configureRmt(CAPTURE_RESOLUTION_OPTIONS_HZ[0]);
#else
  queue_ = xQueueCreate(256, sizeof(Edge));
  if (!queue_) return false;
  pinMode(GPIO_RX, INPUT);
  cpuTickHz_ = getCpuFrequencyMhz() * 1000000UL;
  attachInterruptArg(GPIO_RX, onGpio, this, CHANGE);
  return cpuTickHz_ != 0;
#endif
}

#if OPTICAL_USE_RMT_DMA
bool PulseReceiver::configureRmt(uint32_t resolutionHz) {
  if (channel_ && captureResolutionHz_ == resolutionHz) return true;
  stop();
  if (channel_) {
    if (rmt_del_channel(channel_) != ESP_OK) return false;
    channel_ = nullptr;
  }
  rmt_rx_channel_config_t cfg = {};
  cfg.clk_src = RMT_CLK_SRC_DEFAULT; cfg.resolution_hz = resolutionHz;
  cfg.gpio_num = static_cast<gpio_num_t>(GPIO_RX);
  cfg.flags.invert_in = RX_SIGNAL_INVERTED;
#if CONFIG_IDF_TARGET_ESP32S3
  cfg.mem_block_symbols = 512;
  cfg.flags.with_dma = true;
#else
  // C3 has 48 RMT symbols per channel and no RMT DMA. A request for 512
  // consumes all available blocks and fails with "no free rx channels".
  cfg.mem_block_symbols = RMT_MIN_RECEIVE_SYMBOLS;
  cfg.flags.with_dma = false; // C3 uses hardware RMT ping-pong partial reception
#endif
  if (rmt_new_rx_channel(&cfg, &channel_) != ESP_OK) return false;
  rmt_rx_event_callbacks_t callbacks = {}; callbacks.on_recv_done = onRmt;
  if (rmt_rx_register_event_callbacks(channel_, &callbacks, this) != ESP_OK) {
    rmt_del_channel(channel_); channel_ = nullptr; return false;
  }
  captureResolutionHz_ = resolutionHz;
  return true;
}
#endif

bool PulseReceiver::start(uint32_t expectedHz, float expectedDutyPct) {
#if OPTICAL_USE_RMT_DMA
  const uint32_t resolutionHz = plannedTickHz(expectedHz, expectedDutyPct);
  if (!configureRmt(resolutionHz)) return false;
#else
  (void)expectedHz; (void)expectedDutyPct;
#endif
  resetStream();
#if OPTICAL_USE_RMT_DMA
  // In partial RX mode the callback is delivered when this user buffer fills.
  // Keep chunks near 5 ms so low-frequency input is reported before NO SIGNAL.
  uint64_t symbols = (static_cast<uint64_t>(expectedHz) * RMT_TARGET_CHUNK_US + 999999ULL) / 1000000ULL;
  if (symbols < RMT_MIN_RECEIVE_SYMBOLS) symbols = RMT_MIN_RECEIVE_SYMBOLS;
  if (symbols > RMT_MAX_RECEIVE_SYMBOLS) symbols = RMT_MAX_RECEIVE_SYMBOLS;
  receiveChunkSymbols_ = static_cast<uint16_t>(symbols);
  if (rmt_enable(channel_) != ESP_OK) return false;
  rmt_receive_config_t cfg = {};
  cfg.signal_range_min_ns = 1000000000UL / captureResolutionHz_;
  const uint64_t maxNs = 4000000000ULL / (expectedHz ? expectedHz : 1);
  // A duration field is 15 bits. Keep the driver's end-of-signal threshold
  // strictly below that hardware limit (IDF rejects larger values).
  const uint64_t hardwareMaxNs = static_cast<uint64_t>(RMT_MAX_LEVEL_TICKS) * 1000000000ULL / captureResolutionHz_;
  cfg.signal_range_max_ns = static_cast<uint32_t>(maxNs > hardwareMaxNs ? hardwareMaxNs : maxNs);
  cfg.flags.en_partial_rx = true;
  if (rmt_receive(channel_, receiveBuffer_,
                  receiveChunkSymbols_ * sizeof(receiveBuffer_[0]), &cfg) != ESP_OK) {
    rmt_disable(channel_); return false;
  }
#endif
  running_ = true; return true;
}

void PulseReceiver::stop() {
#if OPTICAL_USE_RMT_DMA
  if (running_) rmt_disable(channel_);
#endif
  running_ = false;
}

void PulseReceiver::resetStream() {
  if (queue_) xQueueReset(queue_);
  overflow_ = false; droppedItems_ = 0; haveRise_ = haveFall_ = haveRawTick_ = false;
  lastRawTick_ = 0; tickEpoch_ = rise_ = fall_ = 0;
#if OPTICAL_USE_RMT_DMA
  block_ = {}; blockIndex_ = 0; phase_ = 0; haveLevel_ = false; level_ = false; rmtTick_ = 0;
#endif
}

bool PulseReceiver::consumeEdge(const Edge &e, PulsePeriod &out) {
  if (haveRawTick_ && e.tick < lastRawTick_ && lastRawTick_ - e.tick > 0x80000000UL)
    tickEpoch_ += 0x100000000ULL;
  haveRawTick_ = true; lastRawTick_ = e.tick;
  const uint64_t tick = tickEpoch_ + e.tick;
  if (e.rising) {
    if (!haveRise_) { rise_ = tick; haveRise_ = true; haveFall_ = false; return false; }
    if (!haveFall_) { overflow_ = true; rise_ = tick; return false; }
    const uint32_t period = static_cast<uint32_t>(tick - rise_);
    const uint32_t active = fall_ - rise_;
    out = {rise_, period, active}; rise_ = tick; haveFall_ = false;
    return true;
  }
  // Reception can begin in the middle of a HIGH pulse. In that case the first
  // observable edge is falling and there is no complete period to validate.
  // Ignore only this leading partial pulse and synchronize on the next rise.
  if (!haveRise_) return false;
  if (haveFall_) { overflow_ = true; return false; }
  fall_ = tick; haveFall_ = true; return false;
}

bool PulseReceiver::overflowed() {
  const bool value = overflow_; overflow_ = false; return value;
}

uint32_t PulseReceiver::takeDroppedItems() {
  return __atomic_exchange_n(&droppedItems_, 0, __ATOMIC_RELAXED);
}

#if OPTICAL_USE_RMT_DMA
bool IRAM_ATTR PulseReceiver::onRmt(rmt_channel_handle_t, const rmt_rx_done_event_data_t *data, void *ctx) {
  PulseReceiver *self = static_cast<PulseReceiver *>(ctx);
  BaseType_t wake = pdFALSE;
  size_t offset = 0;
  while (offset < data->num_symbols) {
    SymbolBlock &b = self->isrBlock_;
    b.count = static_cast<uint16_t>((data->num_symbols - offset) > BLOCK_SYMBOLS ?
      BLOCK_SYMBOLS : (data->num_symbols - offset));
    memcpy(b.symbols, data->received_symbols + offset, b.count * sizeof(rmt_symbol_word_t));
    if (xQueueSendFromISR(self->queue_, &b, &wake) != pdTRUE)
      __atomic_fetch_add(&self->droppedItems_, b.count, __ATOMIC_RELAXED);
    offset += b.count;
  }
  return wake == pdTRUE;
}

bool PulseReceiver::nextRmtEdge(Edge &edge, TickType_t waitTicks) {
  for (;;) {
    if (blockIndex_ >= block_.count) {
      if (xQueueReceive(queue_, &block_, waitTicks) != pdTRUE) return false;
      blockIndex_ = 0; phase_ = 0;
      waitTicks = 0;
    }
    const rmt_symbol_word_t &s = block_.symbols[blockIndex_];
    const bool nextLevel = phase_ == 0 ? s.level0 : s.level1;
    const uint32_t duration = phase_ == 0 ? s.duration0 : s.duration1;
    phase_ ^= 1;
    if (phase_ == 0) ++blockIndex_;
    if (!duration) continue;
    if (!haveLevel_) { haveLevel_ = true; level_ = nextLevel; rmtTick_ += duration; continue; }
    if (nextLevel != level_) {
      level_ = nextLevel; edge = {rmtTick_, static_cast<uint8_t>(nextLevel)};
      rmtTick_ += duration; return true;
    }
    rmtTick_ += duration;
  }
}

size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks) {
  size_t count = 0;
  Edge e;
  while (count < capacity && nextRmtEdge(e, count ? 0 : waitTicks))
    if (consumeEdge(e, periods[count])) ++count;
  return count;
}
#else
void IRAM_ATTR PulseReceiver::onGpio(void *ctx) {
  PulseReceiver *self = static_cast<PulseReceiver *>(ctx);
  bool level = gpio_get_level(static_cast<gpio_num_t>(GPIO_RX));
  if (RX_SIGNAL_INVERTED) level = !level;
  Edge e = {esp_cpu_get_cycle_count(), static_cast<uint8_t>(level)};
  BaseType_t wake = pdFALSE;
  if (xQueueSendFromISR(self->queue_, &e, &wake) != pdTRUE)
    __atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED);
  if (wake) portYIELD_FROM_ISR();
}

size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks) {
  size_t count = 0;
  Edge e;
  while (count < capacity && xQueueReceive(queue_, &e, count ? 0 : waitTicks) == pdTRUE)
    if (consumeEdge(e, periods[count])) ++count;
  return count;
}
#endif
