/*
 * Copyright 2019 HIMSA II K/S - www.himsa.com.
 * Represented by EHIMA - www.ehima.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ble_scanner_hci_interface.h"

#include <base/functional/bind.h>
#include <bluetooth/log.h>

#include "hci/controller_interface.h"
#include "main/shim/entry.h"
#include "stack/include/bt_types.h"
#include "stack/include/btm_client_interface.h"
#include "stack/include/hcimsgs.h"
#include "types/raw_address.h"

using namespace bluetooth;

namespace {
BleScannerHciInterface* instance = nullptr;

static void status_callback(base::Callback<void(uint8_t)> cb, uint8_t* data, uint16_t len) {
  uint8_t status;

  log::assert_that(len == 1, "Received bad response length: {}", len);
  STREAM_TO_UINT8(status, data);

  cb.Run(status);
}

static void status_handle_callback(base::Callback<void(uint8_t, uint16_t)> cb, uint8_t* data,
                                   uint16_t len) {
  uint8_t status;
  uint16_t handle = HCI_INVALID_HANDLE;

  log::assert_that((len > 0) && (len < 4), "Received bad response length: {}", len);
  uint8_t* pp = data;
  STREAM_TO_UINT8(status, pp);

  if (status == HCI_SUCCESS) {
    log::assert_that(len == 3, "Received bad response length: {}", len);

    STREAM_TO_UINT16(handle, pp);
    handle = handle & 0x0EFF;

  } else {
    log::verbose("hci response error code: {}", int{status});
  }
  cb.Run(status, handle);
}

/**
 * BleScannerHciInterface allows the caller to sync to a periodic advertising
 * train and receive periodic advertising data events through a registered
 * observer's callbacks. It also provides a synchronisation transfer API,
 * including in-controller allow list support. The right feature-complete
 * interface implementation is chosen during the init phase based on the
 * controller's list of supported features.
 */
class BleScannerImplBase : public BleScannerHciInterface {
public:
  void SetScanEventObserver(ScanEventObserver* observer) override {
    // TODO: Support multiple observers if ever needed.
    scan_event_observer = observer;
  }

  void PeriodicScanStart(uint8_t options, uint8_t set_id, uint8_t adv_addr_type,
                         const RawAddress& adv_addr, uint16_t skip_num, uint16_t sync_timeout,
                         uint8_t sync_cte_type) override {
    btsnd_hcic_ble_periodic_advertising_create_sync(options, set_id, adv_addr_type, adv_addr,
                                                    skip_num, sync_timeout, sync_cte_type);
  }

