// 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"
#include "device/bluetooth/bluetooth_socket_mac.h"
#include "device/bluetooth/bluetooth_uuid.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

@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 {

// static
base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
    const InitCallback& init_callback) {
  return BluetoothAdapterMac::CreateAdapter();
}

// static
base::WeakPtr<BluetoothAdapter> BluetoothAdapterMac::CreateAdapter() {
  BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
  adapter->Init();
  return adapter->weak_ptr_factory_.GetWeakPtr();
}

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

BluetoothAdapterMac::~BluetoothAdapterMac() {
}

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_;
}

void BluetoothAdapterMac::SetName(const std::string& name,
                                  const base::Closure& callback,
                                  const ErrorCallback& error_callback) {
  NOTIMPLEMENTED();
}

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) {
  NOTIMPLEMENTED();
}

bool BluetoothAdapterMac::IsDiscoverable() const {
  NOTIMPLEMENTED();
  return false;
}

void BluetoothAdapterMac::SetDiscoverable(
    bool discoverable,
    const base::Closure& callback,
    const ErrorCallback& error_callback) {
  NOTIMPLEMENTED();
}

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

void BluetoothAdapterMac::CreateRfcommService(
    const BluetoothUUID& uuid,
    int channel,
    bool insecure,
    const CreateServiceCallback& callback,
    const CreateServiceErrorCallback& error_callback) {
  // TODO(keybuk): implement.
  NOTIMPLEMENTED();
}

void BluetoothAdapterMac::CreateL2capService(
    const BluetoothUUID& uuid,
    int psm,
    const CreateServiceCallback& callback,
    const CreateServiceErrorCallback& error_callback) {
  // TODO(keybuk): implement.
  NOTIMPLEMENTED();
}

void BluetoothAdapterMac::AddDiscoverySession(
    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::RemoveDiscoverySession(
    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::RemovePairingDelegateInternal(
    BluetoothDevice::PairingDelegate* pairing_delegate) {
}

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 = BluetoothDevice::CanonicalizeAddress(
        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_.reset([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 = BluetoothDeviceMac::GetDeviceAddress(device);
    devices_[device_address] = new BluetoothDeviceMac(device);
  }
}

void BluetoothAdapterMac::DeviceInquiryStarted(
    IOBluetoothDeviceInquiry* inquiry) {
  DCHECK_EQ(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_EQ(device_inquiry_, inquiry);
  std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device);
  if (discovered_devices_.find(device_address) == discovered_devices_.end()) {
    BluetoothDeviceMac device_mac(device);
    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
                      DeviceAdded(this, &device_mac));
    discovered_devices_.insert(device_address);
  }
}

void BluetoothAdapterMac::DeviceInquiryComplete(
    IOBluetoothDeviceInquiry* inquiry,
    IOReturn error,
    bool aborted) {
  DCHECK_EQ(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