#pragma once

#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/automation.h"

#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif

namespace esphome::msa3xx {

// Combined register map of MSA301 and MSA311
// Differences
//  What             |  MSA301 | MSA11  |
//  - Resolution     |  14-bit | 12-bit |
//

// I2c address
enum class Model : uint8_t {
  MSA301 = 0x26,
  MSA311 = 0x62,
};

// Combined MSA301 and MSA311 register map
enum class RegisterMap : uint8_t {
  SOFT_RESET = 0x00,
  PART_ID = 0x01,
  ACC_X_LSB = 0x02,
  ACC_X_MSB = 0x03,
  ACC_Y_LSB = 0x04,
  ACC_Y_MSB = 0x05,
  ACC_Z_LSB = 0x06,
  ACC_Z_MSB = 0x07,
  MOTION_INTERRUPT = 0x09,
  DATA_INTERRUPT = 0x0A,
  TAP_ACTIVE_STATUS = 0x0B,
  ORIENTATION_STATUS = 0x0C,
  RESOLUTION_RANGE_CONFIG = 0x0D,
  RANGE_RESOLUTION = 0x0F,
  ODR = 0x10,
  POWER_MODE_BANDWIDTH = 0x11,
  SWAP_POLARITY = 0x12,
  INT_SET_0 = 0x16,
  INT_SET_1 = 0x17,
  INT_MAP_0 = 0x19,
  INT_MAP_1 = 0x1A,
  INT_CONFIG = 0x20,
  INT_LATCH = 0x21,
  FREEFALL_DURATION = 0x22,
  FREEFALL_THRESHOLD = 0x23,
  FREEFALL_HYSTERESIS = 0x24,
  ACTIVE_DURATION = 0x27,
  ACTIVE_THRESHOLD = 0x28,
  TAP_DURATION = 0x2A,
  TAP_THRESHOLD = 0x2B,
  ORIENTATION_CONFIG = 0x2C,
  Z_BLOCK = 0x2D,
  OFFSET_COMP_X = 0x38,
  OFFSET_COMP_Y = 0x39,
  OFFSET_COMP_Z = 0x3A,
};

enum class Range : uint8_t {
  RANGE_2G = 0b00,
  RANGE_4G = 0b01,
  RANGE_8G = 0b10,
  RANGE_16G = 0b11,
};

enum class Resolution : uint8_t {
  RES_14BIT = 0b00,
  RES_12BIT = 0b01,
  RES_10BIT = 0b10,
  RES_8BIT = 0b11,
};

enum class PowerMode : uint8_t {
  NORMAL = 0b00,
  LOW_POWER = 0b01,
  SUSPEND = 0b11,
};

enum class Bandwidth : uint8_t {
  BW_1_95HZ = 0b0000,
  BW_3_9HZ = 0b0011,
  BW_7_81HZ = 0b0100,
  BW_15_63HZ = 0b0101,
  BW_31_25HZ = 0b0110,
  BW_62_5HZ = 0b0111,
  BW_125HZ = 0b1000,
  BW_250HZ = 0b1001,
  BW_500HZ = 0b1010,
};

enum class DataRate : uint8_t {
  ODR_1HZ = 0b0000,     // not available in normal mode
  ODR_1_95HZ = 0b0001,  // not available in normal mode
  ODR_3_9HZ = 0b0010,
  ODR_7_81HZ = 0b0011,
  ODR_15_63HZ = 0b0100,
  ODR_31_25HZ = 0b0101,
  ODR_62_5HZ = 0b0110,
  ODR_125HZ = 0b0111,
  ODR_250HZ = 0b1000,
  ODR_500HZ = 0b1001,   // not available in low power mode
  ODR_1000HZ = 0b1010,  // not available in low power mode
};

enum class OrientationXY : uint8_t {
  PORTRAIT_UPRIGHT = 0b00,
  PORTRAIT_UPSIDE_DOWN = 0b01,
  LANDSCAPE_LEFT = 0b10,
  LANDSCAPE_RIGHT = 0b11,
};

union Orientation {
  struct {
    OrientationXY xy : 2;
    bool z : 1;
    uint8_t reserved : 5;
  } __attribute__((packed));
  uint8_t raw;
};

// 0x09
union RegMotionInterrupt {
  struct {
    bool freefall_interrupt : 1;
    bool reserved_1 : 1;
    bool active_interrupt : 1;
    bool reserved_3 : 1;
    bool double_tap_interrupt : 1;
    bool single_tap_interrupt : 1;
    bool orientation_interrupt : 1;
    bool reserved_7 : 1;
  } __attribute__((packed));
  uint8_t raw;
};

// 0x0C
union RegOrientationStatus {
  struct {
    uint8_t reserved_0_3 : 4;
    OrientationXY orient_xy : 2;
    bool orient_z : 1;
    uint8_t reserved_7 : 1;
  } __attribute__((packed));
  uint8_t raw{0x00};
};

// 0x0f
union RegRangeResolution {
  struct {
    Range range : 2;
    Resolution resolution : 2;
    uint8_t reserved_2 : 4;
  } __attribute__((packed));
  uint8_t raw{0x00};
};

// 0x10
union RegOutputDataRate {
  struct {
    DataRate odr : 4;
    uint8_t reserved_4 : 1;
    bool z_axis_disable : 1;
    bool y_axis_disable : 1;
    bool x_axis_disable : 1;
  } __attribute__((packed));
  uint8_t raw{0xde};
};

// 0x11
union RegPowerModeBandwidth {
  struct {
    uint8_t reserved_0 : 1;
    Bandwidth low_power_bandwidth : 4;
    uint8_t reserved_5 : 1;
    PowerMode power_mode : 2;
  } __attribute__((packed));
  uint8_t raw{0xde};
};

// 0x12
union RegSwapPolarity {
  struct {
    bool x_y_swap : 1;
    bool z_polarity : 1;
    bool y_polarity : 1;
    bool x_polarity : 1;
    uint8_t reserved : 4;
  } __attribute__((packed));
  uint8_t raw{0};
};

// 0x2a
union RegTapDuration {
  struct {
    uint8_t duration : 3;
    uint8_t reserved : 3;
    bool tap_shock : 1;
    bool tap_quiet : 1;
  } __attribute__((packed));
  uint8_t raw{0x04};
};

class MSA3xxComponent final : public PollingComponent, public i2c::I2CDevice {
 public:
  void setup() override;
  void dump_config() override;

