// SPDX-FileCopyrightText: 2023 ailurux <ailuruxx@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only

#pragma once

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

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

namespace drivers {

/*
 * A handle used to interact with the bluetooth state machine. This is the main
 * API that the rest of the system should use to interact with Bluetooth.
 */
class Bluetooth {
 public:
  /*
   * Callback invoked when an event is generated by the Bluetooth stack. This
   * callback is invoked synchronously from a Bluetooth task context, so
   * implementations should immediately hop to a different task to process the
   * event.
   */
  using EventHandler = std::function<void(bluetooth::Event)>;

  Bluetooth(NvsStorage&, tasks::WorkerPool&, EventHandler);

  /* Enables or disables the entire Bluetooth stack. */
  auto enable(bool en) -> void;
  auto enabled() -> bool;

  auto sources(OutputBuffers*) -> void;
  auto softVolume(float) -> void;

  enum class ConnectionState {
    kConnected,
    kConnecting,
    kDisconnected,
  };

  auto connectionState() -> ConnectionState;

  /*
   * The 'paired' device is a device that will be preferred for connections.
   * When Bluetooth is first enabled, we immediately try to connect to the
   * paired device. If the paired device is seen during a scan, then we will
   * also automatically connect to it.
   */
  auto pairedDevice() -> std::optional<bluetooth::MacAndName>;

  /*
   * Sets the preferred device. If a device is provided, a connection will be
   * attempted immediately, even if the device has not been detected in a
   * previous scan.
   */
  auto pairedDevice(std::optional<bluetooth::MacAndName> dev) -> void;

  /* A list of devices that have previously been the paired device. */
  auto knownDevices() -> std::vector<bluetooth::MacAndName>;
  auto forgetKnownDevice(const bluetooth::mac_addr_t&) -> void;

  /* Enables or disables scanning for nearby Bluetooth devices. */
  auto discoveryEnabled(bool) -> void;
  auto discoveryEnabled() -> bool;

  /*
   * A list of nearby devices that have been discovered since discovery was
   * last enabled. This list may include the paired device, as well as devices
   * that are also present in the known devices list.
   */
  auto discoveredDevices() -> std::vector<bluetooth::MacAndName>;

 private:
  NvsStorage& nvs_;
};

namespace bluetooth {

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

struct ConnectTimedOut : public tinyfsm::Event {};
struct PairedDeviceChanged : public tinyfsm::Event {};
struct SourcesChanged : 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;
};
struct Avrctg : public tinyfsm::Event {
  esp_avrc_tg_cb_event_t type;
  esp_avrc_tg_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 enabled() -> bool;

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

 private:
  bool enabled_;
  bool is_discovering_;

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

/*
 * The main state machine for managing the state of the Bluetooth stack, and
 * the current (if any) Bluetooth connection.
 */
class BluetoothState : public tinyfsm::Fsm<BluetoothState> {
 public:
  static auto Init(NvsStorage& storage, Bluetooth::EventHandler) -> void;

  static auto lock() -> std::lock_guard<std::mutex>;

  static auto pairedDevice() -> std::optional<bluetooth::MacAndName>;
  static auto pairedDevice(std::optional<bluetooth::MacAndName>) -> void;

  static auto discovery() -> bool;
  static auto discovery(bool) -> void;
  static auto discoveredDevices() -> std::vector<Device>;

  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::ConnectTimedOut& ev){};
  virtual void react(const events::PairedDeviceChanged& ev){};
  virtual void react(const events::SourcesChanged& ev){};

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

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

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

  static std::mutex sFsmMutex;
  static std::map<mac_addr_t, Device> sDiscoveredDevices_;
  static std::optional<bluetooth::MacAndName> sPairedWith_;
  static std::optional<bluetooth::MacAndName> sConnectingTo_;
  static int sConnectAttemptsRemaining_;

  static std::function<void(Event)> sEventHandler_;

  auto connect(const bluetooth::MacAndName&) -> bool;
};

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

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

  void react(events::internal::Gap ev) override {}
  void react(events::internal::A2dp ev) override {}

  using BluetoothState::react;
};

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

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

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

  using BluetoothState::react;
};

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

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

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

  using BluetoothState::react;
};

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

  void react(const events::PairedDeviceChanged& ev) override;
  void react(const events::SourcesChanged& ev) override;

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

  using BluetoothState::react;

 private:
  uint8_t transaction_num_;
  mac_addr_t connected_to_;
};

}  // namespace bluetooth

}  // namespace drivers
