#pragma once
#include <system_time.hpp>
#include <string>

/** Energy Monitor Device Abstract Interface
 *
 * This interface represents an energy monitor attached to a single power rail.
 *
 * ## Blocking vs Non-Blocking
 *
 * This API is designed around an asynchronous request/get API pairing. You can request the
 * appropriate measure, and then wait for a corresponding event to be fired. The reading of the
 * latest value woudl then need to occur in an event handler.
 *
 * I2C may use a blocking approach in this system, and the energy monitor is likely to be attached
 * to that bus.
 *
 * In this case, there will be no intermediate event fired by the implementation. Instead, you can
 * call getCurrent(), getVoltage() immediately after calling requestCurrent(), requestVoltage().
 *
 * We recommend having an assertion on the value of isBlocking() to make sure that your code is
 * implemented in the appropriate style.
 */
class EnergyMonitor
{
  public:
	EnergyMonitor(std::string id) : id_(id)
	{
	}

	enum class energy_units
	{
		A,
		V,
		W
	};

	/// Contains an energy measurement.
	/// Can represent power, current, or voltage.
	/// To be sure what type of measurement this is, check the
	/// energy_units field.
	///
	/// To ensure a value is appropriately set, you must create a sample
	/// with a specific unit. For example:
	/// @code
	/// energy_sample_t sample(energy_units::mA);
	/// @endcode
	struct energy_sample_t
	{
		uint64_t captureTimestamp;
		float reading;
		energy_units units;

		energy_sample_t(energy_units u) : units(u)
		{
		}
	};

  protected:
	virtual ~EnergyMonitor() = default;

  public:
	/// Perform all actions required to initialize the energy monitor device and put it in an
	/// operational state.
	/// @pre Device is stopped
	virtual void start() = 0;

	/// Perform all actions required to stop the energy monitor device.
	/// @pre Device is started
	virtual void stop() = 0;

	/// Request a new current measurement.
	/// In a non-blocking implementation, this will kick off a transaction and fire an event when
	/// new data is received.
	/// In a blocking implementation, this will return once the transaction is completed.
	///
	/// The implementation should update the latest_current_ variable when the measurement is
	/// available. Users can get the latest value with getCurrent().
	///
	/// @returns true if request was successful, false otherwise
	virtual bool requestCurrent() = 0;

	/// Request a new voltage measurement.
	/// In a non-blocking implementation, this will kick off a transaction and fire an event when
	/// new data is received.
	/// In a blocking implementation, this will return once the transaction is completed.
	///
	/// The implementation should update the latest_voltage_ variable when the measurement is
	/// available. Users can get the latest value with getVoltage().
	///
	/// @returns true if request was successful, false otherwise
	virtual bool requestVoltage() = 0;

	/// @returns true if the underlying implementation of requestCurrent/Voltage/Power() is
	/// blocking. This way, you will know whether you can call getCurrent/Voltage/Power()
	/// immediately, or need to handle an event.
	virtual bool isBlocking() = 0;

	energy_sample_t getCurrent()
	{
		return latest_current_;
	}

	energy_sample_t getVoltage()
	{
		return latest_voltage_;
	}

	std::string_view ID()
	{
		return id_;
	}

  protected:
	const std::string id_;
	energy_sample_t latest_current_{energy_units::A};
	energy_sample_t latest_voltage_{energy_units::V};
};