  void loop() override;
  void update() override;

  void set_model(Model model) { this->model_ = model; }
  void set_offset(float offset_x, float offset_y, float offset_z);
  void set_range(Range range) { this->range_ = range; }
  void set_bandwidth(Bandwidth bandwidth) { this->bandwidth_ = bandwidth; }
  void set_resolution(Resolution resolution) { this->resolution_ = resolution; }
  void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy);

#ifdef USE_BINARY_SENSOR
  SUB_BINARY_SENSOR(tap)
  SUB_BINARY_SENSOR(double_tap)
  SUB_BINARY_SENSOR(active)
#endif

#ifdef USE_SENSOR
  SUB_SENSOR(acceleration_x)
  SUB_SENSOR(acceleration_y)
  SUB_SENSOR(acceleration_z)
#endif

#ifdef USE_TEXT_SENSOR
  SUB_TEXT_SENSOR(orientation_xy)
  SUB_TEXT_SENSOR(orientation_z)
#endif

  Trigger<> *get_tap_trigger() { return &this->tap_trigger_; }
  Trigger<> *get_double_tap_trigger() { return &this->double_tap_trigger_; }
  Trigger<> *get_orientation_trigger() { return &this->orientation_trigger_; }
  Trigger<> *get_freefall_trigger() { return &this->freefall_trigger_; }
  Trigger<> *get_active_trigger() { return &this->active_trigger_; }

 protected:
  Model model_{Model::MSA311};

  PowerMode power_mode_{PowerMode::NORMAL};
  DataRate data_rate_{DataRate::ODR_250HZ};
  Bandwidth bandwidth_{Bandwidth::BW_250HZ};
  Range range_{Range::RANGE_2G};
  Resolution resolution_{Resolution::RES_14BIT};
  float offset_x_, offset_y_, offset_z_;  // in m/s²
  RegSwapPolarity swap_;

  struct {
    int scale_factor_exp;
    uint8_t accel_data_width;
  } device_params_{};

  struct {
    int16_t lsb_x, lsb_y, lsb_z;
    float x, y, z;
  } data_{};

  struct {
    RegMotionInterrupt motion_int;
    RegOrientationStatus orientation;
    RegOrientationStatus orientation_old;

    uint32_t last_tap_ms{0};
    uint32_t last_double_tap_ms{0};
    uint32_t last_action_ms{0};

    bool never_published{true};
  } status_{};

  void setup_odr_(DataRate rate);
  void setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth);
  void setup_range_resolution_(Range range, Resolution resolution);
  void setup_offset_(float offset_x, float offset_y, float offset_z);

  bool read_data_();
  bool read_motion_status_();

  int64_t twos_complement_(uint64_t value, uint8_t bits);

  //
  // Actons / Triggers
  //
  Trigger<> tap_trigger_;
  Trigger<> double_tap_trigger_;
  Trigger<> orientation_trigger_;
  Trigger<> freefall_trigger_;
  Trigger<> active_trigger_;

  void process_motions_(RegMotionInterrupt old);
};

}  // namespace esphome::msa3xx
