
#pragma once

#include <array>
#include <atomic>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <vector>

#include <freertos/FreeRTOS.h>
#include <freertos/stream_buffer.h>
#include "bluetooth_types.hpp"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_gap_bt_api.h"
#include "nvs.hpp"
#include "tinyfsm.hpp"
#include "tinyfsm/include/tinyfsm.hpp"

namespace drivers {

/*
 * A handle used to interact with the bluetooth state machine.
 */
class Bluetooth {
 public:
  Bluetooth(NvsStorage& storage);

  auto Enable() -> bool;
  auto Disable() -> void;
  auto IsEnabled() -> bool;

  /*
   * Sets whether or not the bluetooth stack is allowed to actively scan for
   * new devices.
   */
  auto SetDeviceDiscovery(bool) -> void;

  auto KnownDevices() -> std::vector<bluetooth::Device>;
  auto SetPreferredDevice(const bluetooth::mac_addr_t& mac) -> void;

  auto SetSource(StreamBufferHandle_t) -> void;
  auto SetEventHandler(std::function<void(bluetooth::Event)> cb) -> void;
};

namespace bluetooth {

namespace events {
struct Enable : public tinyfsm::Event {};
struct Disable : public tinyfsm::Event {};

struct PreferredDeviceChanged : public tinyfsm::Event {};
struct SourceChanged : public tinyfsm::Event {};
struct DiscoveryChanged : public tinyfsm::Event {};
struct DeviceDiscovered : public tinyfsm::Event {
  const Device& device;
};

namespace internal {
struct Gap : public tinyfsm::Event {
  esp_bt_gap_cb_event_t type;
  esp_bt_gap_cb_param_t* param;
};
struct A2dp : public tinyfsm::Event {
  esp_a2d_cb_event_t type;
  esp_a2d_cb_param_t* param;
};
struct Avrc : public tinyfsm::Event {
  esp_avrc_ct_cb_event_t type;
  esp_avrc_ct_cb_param_t* param;
};
}  // namespace internal
}  // namespace events

/*
 * Utility for managing scanning, independent of the current connection state.
 */
class Scanner {
 public:
  Scanner();

  auto ScanContinuously() -> void;
  auto ScanOnce() -> void;
  auto StopScanning() -> void;
  auto StopScanningNow() -> void;

  auto HandleGapEvent(const events::internal::Gap&) -> void;

 private:
  bool enabled_;
  bool is_discovering_;

  auto HandleDeviceDiscovery(const esp_bt_gap_cb_param_t& param) -> void;
};

class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
 public:
  static auto Init(NvsStorage& storage) -> void;

  static auto devices() -> std::vector<Device>;
  static auto preferred_device() -> std::optional<mac_addr_t>;
  static auto preferred_device(std::optional<mac_addr_t>) -> void;

  static auto discovery() -> bool;
  static auto discovery(bool) -> void;

  static auto source() -> StreamBufferHandle_t;
  static auto source(StreamBufferHandle_t) -> void;

  static auto event_handler(std::function<void(Event)>) -> void;

  virtual ~BluetoothState(){};

  virtual void entry() {}
  virtual void exit() {}

  virtual void react(const events::Enable& ev){};
  virtual void react(const events::Disable& ev) = 0;
  virtual void react(const events::PreferredDeviceChanged& ev){};
  virtual void react(const events::SourceChanged& ev){};
  virtual void react(const events::DiscoveryChanged&);

  virtual void react(const events::DeviceDiscovered&);

  virtual void react(const events::internal::Gap& ev) = 0;
  virtual void react(const events::internal::A2dp& ev){};
  virtual void react(const events::internal::Avrc& ev){};

 protected:
  static NvsStorage* sStorage_;
  static Scanner* sScanner_;

  static std::mutex sDevicesMutex_;
  static std::map<mac_addr_t, Device> sDevices_;
  static std::optional<mac_addr_t> sPreferredDevice_;
  static mac_addr_t sCurrentDevice_;
  static bool sIsDiscoveryAllowed_;

  static std::atomic<StreamBufferHandle_t> sSource_;
  static std::function<void(Event)> sEventHandler_;
};

class Disabled : public BluetoothState {
 public:
  void entry() override;
  void exit() override;

  void react(const events::Enable& ev) override;
  void react(const events::Disable& ev) override{};

  void react(const events::internal::Gap& ev) override {}
  void react(const events::internal::A2dp& ev) override {}
  void react(const events::DiscoveryChanged& ev) override{};

  using BluetoothState::react;
};

class Idle : public BluetoothState {
 public:
  void entry() override;

  void react(const events::Disable& ev) override;
  void react(const events::PreferredDeviceChanged& ev) override;

  void react(const events::internal::Gap& ev) override;

  using BluetoothState::react;
};

class Connecting : public BluetoothState {
 public:
  void entry() override;
  void exit() override;

  void react(const events::PreferredDeviceChanged& ev) override;

  void react(const events::Disable& ev) override;
  void react(const events::internal::Gap& ev) override;
  void react(const events::internal::A2dp& ev) override;

  using BluetoothState::react;
};

class Connected : public BluetoothState {
 public:
  void entry() override;
  void exit() override;

  void react(const events::PreferredDeviceChanged& ev) override;
  void react(const events::SourceChanged& ev) override;

  void react(const events::Disable& ev) override;
  void react(const events::internal::Gap& ev) override;
  void react(const events::internal::A2dp& ev) override;
  void react(const events::internal::Avrc& ev) override;

  using BluetoothState::react;
};

}  // namespace bluetooth

}  // namespace drivers
