// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/bluetooth/bluetooth_low_energy_device_watcher_mac.h"

#include <utility>

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#import "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "device/bluetooth/bluetooth_adapter_mac.h"

namespace device {

namespace {

constexpr char kBluetoothPlistFilePath[] =
    "/Library/Preferences/com.apple.Bluetooth.plist";

}  // namespace

// static
scoped_refptr<BluetoothLowEnergyDeviceWatcherMac>
BluetoothLowEnergyDeviceWatcherMac::CreateAndStartWatching(
    scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
    LowEnergyDeviceListUpdatedCallback
        low_energy_device_list_updated_callback) {
  auto watcher = base::MakeRefCounted<BluetoothLowEnergyDeviceWatcherMac>(
      std::move(ui_thread_task_runner),
      std::move(low_energy_device_list_updated_callback));
  watcher->Init();
  return watcher;
}

BluetoothLowEnergyDeviceWatcherMac::BluetoothLowEnergyDeviceWatcherMac(
    scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
    LowEnergyDeviceListUpdatedCallback low_energy_device_list_updated_callback)
    : ui_thread_task_runner_(std::move(ui_thread_task_runner)),
      low_energy_device_list_updated_callback_(
          std::move(low_energy_device_list_updated_callback)) {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

BluetoothLowEnergyDeviceWatcherMac::~BluetoothLowEnergyDeviceWatcherMac() {
  file_thread_task_runner_->DeleteSoon(FROM_HERE,
                                       property_list_watcher_.release());
}

void BluetoothLowEnergyDeviceWatcherMac::OnPropertyListFileChangedOnFileThread(
    const base::FilePath& path,
    bool error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (error) {
    LOG(WARNING) << "Failed to read com.apple.Bluetooth.plist.";
    return;
  }

  // Bluetooth property list file is expected to have the following format:
  //
  //   "CoreBluetoothCache" => {
  //    "E7F8589A-A7D9-4B94-9A08-D89076A159F4" => {
  //      "DeviceAddress" => "11-11-11-11-11-11"
  //      "DeviceAddressType" => 1
  //      "ServiceChangedHandle" => 3
  //      "ServiceChangedSubscribed" => 0
  //      "ServiceDiscoveryComplete" => 0
  //    }
  //    "D3CAC59E-C501-4599-97DA-2DF491544EEE" => {
  //      "DeviceAddress" => "22-22-22-22-22-22"
  //      "DeviceAddressType" => 1
  //      "ServiceChangedHandle" => 3
  //      "ServiceChangedSubscribed" => 0
  //      "ServiceDiscoveryComplete" => 0
  //    }
  //  }
  NSString* plist_file_path = base::SysUTF8ToNSString(path.value());
  NSDictionary* bluetooth_info_dictionary =
      [NSDictionary dictionaryWithContentsOfFile:plist_file_path];

  // |bluetooth_info_dictionary| is nil if there was an error reading the file
  // or if the content of the read file cannot be represented by a dictionary.
  if (!bluetooth_info_dictionary)
    return;

  auto parsed_data =
      ParseBluetoothDevicePropertyListData(bluetooth_info_dictionary);
  ui_thread_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(low_energy_device_list_updated_callback_,
                                std::move(parsed_data)));
}

void BluetoothLowEnergyDeviceWatcherMac::Init() {
  file_thread_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&BluetoothLowEnergyDeviceWatcherMac::
                                    AddBluetoothPropertyListFileWatcher,
                                this));
}

void BluetoothLowEnergyDeviceWatcherMac::ReadBluetoothPropertyListFile() {
  file_thread_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&BluetoothLowEnergyDeviceWatcherMac::
                         OnPropertyListFileChangedOnFileThread,
                     this, BluetoothPlistFilePath(), false /* error */));
}

std::map<std::string, std::string>
BluetoothLowEnergyDeviceWatcherMac::ParseBluetoothDevicePropertyListData(
    NSDictionary* data) {
  std::map<std::string, std::string> updated_low_energy_devices_info;
  NSDictionary* low_energy_devices_info = data[@"CoreBluetoothCache"];
  if (!low_energy_devices_info)
    return updated_low_energy_devices_info;

  for (NSString* identifier in low_energy_devices_info) {
    NSDictionary* device_info = low_energy_devices_info[identifier];
    if (!device_info)
      continue;

    NSString* raw_device_address = device_info[@"DeviceAddress"];
    if (!raw_device_address)
      continue;

    NSString* formatted_device_address =
        [raw_device_address stringByReplacingOccurrencesOfString:@"-"
                                                      withString:@":"];
    updated_low_energy_devices_info[base::SysNSStringToUTF8(identifier)] =
        base::SysNSStringToUTF8(formatted_device_address);
  }

  return updated_low_energy_devices_info;
}

// static
const base::FilePath&
BluetoothLowEnergyDeviceWatcherMac::BluetoothPlistFilePath() {
  static const base::FilePath file_path(kBluetoothPlistFilePath);
  return file_path;
}

void BluetoothLowEnergyDeviceWatcherMac::AddBluetoothPropertyListFileWatcher() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  property_list_watcher_->Watch(
      BluetoothPlistFilePath(), base::FilePathWatcher::Type::kNonRecursive,
      base::BindRepeating(&BluetoothLowEnergyDeviceWatcherMac::
                              OnPropertyListFileChangedOnFileThread,
                          this));
}

}  // namespace device
