#pragma once

#include <stdint.h>
#include <stddef.h>

enum class Role : uint8_t { SOLO, MASTER, SLAVE };
enum class TestGroup : uint8_t { OPTICS, BOARD };
enum class TestKind : uint8_t { OPTICAL, DRIVER };
enum class BoardTest : uint8_t { ADC, PWM_OUTPUT, RX_INPUT };
enum class LightCode : uint8_t { HH, HL, LH, LL };
enum class FailReason : uint8_t {
  NONE, NO_SIGNAL, PERIOD_OUT, DUTY_OUT, EXTRA_EDGE, GLITCH, LOST_EDGE,
  DATA_LOSS, LINK_LOST, UNSUPPORTED, RESOLUTION, ABORTED,
  ACK_MISSING, ACK_TIMING, DRIVER_FAULT, ACK_MERGED
};

const char *roleName(Role role);
const char *testGroupName(TestGroup group);
const char *testKindName(TestKind kind);
const char *boardTestName(BoardTest test);
const char *lightCodeName(LightCode code);
const char *failName(FailReason reason);

struct Settings {
  uint16_t version;
  uint8_t role;
  uint8_t testKind;
  uint8_t lightCode;
  uint8_t frequencyIndex;
  uint8_t maxPulseIndex;
  uint8_t minPulseIndex;
  uint8_t accuracyIndex;
  uint8_t timeIndex;
  uint8_t testGroup;
  uint8_t boardTest;
  uint32_t checksum;
};

struct TestParams {
  uint32_t frequencyHz;
  uint32_t maxPulseNs;
  uint32_t minPulseNs;
  float accuracyPct;
  uint32_t testTimeMs;
};

struct PulsePeriod {
  uint64_t startTick;
  uint32_t periodTicks;
  uint32_t activeTicks;
  uint32_t activeTickHz;
};

struct StageStats {
  uint32_t periods;
  uint64_t periodSum;
  uint64_t activeSum;
  uint32_t minPeriod;
  uint32_t maxPeriod;
  uint32_t minActive;
  uint32_t maxActive;
  uint32_t firstBadPeriod;
  uint8_t firstBadRepeat;
  float badFrequency;
  float badDuty;
  uint32_t droppedItems;
  FailReason reason;
  void reset();
};

struct PeriodLimits {
  uint32_t minPeriodTicks;
  uint32_t maxPeriodTicks;
  uint32_t minDutyX100;
  uint32_t maxDutyX100;
};

struct IntegerPwmConfig {
  uint32_t actualHz;
  uint32_t dividerRaw;
  uint32_t dutyCount;
  uint32_t actualPulseNs;
  uint8_t bits;
};

uint32_t settingsChecksum(const Settings &s);
bool txActiveLightOn(const Settings &s);
bool rxActiveLightOn(const Settings &s);
uint32_t pulseWidthPointCount(uint32_t maxPulseNs, uint32_t minPulseNs);
uint32_t pulseWidthAt(uint32_t maxPulseNs, uint32_t minPulseNs, uint32_t index);
uint64_t nominalStageUs(uint32_t frequencyHz, uint32_t sampleTimeMs, uint32_t settleCycles);
uint64_t nominalTotalUs(const TestParams &p, uint32_t settleCycles);
bool periodWithin(float measuredHz, float expectedHz, float tolerancePct);
bool dutyWithin(float measuredPct, float expectedPct, float tolerancePct);
float effectiveTolerancePct(float configuredPct);
uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz,
                            uint8_t maxBits);
uint8_t chooseStablePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz,
                                  uint8_t maxBits, uint8_t dutyPct);
bool choosePwmConfig(uint32_t requestedHz, uint32_t requestedPulseNs,
                            uint32_t sourceClockHz, uint8_t maxBits,
                            IntegerPwmConfig &config);
FailReason validateResolution(uint32_t frequencyHz, float dutyPct, float accuracyPct,
                              uint32_t periodResolutionHz, uint32_t pulseResolutionHz,
                              uint8_t pwmBits,
                              uint16_t averagingPeriods);
FailReason evaluatePeriod(const PulsePeriod &period, uint32_t tickHz, float expectedHz,
                          float expectedDuty, float tolerancePct, uint8_t repeat,
                          StageStats &stats);
bool makePeriodLimits(uint32_t expectedHz, float expectedDuty, float tolerancePct,
                      uint32_t tickHz, PeriodLimits &limits);
FailReason evaluatePeriodFast(const PulsePeriod &period, uint32_t tickHz,
                              const PeriodLimits &limits, uint8_t repeat,
                              StageStats &stats);
FailReason evaluatePeriodWindow(uint64_t periodSum, uint64_t activeSum,
                                uint32_t periodCount, uint32_t tickHz,
                                float expectedHz, float expectedDuty,
                                float tolerancePct,
                                uint32_t minPeriod, uint32_t maxPeriod,
                                uint8_t repeat,
                                StageStats &stats);
