#pragma once
#include "Buttons.h"
#include "Display.h"
#include "DriverTest.h"
#include "Measurement.h"
#include "OpticalCurrent.h"
#include "Pwm.h"
#include "Radio.h"
#include "SettingsStore.h"

enum class AppState : uint8_t {
  IDLE, MENU, BOARD_TEST, SOLO_MEASURE, SOLO_DRIVER, MASTER_DISCOVER, MASTER_WAIT_READY,
  MASTER_WAIT_RESULT, MASTER_FINALIZE, SLAVE_READY, SLAVE_WAIT_START, SLAVE_MEASURE,
  SLAVE_WAIT_ACK, FINISHED
};

class App {
 public:
  App();
  void begin();
  void update();
 private:
  void showIdle();
  void finishInitialization(bool factoryReset);
  void showMenu();
  void changeMenu(int direction);
  void enterOpticalCalibration(uint32_t now);
  void updateOpticalCalibration(uint32_t now, bool force = false);
  void leaveOpticalCalibration();
  void cycleRunMode();
  void sanitizeRange();
  void startTest();
  void startBoardTest();
  void updateBoardTest(uint32_t now);
  void stopBoardTest();
  bool armSlave(bool preserveDisplay = false);
  bool prepareStage(bool showProgress = true);
  bool startLocalMeasurement(float hz, float duty);
  bool startDriverMeasurement();
  void startMasterDiscovery();
  void handleRadio();
  void updateMaster();
  void updateSlave();
  void stagePassed();
  void finish(bool pass, FailReason reason, bool preserveDisplay = false);
  void abortTest();
  void sendAbort(FailReason reason = FailReason::ABORTED);
  void printConfiguration();
  void printStageStats(const StageStats &s, uint32_t hz);
  void showStageResult(const StageStats &s);
  void showDriverResult(const DriverStats &s, bool testPassed = false);
  void showRemoteResult(const ProtocolPacket &packet);
  void fillMeasuredResult(ProtocolPacket &packet, const StageStats &stats) const;
  void showStageProgress();
  uint64_t actualNominalTotalUs();
  ProtocolPacket makePacket(MessageType type) const;
  bool sendLinked(ProtocolPacket packet);
  void sendCurrent(MessageType type);
  void updateHeartbeat();
  bool packetForCurrent(const ProtocolPacket &p) const;
  void serviceIdlePowerSave();
  void serviceSerialConsole();
  void handleSerialCommand(char *line);
  void printSerialHelp();
  void printSerialStatus();
  bool serialSettingsMutable() const;
  void finishSerialSettingsChange();
  void leaveIdlePowerSave(bool wakeDisplay = true);
  bool idlePowerSaveAllowed() const;
  bool usbHostPresent() const;
  void setStandbyOpticalOutput();
  void setActivePerformance(bool active);

  Button startButton_, modeButton_;
  Display display_;
  SettingsStore store_;
  Settings settings_ = {};
  TestParams params_ = {};
  PwmGenerator pwm_;
  PulseReceiver receiver_;
  Measurement measurement_;
  DriverTest driverTest_;
  Radio radio_;
  AppState state_ = AppState::IDLE;
  uint8_t menuItem_ = 0;
  uint32_t stageIndex_ = 0, stageCount_ = 0;
  uint32_t requestedHz_ = 0, requestedPulseNs_ = 0;
  ActualPwm actual_ = {};
  FailReason pendingReason_ = FailReason::NONE;
  uint32_t session_ = 0;
  uint16_t sequence_ = 0;
  uint8_t peer_[6] = {};
  bool havePeer_ = false;
  uint32_t deadlineMs_ = 0, lastSendMs_ = 0;
  uint32_t localMeasurementDeadlineMs_ = 0;
  uint32_t lastHeartbeatMs_ = 0, lastPeerSeenMs_ = 0;
  uint8_t retries_ = 0;
  ProtocolPacket pendingPacket_ = {};
  bool initialized_ = false, bootResetCandidate_ = false;
  uint32_t bootCheckStartedMs_ = 0;
  uint32_t slaveRearmAtMs_ = 0;
  bool stageStartConfirmed_ = false;
  uint32_t lastUserActivityMs_ = 0;
  bool idlePowerSave_ = false;
  bool idleSleepRadioStopped_ = false;
  uint32_t lastOpticalWakeToggleMs_ = 0;
  bool opticalWakeActive_ = false;
  bool rxPinStateKnown_ = false, rxPinState_ = false;
  uint32_t boardTestUpdatedMs_ = 0;
  uint32_t boardDisplayUpdatedMs_ = 0;
  bool boardPwmLightOn_ = false;
  bool opticalCalibrationActive_ = false;
  uint32_t opticalCalibrationStartedMs_ = 0;
  uint32_t opticalCalibrationUpdatedMs_ = 0;
  mutable uint32_t lastUsbHostSeenMs_ = 0;
  char serialLine_[96] = {};
  uint8_t serialLineLength_ = 0;
  bool serialLineOverflow_ = false;
};
