#pragma once

#include <Arduino.h>
#include <math.h>

#include "config.h"
#include "ms5611_crc.h"

//
//  One validated MS5611 measurement channel.
//
//  The class owns everything that is identical for both sensors - reset and
//  PROM/CRC handshake, per-poll validation, error accounting and the 1 second
//  error LED pulse. Only the five driver calls at the bottom are virtual, and
//  they are implemented once for the I2C part and once for the SPI part.
//

enum class BaroStatus : uint8_t
{
  NotInitialised = 0,
  Ok,
  InitFailed,     //  reset/handshake did not complete
  PromCrcError,   //  factory calibration failed its CRC-4 or is implausible
  ReadError,      //  the driver reported a bus/ADC failure
  OutOfRange,     //  values outside the MS5611 operating envelope
  Stale,          //  sensor answers but has stopped producing new samples
};

//  Short, log-friendly name for a status - never returns NULL.
const char *baroStatusName(BaroStatus status);


class BaroChannel
{
public:
  BaroChannel(const char *name, uint8_t ledPin)
    : _name(name), _ledPin(ledPin) {}

  //  Configures the LED pin and makes the first initialisation attempt.
  void begin(uint32_t now);

  //  One acquisition cycle: read, validate, update status and LED.
  //  Blocks for roughly 2x the ADC conversion time of the configured OSR.
  void poll(uint32_t now);

  //  Releases the error LED once its hold time has elapsed. Cheap, call often.
  void updateLed(uint32_t now);

  const char *name() const        { return _name; }
  BaroStatus  status() const      { return _status; }
  bool        isOk() const        { return _status == BaroStatus::Ok; }
  float       pressure() const    { return _pressure; }     //  mbar
  float       temperature() const { return _temperature; }  //  degrees C
  uint32_t    errorCount() const  { return _errorCount; }

protected:
  //  Not deleted through this type - keeps the vtable free of a destructor
  //  slot and avoids dragging in operator delete.
  ~BaroChannel() = default;

  //  Reset the part and apply the configured oversampling.
  virtual bool  driverBegin() = 0;
  //  Fill prom[0..7] with the factory calibration words.
  virtual void  driverReadProm(uint16_t *prom) = 0;
  //  True when the driver reported MS5611_READ_OK.
  virtual bool  driverRead() = 0;
  virtual float driverPressure() = 0;
  virtual float driverTemperature() = 0;

private:
  bool tryInit(uint32_t now);
  void fail(BaroStatus status, uint32_t now);

  const char *_name;
  uint8_t     _ledPin;

  BaroStatus  _status          = BaroStatus::NotInitialised;
  bool        _initialised     = false;
  uint32_t    _lastInitAttempt = 0;

  float       _pressure        = NAN;
  float       _temperature     = NAN;

  uint8_t     _repeatCount     = 0;
  uint8_t     _errorStreak     = 0;
  uint32_t    _errorCount      = 0;

  bool        _ledOn           = false;
  uint32_t    _ledOffAt        = 0;
};


//
//  The two concrete channels are built in their own translation units on
//  purpose: MS5611.h and MS5611_SPI.h each define `enum osr_t` and their own
//  MS5611_READ_OK, so including both in one file does not compile. These
//  accessors hand out the instances without leaking either driver header.
//
BaroChannel &baroI2cChannel();
BaroChannel &baroSpiChannel();
