/**
 * @file PowerManager.h
 * @author Phillip Kühne
 * @brief This component provides utilities for keeping track of power usage
 * consumption.
 * @version 0.1
 * @date 2024-11-23
 */

#include "PowerScheduler.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc/adc_oneshot.h"
#include "freertos/semphr.h"
#ifndef PowerManager_h
#define PowerManager_h

#define TAG "Power"

enum TaskResumptionReason { POWER_AVAILABLE, TIMEOUT };

class PowerManager {

private:
  static SemaphoreHandle_t powerMutex;
  static constexpr uint16_t MUTEX_TIMEOUT_MS = 1;

  // RAII for mutex
  class PowerMutex {
  private:
    SemaphoreHandle_t &mutex;
    bool locked;

  public:
    PowerMutex(SemaphoreHandle_t &mutex) : mutex(mutex), locked(false) {
      locked =
          (xSemaphoreTake(mutex, pdMS_TO_TICKS(MUTEX_TIMEOUT_MS)) == pdTRUE);
      if (!locked) {
        ESP_LOGW(TAG, "Could not take power mutex");
      }
    }

    ~PowerMutex() {
      if (locked) {
        xSemaphoreGive(mutex);
      }
    }
    bool isLocked() const { return locked; }
  };

protected:
  /// @brief PowerScheduler instance to manage power consumption
  static PowerScheduler *powerScheduler;

  /*
   * Power State
   */

  /// @brief last time of power state update
  static TickType_t lastPowerStateUpdate;

  /// @brief remaining Charge in coulombs
  static float coloumbsRemaining;

  /// @brief remaining Charge in percent
  static float percentRemaining;

  static bool busPowered;

  static bool chargingState;

  static float fullVoltageOffset;

  /// @brief Circular array of last calculated values for current state of
  /// charge
  static float lastSOC[PowerParameters::Battery::AVERAGING_SAMPLES];
  static int latestSoCIndex;

  /// @brief Add calculated value to circular array, pushing out oldest value
  static void addSoCSample(float soc);

  /// @brief initialize the power state
  static void initPowerState(void);

public:
  static void begin(void);
  PowerManager();
  ~PowerManager();
  /// @brief Get the current free current budget (to C1 discharge)
  /// @return the amount of power that is currently available (in mA)
  static float getFreeLimitCurrentBudget(void);
  /// @brief Get the current hard maximum free current (to C2 discharge)
  /// @return the maximum amount of power that can be allocated (in mA)
  static float getFreeMaximumCurrentBudget(void);

  /// @brief Request an allowance of a certain number of milliamperes from the
  /// power scheduler without waiting for it (meaning it will not be scheduled
  /// for future allocation). Only one can be active per consumer.
  /// @param neededCurrent the amount of current we want to be accounted for
  /// (in mA)
  /// @return whether the current could be successfully allocated
  static bool
  tryAccquireCurrentAllowance(PowerParameters::PowerConsumers consumer,
                              uint16_t neededCurrent,
                              uint16_t requestedDurationMs = 0);
  /// @brief "Return" the current currently allocated to a consumer
  /// @param consumer the active consumer to release the current for
  static void releaseCurrent(PowerParameters::PowerConsumers consumer);

  /// @brief Wait for a certain amount of current to be available. This will
  /// "reseve a spot in the queue". Only one can be active per consumer.
  /// @param neededCurrent the amount of power we want to be accounted for (in
  /// mW)
  /// @param TicksToWait the amount of time to wait for the power to become
  /// available
  /// @return whether the power could be successfully allocatedy
  static bool waitForCurrentAllowance(PowerParameters::PowerConsumers consumer,
                                      uint16_t neededCurrent,
                                      uint16_t maxSlackTimeMs,
                                      uint16_t requestedDurationMs);
  /// @brief Put the ESP32 into deep sleep mode, without a method to wake up
  /// again. Basically this is a shutdown.
  static void beginPermanentDeepSleep(void);

  /// @brief Get currently granted current
  /// @return the amount of current that is currently allocated (in mA)
  static float getCurrentCurrent(void);

  /// @brief get the current theoretically flowing at the battery
  /// @return the amount of current that is currently flowing (in mA)
  static float getBatteryCurrent(void);

  /// @brief Responsible for recalculating the current budgets
  /// @note these change based on the current state of charge
  static void recalculateCurrentBudgets(void);

  // @brief Get current consumption of a consumer
  static float getConsumerCurrent(PowerParameters::PowerConsumers consumer);

  /// @brief Get battery voltage measurement.
  /// @return Battery Terminal Voltage in Volts
  static float getBatteryVoltage();

  /// @brief Get estimated battery charge state as percentage
  /// @return Battery charge state in percent
  static float getBatteryChargePercent();

  /// @brief Get estimated battery charge state as percentage based on
  // voltage directly
  /// @return Battery charge state in percent
  static float getBatteryVoltageChargePercent();

  /// @brief Get estimated battery charge state as coulombs
  /// @return Battery charge state in coulombs
  static float getBatteryChargeCoulombs();

  /// @brief get available current (after voltage conversion and efficiency
  /// losses, referencing 1C discharge)
  /// @return available current in milliamps
  static float getMax3V3Current();

  /// @brief update Power State
  /// @note needs to be public for task creation
  static void updatePowerStateHandler();

  /// @brief dump power statistics to serial
  static void dumpPowerStatistics();

  /// @brief dump consumer statistics to serial
  static void dumpConsumerStatistics();

  /// @brief get wether power is supplied via USB
  /// @return true if power is supplied via USB
  static bool isUSBPowered();

  /// @brief get wether power is supplied via battery
  /// @return true if power is supplied via battery
  static bool isBatteryPowered();

  /// @brief get wether the battery is currently charging
  /// @return true if the battery is charging
  static bool isBatteryCharging();

  /// @brief get wether the battery is currently discharging
  /// @return true if the battery is discharging
  static bool isBatteryDischarging();

  /// @brief get wether the battery is currently fully charged
  /// @return true if the battery is fully charged
  static bool isBatteryFullyCharged();

};

extern PowerManager power;

#endif // Power
