// Copyright 2013 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_adapter_mac.h"

#import <IOBluetooth/objc/IOBluetoothDevice.h>
#import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h>
#import <IOBluetooth/objc/IOBluetoothHostController.h>

#include <string>

#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/containers/hash_tables.h"
#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/sys_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "device/bluetooth/bluetooth_device_mac.h"

// Replicate specific 10.7 SDK declarations for building with prior SDKs.
#if !defined(MAC_OS_X_VERSION_10_7) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7

@interface IOBluetoothHostController (LionSDKDeclarations)
- (NSString*)nameAsString;
- (BluetoothHCIPowerState)powerState;
@end

@interface IOBluetoothDevice (LionSDKDeclarations)
- (NSString*)addressString;
@end

@protocol IOBluetoothDeviceInquiryDelegate
- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender;
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
                          device:(IOBluetoothDevice*)device;
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
                        error:(IOReturn)error
                      aborted:(BOOL)aborted;
@end

#endif  // MAC_OS_X_VERSION_10_7

@interface BluetoothAdapterMacDelegate
    : NSObject <IOBluetoothDeviceInquiryDelegate> {
 @private
  device::BluetoothAdapterMac* adapter_;  // weak
}

- (id)initWithAdapter:(device::BluetoothAdapterMac*)adapter;

@end

@implementation BluetoothAdapterMacDelegate

- (id)initWithAdapter:(device::BluetoothAdapterMac*)adapter {
  if ((self = [super init]))
    adapter_ = adapter;

  return self;
}

- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender {
  adapter_->DeviceInquiryStarted(sender);
}

- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
                          device:(IOBluetoothDevice*)device {
  adapter_->DeviceFound(sender, device);
}

- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
                        error:(IOReturn)error
                      aborted:(BOOL)aborted {
  adapter_->DeviceInquiryComplete(sender, error, aborted);
}

@end

namespace {

const int kPollIntervalMs = 500;

}  // namespace

namespace device {

BluetoothAdapterMac::BluetoothAdapterMac()
    : BluetoothAdapter(),
      powered_(false),
      discovery_status_(NOT_DISCOVERING),
      adapter_delegate_(
          [[BluetoothAdapterMacDelegate alloc] initWithAdapter:this]),
      device_inquiry_(
          [[IOBluetoothDeviceInquiry
              inquiryWithDelegate:adapter_delegate_] retain]),
      recently_accessed_device_timestamp_(nil),
      weak_ptr_factory_(this) {
}

BluetoothAdapterMac::~BluetoothAdapterMac() {
  [device_inquiry_ release];
  [adapter_delegate_ release];
  [recently_accessed_device_timestamp_ release];
}

void BluetoothAdapterMac::AddObserver(BluetoothAdapter::Observer* observer) {
  DCHECK(observer);
  observers_.AddObserver(observer);
}

void BluetoothAdapterMac::RemoveObserver(BluetoothAdapter::Observer* observer) {
  DCHECK(observer);
  observers_.RemoveObserver(observer);
}

std::string BluetoothAdapterMac::GetAddress() const {
  return address_;
}

std::string BluetoothAdapterMac::GetName() const {
  return name_;
}

bool BluetoothAdapterMac::IsInitialized() const {
  return true;
}

bool BluetoothAdapterMac::IsPresent() const {
  return !address_.empty();
}

bool BluetoothAdapterMac::IsPowered() const {
  return powered_;
}

void BluetoothAdapterMac::SetPowered(bool powered,
                                     const base::Closure& callback,
                                     const ErrorCallback& error_callback) {
}

bool BluetoothAdapterMac::IsDiscovering() const {
  return discovery_status_ == DISCOVERING ||
      discovery_status_ == DISCOVERY_STOPPING;
}

void BluetoothAdapterMac::StartDiscovering(
    const base::Closure& callback,
    const ErrorCallback& error_callback) {
  if (discovery_status_ == DISCOVERING) {
    num_discovery_listeners_++;
    callback.Run();
    return;
  }
  on_start_discovery_callbacks_.push_back(
      std::make_pair(callback, error_callback));
  MaybeStartDeviceInquiry();
}

void BluetoothAdapterMac::StopDiscovering(const base::Closure& callback,
                                          const ErrorCallback& error_callback) {
  if (discovery_status_ == NOT_DISCOVERING) {
    error_callback.Run();
    return;
  }
  on_stop_discovery_callbacks_.push_back(
      std::make_pair(callback, error_callback));
  MaybeStopDeviceInquiry();
}

void BluetoothAdapterMac::ReadLocalOutOfBandPairingData(
    const BluetoothOutOfBandPairingDataCallback& callback,
    const ErrorCallback& error_callback) {
}

void BluetoothAdapterMac::Init() {
  ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
  PollAdapter();
}

void BluetoothAdapterMac::InitForTest(
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
  ui_task_runner_ = ui_task_runner;
  PollAdapter();
}

void BluetoothAdapterMac::PollAdapter() {
  bool was_present = IsPresent();
  std::string name = "";
  std::string address = "";
  bool powered = false;
  IOBluetoothHostController* controller =
      [IOBluetoothHostController defaultController];

  if (controller != nil) {
    name = base::SysNSStringToUTF8([controller nameAsString]);
    address = base::SysNSStringToUTF8([controller addressAsString]);
    powered = ([controller powerState] == kBluetoothHCIPowerStateON);
  }

  bool is_present = !address.empty();
  name_ = name;
  address_ = address;

  if (was_present != is_present) {
    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
                      AdapterPresentChanged(this, is_present));
  }
  if (powered_ != powered) {
    powered_ = powered;
    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
                      AdapterPoweredChanged(this, powered_));
  }

  IOBluetoothDevice* recent_device =
      [[IOBluetoothDevice recentDevices:1] lastObject];
  NSDate* access_timestamp = [recent_device recentAccessDate];
  if (recently_accessed_device_timestamp_ == nil ||
      access_timestamp == nil ||
      [recently_accessed_device_timestamp_ compare:access_timestamp] ==
          NSOrderedAscending) {
    UpdateDevices([IOBluetoothDevice pairedDevices]);
    [recently_accessed_device_timestamp_ release];
    recently_accessed_device_timestamp_ = [access_timestamp copy];
  }

  ui_task_runner_->PostDelayedTask(
      FROM_HERE,
      base::Bind(&BluetoothAdapterMac::PollAdapter,
                 weak_ptr_factory_.GetWeakPtr()),
      base::TimeDelta::FromMilliseconds(kPollIntervalMs));
}