  void PeriodicScanCancelStart(status_cb command_complete) override {
    btsnd_hcic_ble_periodic_advertising_create_sync_cancel(
            base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicScanTerminate(uint16_t sync_handle, status_cb command_complete) override {
    btsnd_hcic_ble_periodic_advertising_terminate_sync(
            sync_handle, base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicScanResultEvtEnable(uint16_t sync_handle, bool enable,
                                   status_cb command_complete) override {
    btsnd_hcic_ble_set_periodic_advertising_receive_enable(
            sync_handle, enable, base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicAdvertiserListGetSize(
          BleScannerHciInterface::list_size_cb command_complete) override {
    command_complete.Run(bluetooth::shim::GetController()->GetLePeriodicAdvertiserListSize());
  }

  void PeriodicAdvertiserListAddDevice(uint8_t adv_addr_type, RawAddress& adv_addr, uint8_t set_id,
                                       status_cb command_complete) override {
    btsnd_hci_ble_add_device_to_periodic_advertiser_list(
            adv_addr_type, adv_addr, set_id,
            base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicAdvertiserListRemoveDevice(uint8_t adv_addr_type, RawAddress& adv_addr,
                                          uint8_t set_id, status_cb command_complete) override {
    btsnd_hci_ble_remove_device_from_periodic_advertiser_list(
            adv_addr_type, adv_addr, set_id,
            base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicAdvertiserListClear(status_cb command_complete) override {
    btsnd_hci_ble_clear_periodic_advertiser_list(
            base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicAdvSyncTransfer(const RawAddress& bd_addr, uint16_t service_data,
                               uint16_t sync_handle,
                               BleScannerHciInterface::handle_cb command_complete) override {
    uint16_t acl_handle =
            get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_LE);

    if (acl_handle == HCI_INVALID_HANDLE) {
      log::error("Wrong mode: no LE link exist or LE not supported");
      return;
    }

    btsnd_hcic_ble_periodic_advertising_sync_transfer(
            acl_handle, service_data, sync_handle,
            base::Bind(&status_handle_callback, std::move(command_complete)));
  }

  void PeriodicAdvSetInfoTransfer(const RawAddress& bd_addr, uint16_t service_data,
                                  uint8_t adv_handle, handle_cb command_complete) override {
    uint16_t acl_handle =
            get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_LE);

    if (acl_handle == HCI_INVALID_HANDLE) {
      log::error("Wrong mode: no LE link exist or LE not supported");
      return;
    }

    btsnd_hcic_ble_periodic_advertising_set_info_transfer(
            acl_handle, service_data, adv_handle,
            base::Bind(&status_handle_callback, std::move(command_complete)));
  }

  void SetPeriodicAdvSyncTransferParams(const RawAddress& bd_addr, uint8_t mode, uint16_t skip,
                                        uint16_t sync_timeout, uint8_t cte_type, bool set_defaults,
                                        status_cb command_complete) override {
    uint16_t acl_handle =
            get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_LE);

    if (acl_handle == HCI_INVALID_HANDLE) {
      log::error("Wrong mode: no LE link exist or LE not supported");
      return;
    }

    if (set_defaults) {
      btsnd_hcic_ble_set_default_periodic_advertising_sync_transfer_params(
              acl_handle, mode, skip, sync_timeout, cte_type,
              base::Bind(&status_callback, std::move(command_complete)));
    } else {
      btsnd_hcic_ble_set_periodic_advertising_sync_transfer_params(
              acl_handle, mode, skip, sync_timeout, cte_type,
              base::Bind(&status_callback, std::move(command_complete)));
    }
  }

  void OnPeriodicAdvSyncEstablished(uint8_t status, uint16_t sync_handle, uint8_t adv_sid,
                                    uint8_t adv_addr_type, RawAddress adv_addr, uint8_t adv_phy,
                                    uint16_t adv_interval, uint8_t adv_clock_accuracy) {
    if (scan_event_observer) {
      scan_event_observer->OnPeriodicScanEstablished(status, sync_handle, adv_sid, adv_addr_type,
                                                     adv_addr, adv_phy, adv_interval,
                                                     adv_clock_accuracy);
    }
  }

  void OnPeriodicScanResult(uint16_t sync_handle, uint8_t tx_power, int8_t rssi, uint8_t cte_type,
                            uint8_t pkt_data_status, uint8_t pkt_data_len,
                            const uint8_t* p_pkt_data) {
    // The observer should handle the caching and reassembly of the fragmented
    // packet.
    if (scan_event_observer) {
      scan_event_observer->OnPeriodicScanResult(sync_handle, tx_power, rssi, cte_type,
                                                pkt_data_status, pkt_data_len, p_pkt_data);
    }
  }

  void OnPeriodicSyncLost(uint16_t sync_handle) {
    if (scan_event_observer) {
      scan_event_observer->OnPeriodicScanLost(sync_handle);
    }
  }

private:
  ScanEventObserver* scan_event_observer = nullptr;
};

class BleScannerListImpl : public virtual BleScannerImplBase {
  void PeriodicAdvertiserListAddDevice(uint8_t adv_addr_type, RawAddress& adv_addr, uint8_t set_id,
                                       status_cb command_complete) override {
    btsnd_hci_ble_add_device_to_periodic_advertiser_list(
            adv_addr_type, adv_addr, set_id,
            base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicAdvertiserListRemoveDevice(uint8_t adv_addr_type, RawAddress& adv_addr,
                                          uint8_t set_id, status_cb command_complete) override {
    btsnd_hci_ble_remove_device_from_periodic_advertiser_list(
            adv_addr_type, adv_addr, set_id,
            base::Bind(&status_callback, std::move(command_complete)));
  }

  void PeriodicAdvertiserListClear(status_cb command_complete) override {
    btsnd_hci_ble_clear_periodic_advertiser_list(
            base::Bind(&status_callback, std::move(command_complete)));
  }
};

class BleScannerSyncTransferImpl : public virtual BleScannerImplBase {
  void PeriodicAdvSyncTransfer(const RawAddress& bd_addr, uint16_t service_data,
                               uint16_t sync_handle,
                               BleScannerHciInterface::handle_cb command_complete) override {
    uint16_t acl_handle =
            get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_LE);

    if (acl_handle == HCI_INVALID_HANDLE) {
      log::error("Wrong mode: no LE link exist or LE not supported");
      return;
    }

    btsnd_hcic_ble_periodic_advertising_sync_transfer(
            acl_handle, service_data, sync_handle,
            base::Bind(&status_handle_callback, std::move(command_complete)));
  }

  void PeriodicAdvSetInfoTransfer(const RawAddress& bd_addr, uint16_t service_data,
                                  uint8_t adv_handle, handle_cb command_complete) override {
    uint16_t acl_handle =
            get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_LE);

    if (acl_handle == HCI_INVALID_HANDLE) {
      log::error("Wrong mode: no LE link exist or LE not supported");
      return;
    }

    btsnd_hcic_ble_periodic_advertising_set_info_transfer(
            acl_handle, service_data, adv_handle,
            base::Bind(&status_handle_callback, std::move(command_complete)));
  }

  void SetPeriodicAdvSyncTransferParams(const RawAddress& bd_addr, uint8_t mode, uint16_t skip,
                                        uint16_t sync_timeout, uint8_t cte_type, bool set_defaults,
                                        status_cb command_complete) override {
    uint16_t acl_handle =
            get_btm_client_interface().peer.BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_LE);

    if (acl_handle == HCI_INVALID_HANDLE) {
      log::error("Wrong mode: no LE link exist or LE not supported");
      return;
    }

    if (set_defaults) {
      btsnd_hcic_ble_set_default_periodic_advertising_sync_transfer_params(
              acl_handle, mode, skip, sync_timeout, cte_type,
              base::Bind(&status_callback, std::move(command_complete)));
    } else {
      btsnd_hcic_ble_set_periodic_advertising_sync_transfer_params(
              acl_handle, mode, skip, sync_timeout, cte_type,
              base::Bind(&status_callback, std::move(command_complete)));
    }
  }
};

class BleScannerCompleteImpl : public BleScannerListImpl, public BleScannerSyncTransferImpl {
  // Not much to do here :)
};

}  // namespace

void BleScannerHciInterface::Initialize() {
  log::assert_that(instance == nullptr, "Was already initialized.");

  if ((bluetooth::shim::GetController()->GetLePeriodicAdvertiserListSize()) &&
      (bluetooth::shim::GetController()->SupportsBlePeriodicAdvertisingSyncTransferSender())) {
    log::info("Advertiser list in controller can be used");
    log::info("Periodic Adv Sync Transfer Sender role is supported");
    instance = new BleScannerCompleteImpl();
  } else if (bluetooth::shim::GetController()->SupportsBlePeriodicAdvertisingSyncTransferSender()) {
    log::info("Periodic Adv Sync Transfer Sender role is supported");
    instance = new BleScannerSyncTransferImpl();
  } else if (bluetooth::shim::GetController()->GetLePeriodicAdvertiserListSize()) {
    log::info("Periodic Adv Sync Transfer Recipient role is supported");
    instance = new BleScannerListImpl();
  }
  // TODO: Implement periodic adv. sync. recipient role if ever needed.
}

BleScannerHciInterface* BleScannerHciInterface::Get() { return instance; }

void BleScannerHciInterface::CleanUp() {
  delete instance;
  instance = nullptr;
}
