/*
 * Copyright (c) 2025, windnerd.net
 * All rights reserved.
 *
 * This source code is licensed under the BSD 3-Clause License found in the
 * LICENSE file in the root directory of this source tree.
 */

#pragma once
#include "Arduino.h"
#include "Windnerd_Rolling_Buffer.h"
#include "Windnerd_Vector_Averager.h"

// LED pins for WindNerd Core board
#define CORE_SPEED_LED_PIN PA7
#define CORE_NORTH_LED_PIN 17

// Pulse/speed input pin for WindNerd Core board
#define CORE_SPEED_INPUT_PIN 25

// I2C pins for WindNerd Core board
#define CORE_SDA_PIN 23
#define CORE_SCL_PIN 22


typedef struct
{
  float speed = 0;
  uint16_t dir = 0;
} wn_instant_wind_sample_t;

typedef struct
{
  float avg_speed = 0;
  uint16_t avg_dir = 0;
  float min_speed = 0;
  float max_speed = 0;
} wn_wind_report_t;

typedef enum
{
  UNIT_MS = 0,
  UNIT_KN,
  UNIT_KPH,
  UNIT_MPH
} wn_wind_unit_t;

typedef enum
{
  LED_PUSH_PULL = 0, // default: push-pull, active-high (LED anode-to-pin, cathode-via-resistor-to-GND)
  LED_OPEN_DRAIN     // open-drain, inverted (LED cathode-via-resistor-to-pin, anode-to-Vcc)
} wn_led_drive_t;

class WN_Core
{
public:
  // Constructor
    WN_Core(
      uint8_t speed_led_pin = CORE_SPEED_LED_PIN,
      uint8_t north_led_pin = CORE_NORTH_LED_PIN,
      uint8_t speed_input_pin = CORE_SPEED_INPUT_PIN,
      uint8_t scl_pin = CORE_SCL_PIN,
      uint8_t sda_pin = CORE_SDA_PIN
    );
  void loop(void);
  // set a callback function that will be triggered  every 3 sec for instant wind update
  void onInstantWindUpdate(void (*cb)(wn_instant_wind_sample_t instant_report));
  void triggerInstantWindCb(wn_instant_wind_sample_t &instant_report);

  // set a callback function that will be triggered every 1 minute for average wind update, the
  void onNewWindReport(void (*cb)(wn_wind_report_t report));
  void triggerAvgWindCb(wn_wind_report_t &report);

  void begin();
  bool setAveragingPeriodInSec(uint16_t period);
  bool setReportingIntervalInSec(uint16_t period);
  void setFrequencyToWindSpeedRatio(float ratio);
  void setSpeedUnit(wn_wind_unit_t unit);
  void invertVanePolarity(bool should_invert);
  void setSpeedLedDrive(wn_led_drive_t mode);
  void setNorthLedDrive(wn_led_drive_t mode);
  void enableLowPowerMode();
  void disableLowPowerMode();
  bool isLowPowerMode();
  uint8_t getI2cError();
  wn_wind_report_t computeReportForRecentPeriodInSec(uint16_t period);
  wn_wind_report_t computeReportForPeriodInSecIndexedFromLast(uint16_t period, uint16_t index);
  // Same, but force the speeds into an explicit unit regardless of setSpeedUnit().
  wn_wind_report_t computeReportForPeriodInSecIndexedFromLast(uint16_t period, uint16_t index, wn_wind_unit_t unit);
  wn_instant_wind_sample_t getSampleIndexedFromLast(uint16_t index);
  wn_instant_wind_sample_t getSampleIndexedFromLast(uint16_t index, wn_wind_unit_t unit);

private:
  float _HZ_to_ms;
  uint16_t _timeBetweenRefresh;
  uint8_t _speed_led_pin;
  uint8_t _north_led_pin;
  uint8_t _speed_input_pin;
  uint8_t _scl_pin;
  uint8_t _sda_pin;


  uint16_t _wind_average_period_sec;
  uint16_t _wind_update_period_sec;
  uint32_t ticks_cnt = 0; // ticks counter to be used as time base for periodic functions
  wn_wind_unit_t _unit_in_use = UNIT_MS;
  bool _invert_polarity = false;
  wn_led_drive_t _speed_led_drive = LED_PUSH_PULL;
  wn_led_drive_t _north_led_drive = LED_PUSH_PULL;
  uint8_t _speed_led_on_level = HIGH;  // resolved in begin() based on drive mode
  uint8_t _speed_led_off_level = LOW;
  uint8_t _north_led_on_level = HIGH;
  uint8_t _north_led_off_level = LOW;

  long last_sampling_window_millis = 0;

  WN_ROLLINGBUFFER RollingBuffer;
  WN_VECTOR_AVERAGER VaneAverager;

  void (*instantWindCb)(wn_instant_wind_sample_t instant_report) = nullptr;
  void (*avgWindCb)(wn_wind_report_t report) = nullptr;
  wn_wind_report_t formatRawReport(wn_raw_wind_report_t &raw_report, wn_wind_unit_t unit);
  wn_instant_wind_sample_t formatRawSample(wn_raw_wind_sample_t &raw_sample, wn_wind_unit_t unit);

  float pulsesToSpeed(float pulses, wn_wind_unit_t unit);
  void signalIfNorth(uint16_t angle);
};
