#pragma once

#include <Arduino.h>

#include "Receiver.h"

enum class DriverState : uint8_t {
  IDLE, SETTLING, RUNNING, SUBSAMPLE_DONE, PASS, FAIL
};

struct DriverEdgeStats {
  uint32_t responses;
  uint32_t minDelayTicks;
  uint32_t maxDelayTicks;
  uint64_t delaySumTicks;
  uint32_t minResponseTicks;
  uint32_t maxResponseTicks;
  uint64_t responseSumTicks;
  void reset();
};

struct DriverStats {
  uint32_t inputEdges;
  uint32_t responses;
  uint32_t minDelayTicks;
  uint32_t maxDelayTicks;
  uint32_t minResponseTicks;
  uint32_t maxResponseTicks;
  uint32_t lastDelayTicks;
  uint32_t lastResponseTicks;
  uint32_t droppedItems;
  uint32_t unexpectedResponses;
  uint64_t errorElapsedTicks;
  uint32_t errorTriggerTicks;
  uint32_t errorDelayTicks;
  uint32_t errorPulseTicks;
  bool errorTriggerValid;
  bool errorDelayValid;
  bool errorPulseValid;
  DriverEdgeStats turnOn;
  DriverEdgeStats turnOff;
  FailReason reason;
  void reset();
};

class DriverTest {
 public:
  explicit DriverTest(PulseReceiver &receiver) : receiver_(receiver) {}
  bool start(uint32_t frequencyHz, uint32_t pulseNs, float tolerancePct,
             uint32_t testTimeMs, uint8_t settleCycles,
             bool activeTxLightOn, bool activeRxLightOn);
  DriverState update() const { return state_; }
  void abort();
  void forceFail(FailReason reason);
  bool resumeSubsample();
  bool takeProgressUpdate();
  void printSummary() const;
  void printTrace() const;
  uint8_t progressStep() const { return currentStep_; }
  uint8_t progressSteps() const { return subsampleCount_; }
  uint32_t tickHz() const { return captureHz_; }
  const DriverStats &stats() const { return publishedStats_; }

 private:
  enum class Source : uint8_t { TX, RX, WINDOW_END };
  struct RawEvent { uint32_t tick; bool rising; Source source; };
  struct TimedEvent { uint64_t tick; bool rising; Source source; };
  struct PendingTx {
    uint64_t tick;
    bool lightOn;
  };
  struct Response {
    bool active;
    bool associated;
    uint64_t startTick;
    PendingTx tx;
  };
  struct TraceEvent {
    uint64_t tick;
    uint8_t source;
    uint8_t rising;
    uint8_t state;
    uint8_t pending;
  };

  static void analyzerTaskEntry(void *context);
  static void pollTaskEntry(void *context);
  void analyzerTaskLoop();
  void IRAM_ATTR pollTaskLoop();
  void processEvent(const TimedEvent &event);
  void processSettling(const TimedEvent &event);
  void processRunning(const TimedEvent &event);
  void processTx(const TimedEvent &event, bool lightOn);
  void processRx(const TimedEvent &event, bool activeNow);
  void expirePending(uint64_t now);
  void completeIfPossible(uint64_t now);
  bool addPending(uint64_t tick, bool lightOn);
  int8_t matchingPending(uint64_t rxTick) const;
  void removePending(uint8_t index);
  uint64_t mergeGuardTicks() const;
  void acceptAcknowledgement(uint64_t delay, uint64_t width, bool lightOn);
  void fail(FailReason reason, uint64_t tick = 0, uint64_t delay = 0,
            uint64_t pulseWidth = 0, uint64_t triggerAfterTx = 0);
  void publishStats();
  void rememberTrace(const TimedEvent &event);
  bool armCapture();
  void requestCaptureStop();
  bool waitCaptureStopped(uint32_t timeoutMs);
  void clearCapture();
  void recordRaw(uint32_t tick, bool rising, Source source);
  size_t readRaw(TimedEvent *events, size_t capacity, TickType_t waitTicks);
  uint32_t takeDropped();
  uint64_t nsToTicks(uint32_t ns) const;
  uint64_t ticksToNs(uint64_t ticks) const;

  static constexpr uint8_t MAX_PENDING = 8;
  static constexpr uint16_t RING_CAPACITY = 2048;
  static constexpr uint8_t TRACE_CAPACITY = 32;
  static_assert((RING_CAPACITY & (RING_CAPACITY - 1U)) == 0,
    "driver ring capacity must be a power of two");

  PulseReceiver &receiver_;
  TaskHandle_t analyzerTask_ = nullptr;
  TaskHandle_t pollTask_ = nullptr;
  volatile DriverState state_ = DriverState::IDLE;
  DriverStats stats_ = {};
  DriverStats publishedStats_ = {};
  mutable portMUX_TYPE statsMux_ = portMUX_INITIALIZER_UNLOCKED;

  RawEvent ring_[RING_CAPACITY] = {};
  volatile uint16_t ringWrite_ = 0;
  volatile uint16_t ringRead_ = 0;
  volatile uint32_t droppedItems_ = 0;
  volatile bool captureActive_ = false;
  volatile bool captureReady_ = false;
  volatile bool core0WdtDisabled_ = false;
  uint32_t captureHz_ = 0;
  uint32_t captureFrequencyHz_ = 0;
  uint32_t capturePulseNs_ = 0;
  uint32_t pollPeriodCycles_ = 0;
  uint32_t pollWindowBeforeCycles_ = 0;
  uint32_t pollWindowAfterCycles_ = 0;
  bool pollTxStartRawHigh_ = false;
  bool captureTxLightOn_ = true;
  portMUX_TYPE pollMux_ = portMUX_INITIALIZER_UNLOCKED;

  PendingTx pending_[MAX_PENDING] = {};
  uint8_t pendingCount_ = 0;
  Response response_ = {};
  uint64_t ackStartMaxTicks_ = 0;
  uint64_t faultLongTicks_ = 0;
  uint64_t stuckTicks_ = 0;
  uint64_t testTicks_ = 0;
  uint64_t subsampleTicks_ = 0;
  uint64_t settlingTimeoutUs_ = 0;
  uint64_t settlingDeadlineUs_ = 0;
  uint64_t measurementStartTick_ = 0;
  uint64_t deadlineTick_ = 0;
  uint64_t pointOriginTick_ = 0;
  uint64_t lastEventTick_ = 0;
  uint64_t lastActiveTxTick_ = 0;
  uint8_t settleCycles_ = 0;
  uint8_t settledCycles_ = 0;
  uint8_t subsampleCount_ = DRIVER_SHORT_SAMPLE_PROGRESS_STEPS;
  uint8_t completedSubsamples_ = 0;
  bool rxActiveRawHigh_ = true;
  bool txPulseLightOn_ = true;
  bool rxActive_ = false;
  bool measurementClosed_ = false;
  bool havePointOrigin_ = false;
  bool haveLastActiveTx_ = false;
  bool haveRawTick_ = false;
  uint32_t lastRawTick_ = 0;
  uint64_t tickEpoch_ = 0;
  volatile uint8_t currentStep_ = 0;
  volatile bool progressUpdatePending_ = false;
  TraceEvent trace_[TRACE_CAPACITY] = {};
  uint8_t traceWrite_ = 0;
  uint8_t traceCount_ = 0;
};