void BluetoothAdapterMac::UpdateDevices(NSArray* devices) {
  STLDeleteValues(&devices_);
  for (IOBluetoothDevice* device in devices) {
    std::string device_address =
        base::SysNSStringToUTF8([device addressString]);
    devices_[device_address] = new BluetoothDeviceMac(device);
  }
}

void BluetoothAdapterMac::DeviceInquiryStarted(
    IOBluetoothDeviceInquiry* inquiry) {
  DCHECK(device_inquiry_ == inquiry);
  if (discovery_status_ == DISCOVERING)
    return;

  discovery_status_ = DISCOVERING;
  RunCallbacks(on_start_discovery_callbacks_, true);
  num_discovery_listeners_ = on_start_discovery_callbacks_.size();
  on_start_discovery_callbacks_.clear();

  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
                    AdapterDiscoveringChanged(this, true));
  MaybeStopDeviceInquiry();
}

void BluetoothAdapterMac::DeviceFound(IOBluetoothDeviceInquiry* inquiry,
                                      IOBluetoothDevice* device) {
  DCHECK(device_inquiry_ == inquiry);
  std::string device_address = base::SysNSStringToUTF8([device addressString]);
  if (discovered_devices_.find(device_address) == discovered_devices_.end()) {
    scoped_ptr<BluetoothDeviceMac> device_mac(new BluetoothDeviceMac(device));
    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
                      DeviceAdded(this, device_mac.get()));
    discovered_devices_.insert(device_address);
  }
}

void BluetoothAdapterMac::DeviceInquiryComplete(
    IOBluetoothDeviceInquiry* inquiry,
    IOReturn error,
    bool aborted) {
  DCHECK(device_inquiry_ == inquiry);
  if (discovery_status_ == DISCOVERING &&
      [device_inquiry_ start] == kIOReturnSuccess) {
    return;
  }

  // Device discovery is done.
  discovered_devices_.clear();
  discovery_status_ = NOT_DISCOVERING;
  RunCallbacks(on_stop_discovery_callbacks_, error == kIOReturnSuccess);
  num_discovery_listeners_ = 0;
  on_stop_discovery_callbacks_.clear();
  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
                    AdapterDiscoveringChanged(this, false));
  MaybeStartDeviceInquiry();
}

void BluetoothAdapterMac::MaybeStartDeviceInquiry() {
  if (discovery_status_ == NOT_DISCOVERING &&
      !on_start_discovery_callbacks_.empty()) {
    discovery_status_ = DISCOVERY_STARTING;
    if ([device_inquiry_ start] != kIOReturnSuccess) {
      discovery_status_ = NOT_DISCOVERING;
      RunCallbacks(on_start_discovery_callbacks_, false);
      on_start_discovery_callbacks_.clear();
    }
  }
}

void BluetoothAdapterMac::MaybeStopDeviceInquiry() {
  if (discovery_status_ != DISCOVERING)
    return;

  if (on_stop_discovery_callbacks_.size() < num_discovery_listeners_) {
    RunCallbacks(on_stop_discovery_callbacks_, true);
    num_discovery_listeners_ -= on_stop_discovery_callbacks_.size();
    on_stop_discovery_callbacks_.clear();
    return;
  }

  discovery_status_ = DISCOVERY_STOPPING;
  if ([device_inquiry_ stop] != kIOReturnSuccess) {
    RunCallbacks(on_stop_discovery_callbacks_, false);
    on_stop_discovery_callbacks_.clear();
  }
}

void BluetoothAdapterMac::RunCallbacks(
    const DiscoveryCallbackList& callback_list, bool success) const {
  for (DiscoveryCallbackList::const_iterator iter = callback_list.begin();
       iter != callback_list.end();
       ++iter) {
    if (success)
      ui_task_runner_->PostTask(FROM_HERE, iter->first);
    else
      ui_task_runner_->PostTask(FROM_HERE, iter->second);
  }
}

}  // namespace device