#include "Measurement.h"
#include "Config.h"
#include <string.h>

bool Measurement::start(float hz, float duty, float tolerance, uint32_t timeMs,
                        uint16_t averagingPeriods, uint8_t settleCycles,
                        bool activeRxLightOn) {
  if (!task_ && xTaskCreate(taskEntry, "optical-rx", 4096, this, 4, &task_) != pdPASS) return false;
  expectedHz_ = static_cast<uint32_t>(hz + 0.5f);
  expectedDutyPct_ = duty;
  tolerance = effectiveTolerancePct(tolerance);
  if (!expectedHz_ || !timeMs || !averagingPeriods ||
      !receiver_.start(expectedHz_, expectedDutyPct_, activeRxLightOn)) return false;
  settleCycles_ = settleCycles; settleLeft_ = settleCycles;
  tolerancePct_ = tolerance;
  stepTimeMs_ = (timeMs + MEASUREMENT_PROGRESS_STEPS - 1U) / MEASUREMENT_PROGRESS_STEPS;
  stepTicks_ = static_cast<uint64_t>(receiver_.tickHz()) * timeMs /
    (1000ULL * MEASUREMENT_PROGRESS_STEPS);
  if (!stepTicks_) stepTicks_ = 1;
  currentStep_ = 0;
  __atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
  stats_.reset();
  publishStats();
  measurementStartTick_ = deadlineTick_ = 0; startedMs_ = millis();
  measurementStartMs_ = lastPeriodMs_ = 0;
  expectedPeriodMs_ = static_cast<uint32_t>((1000ULL + expectedHz_ - 1U) / expectedHz_);
  if (!expectedPeriodMs_) expectedPeriodMs_ = 1;
  state_ = MeasureState::SETTLING;
  xTaskNotifyGive(task_);
  return true;
}

void Measurement::taskEntry(void *context) {
  static_cast<Measurement *>(context)->taskLoop();
}

void Measurement::taskLoop() {
  for (;;) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    while (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING) processOnce();
  }
}

void Measurement::fail(FailReason reason) {
  if (stats_.reason == FailReason::NONE) stats_.reason = reason;
  __atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
  publishStats();
  // App stops PWM first and only then disables capture. Disabling MCPWM from
  // this RX task while input edges are still arriving can race its ISR.
  state_ = MeasureState::FAIL;
}

void Measurement::completeMeasurement() {
  const uint32_t dropped = receiver_.takeDroppedItems();
  stats_.droppedItems += dropped;
  if (dropped) { fail(FailReason::DATA_LOSS); return; }
  publishStats();
  if (++currentStep_ < MEASUREMENT_PROGRESS_STEPS) {
    // Capture and validation continue while the main task draws OLED. Pausing
    // here would overflow the edge queue at higher PWM frequencies; stopping
    // MCPWM Capture can race an edge ISR. Publish a snapshot, then advance the
    // edge-based window without interrupting the RX pipeline.
    __atomic_store_n(&progressUpdatePending_, true, __ATOMIC_RELEASE);
    measurementStartTick_ = deadlineTick_;
    deadlineTick_ += stepTicks_;
    measurementStartMs_ = lastPeriodMs_ = millis();
    return;
  }
  if (!stats_.periods) {
    fail(FailReason::DATA_LOSS); return;
  }
  __atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE);
  state_ = MeasureState::PASS;
}

void Measurement::publishStats() {
  portENTER_CRITICAL(&statsMux_);
  publishedStats_ = stats_;
  portEXIT_CRITICAL(&statsMux_);
}

bool Measurement::statsSnapshot(StageStats &out) const {
  portENTER_CRITICAL(&statsMux_);
  out = publishedStats_;
  portEXIT_CRITICAL(&statsMux_);
  return out.periods && out.periodSum;
}

MeasureState Measurement::processOnce() {
  if (state_ != MeasureState::SETTLING && state_ != MeasureState::RUNNING) return state_;
  bool receivedPeriod = false;
  for (;;) {
    const size_t periodCount = receiver_.readPeriods(periodBatch_, PERIOD_BATCH_SIZE, pdMS_TO_TICKS(2));
    const uint32_t dropped = receiver_.takeDroppedItems();
    stats_.droppedItems += dropped;
    if (dropped) { fail(FailReason::DATA_LOSS); return state_; }
    if (!periodCount) break;
    receivedPeriod = true;
    for (size_t periodIndex = 0; periodIndex < periodCount; ++periodIndex) {
      if (state_ != MeasureState::SETTLING && state_ != MeasureState::RUNNING) return state_;
      const PulsePeriod &period = periodBatch_[periodIndex];
      if (state_ == MeasureState::SETTLING) {
        if (settleLeft_) --settleLeft_;
        if (!settleLeft_) {
          measurementStartTick_ = period.startTick + period.periodTicks;
          deadlineTick_ = measurementStartTick_ + stepTicks_;
          measurementStartMs_ = lastPeriodMs_ = millis(); state_ = MeasureState::RUNNING;
        }
        continue;
      }
      if (period.startTick < measurementStartTick_) continue; // leading incomplete period
      while (period.startTick >= deadlineTick_) {
        // Progress boundaries never discard a pulse. A complete period is
        // assigned by its start edge, then validated exactly once. The nine
        // intermediate boundaries only publish UI snapshots.
        completeMeasurement();
        if (state_ != MeasureState::RUNNING) return state_;
      }
      if (!period.periodTicks || !period.activeTicks || !period.activeTickHz) {
        fail(FailReason::EXTRA_EDGE); return state_;
      }

      const FailReason result = evaluatePeriod(period, receiver_.tickHz(),
        expectedHz_, expectedDutyPct_, tolerancePct_, currentStep_ + 1U, stats_);
      if (result != FailReason::NONE) { fail(result); return state_; }
    }
  }
  if (receivedPeriod && state_ == MeasureState::RUNNING) lastPeriodMs_ = millis();
  const uint64_t edgeBasedTimeout =
    static_cast<uint64_t>(PWM_SETTLE_CYCLES + NO_SIGNAL_TIMEOUT_PERIODS) * expectedPeriodMs_ + 20;
  const uint64_t settleTimeout = edgeBasedTimeout;
  if (state_ == MeasureState::SETTLING && millis() - startedMs_ > settleTimeout) fail(FailReason::NO_SIGNAL);
  if (state_ == MeasureState::RUNNING && measurementStartTick_) {
    const uint32_t now = millis();
    const uint32_t edgeTimeoutMs = expectedPeriodMs_ * NO_SIGNAL_TIMEOUT_PERIODS + 2U;
    if (now - measurementStartMs_ < stepTimeMs_ && now - lastPeriodMs_ > edgeTimeoutMs) {
      fail(FailReason::LOST_EDGE); return state_;
    }
    if (now - measurementStartMs_ > stepTimeMs_ + expectedPeriodMs_ + 2) completeMeasurement();
  }
  return state_;
}

MeasureState Measurement::update() { return state_; }

bool Measurement::takeProgressUpdate() {
  return __atomic_exchange_n(&progressUpdatePending_, false, __ATOMIC_ACQ_REL);
}

void Measurement::abort() {
  if (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING)
    fail(FailReason::ABORTED);
}

void Measurement::forceFail(FailReason reason) {
  if (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING)
    fail(reason);
}
