// Copyright 2015 The Chromium Authors. All rights reserved.
// 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_discovery_manager_mac.h"

#include "base/mac/mac_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/sys_string_conversions.h"
#include "device/bluetooth/bluetooth_low_energy_device_mac.h"

using device::BluetoothLowEnergyDeviceMac;
using device::BluetoothLowEnergyDiscoveryManagerMac;
using device::BluetoothLowEnergyDiscoveryManagerMacDelegate;

namespace device {

// This class is a helper to call some protected methods in
// BluetoothLowEnergyDiscoveryManagerMac.
class BluetoothLowEnergyDiscoveryManagerMacDelegate {
 public:
  BluetoothLowEnergyDiscoveryManagerMacDelegate(
      BluetoothLowEnergyDiscoveryManagerMac* manager)
      : manager_(manager) {}

  virtual ~BluetoothLowEnergyDiscoveryManagerMacDelegate() {}

  virtual void DiscoveredPeripheral(CBPeripheral* peripheral,
                                    NSDictionary* advertisementData,
                                    int rssi) {
    manager_->DiscoveredPeripheral(peripheral, advertisementData, rssi);
  }

  virtual void TryStartDiscovery() { manager_->TryStartDiscovery(); }

 private:
  BluetoothLowEnergyDiscoveryManagerMac* manager_;
};

}  // namespace device

// This class will serve as the Objective-C delegate of CBCentralManager.
@interface BluetoothLowEnergyDiscoveryManagerMacBridge
    : NSObject<CBCentralManagerDelegate> {
  BluetoothLowEnergyDiscoveryManagerMac* manager_;
  scoped_ptr<BluetoothLowEnergyDiscoveryManagerMacDelegate> delegate_;
}

- (id)initWithManager:(BluetoothLowEnergyDiscoveryManagerMac*)manager;

@end

@implementation BluetoothLowEnergyDiscoveryManagerMacBridge

- (id)initWithManager:(BluetoothLowEnergyDiscoveryManagerMac*)manager {
  if ((self = [super init])) {
    manager_ = manager;
    delegate_.reset(
        new BluetoothLowEnergyDiscoveryManagerMacDelegate(manager_));
  }
  return self;
}

- (void)centralManager:(CBCentralManager*)central
    didDiscoverPeripheral:(CBPeripheral*)peripheral
        advertisementData:(NSDictionary*)advertisementData
                     RSSI:(NSNumber*)RSSI {
  // Notifies the discovery of a device.
  delegate_->DiscoveredPeripheral(peripheral, advertisementData,
                                  [RSSI intValue]);
}

- (void)centralManagerDidUpdateState:(CBCentralManager*)central {
  // Notifies when the powered state of the central manager changed.
  delegate_->TryStartDiscovery();
}

@end

BluetoothLowEnergyDiscoveryManagerMac::
    ~BluetoothLowEnergyDiscoveryManagerMac() {
  ClearDevices();
}

bool BluetoothLowEnergyDiscoveryManagerMac::IsDiscovering() const {
  return discovering_;
}

void BluetoothLowEnergyDiscoveryManagerMac::StartDiscovery(
    BluetoothDevice::UUIDList services_uuids) {
  ClearDevices();
  discovering_ = true;
  pending_ = true;
  services_uuids_ = services_uuids;
  TryStartDiscovery();
}

void BluetoothLowEnergyDiscoveryManagerMac::TryStartDiscovery() {
  if (!discovering_) {
    return;
  }

  if (!pending_) {
    return;
  }

  // Can only start if the bluetooth power is turned on.
  if ([manager_ state] != CBCentralManagerStatePoweredOn) {
    return;
  }

  // Converts the services UUIDs to a CoreBluetooth data structure.
  NSMutableArray* services = nil;
  if (!services_uuids_.empty()) {
    services = [NSMutableArray array];
    for (auto& service_uuid : services_uuids_) {
      NSString* uuidString =
          base::SysUTF8ToNSString(service_uuid.canonical_value().c_str());
      Class aClass = NSClassFromString(@"CBUUID");
      CBUUID* uuid = [aClass UUIDWithString:uuidString];
      [services addObject:uuid];
    }
  };

  [manager_ scanForPeripheralsWithServices:services options:nil];
  pending_ = false;
}

void BluetoothLowEnergyDiscoveryManagerMac::StopDiscovery() {
  if (discovering_ && !pending_) {
    [manager_ stopScan];
  }
  discovering_ = false;
}

void BluetoothLowEnergyDiscoveryManagerMac::DiscoveredPeripheral(
    CBPeripheral* peripheral,
    NSDictionary* advertisementData,
    int rssi) {
  // Look for existing device.
  auto iter = devices_.find(
      BluetoothLowEnergyDeviceMac::GetPeripheralIdentifier(peripheral));
  if (iter == devices_.end()) {
    // A device has been added.
    BluetoothLowEnergyDeviceMac* device =
        new BluetoothLowEnergyDeviceMac(peripheral, advertisementData, rssi);
    devices_.insert(devices_.begin(),
                    std::make_pair(device->GetIdentifier(), device));
    observer_->DeviceFound(device);
    return;
  }

  // A device has an update.
  BluetoothLowEnergyDeviceMac* old_device = iter->second;
  old_device->Update(peripheral, advertisementData, rssi);
  observer_->DeviceUpdated(old_device);
}

BluetoothLowEnergyDiscoveryManagerMac*
BluetoothLowEnergyDiscoveryManagerMac::Create(Observer* observer) {
  return new BluetoothLowEnergyDiscoveryManagerMac(observer);
}

BluetoothLowEnergyDiscoveryManagerMac::BluetoothLowEnergyDiscoveryManagerMac(
    Observer* observer)
    : observer_(observer) {
  bridge_.reset([[BluetoothLowEnergyDiscoveryManagerMacBridge alloc]
      initWithManager:this]);
  // Since CoreBluetooth is only available on OS X 10.7 or later, we
  // instantiate CBCentralManager only for OS X >= 10.7.
  if (base::mac::IsOSLionOrLater()) {
    Class aClass = NSClassFromString(@"CBCentralManager");
    manager_.reset(
        [[aClass alloc] initWithDelegate:bridge_
                                   queue:dispatch_get_main_queue()]);
  }
  discovering_ = false;
}

void BluetoothLowEnergyDiscoveryManagerMac::ClearDevices() {
  STLDeleteValues(&devices_);
}