// Copyright (c) 2012 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 "content/browser/device_monitor_mac.h"

#include <IOKit/audio/IOAudioDefines.h>
#include <IOKit/usb/IOUSBLib.h>

#include "base/logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_ioobject.h"

namespace content {

namespace {

const io_name_t kServices[] = {
  kIOFirstPublishNotification,
  kIOTerminatedNotification,
};

CFMutableDictionaryRef CreateMatchingDictionaryForUSBDevices(
    SInt32 interface_class_code, SInt32 interface_subclass_code) {
  CFMutableDictionaryRef matching_dictionary =
      IOServiceMatching(kIOUSBInterfaceClassName);
  base::mac::ScopedCFTypeRef<CFNumberRef> number_ref(CFNumberCreate(
      kCFAllocatorDefault, kCFNumberSInt32Type, &interface_class_code));
  DCHECK(number_ref);
  CFDictionaryAddValue(matching_dictionary, CFSTR(kUSBInterfaceClass),
                       number_ref);

  number_ref.reset(CFNumberCreate(kCFAllocatorDefault,
                                  kCFNumberSInt32Type,
                                  &interface_subclass_code));
  DCHECK(number_ref);
  CFDictionaryAddValue(matching_dictionary, CFSTR(kUSBInterfaceSubClass),
                       number_ref);

  return matching_dictionary;
}

void RegisterCallbackToIOService(IONotificationPortRef port,
                                 const io_name_t type,
                                 CFMutableDictionaryRef dictionary,
                                 IOServiceMatchingCallback callback,
                                 void* context,
                                 io_iterator_t* service) {
  kern_return_t err = IOServiceAddMatchingNotification(port,
                                                       type,
                                                       dictionary,
                                                       callback,
                                                       context,
                                                       service);
  if (err) {
    NOTREACHED() << "Failed to register the IO matched notification for type "
                 << type;
    return;
  }
  DCHECK(*service);

  // Iterate over set of matching devices to access already-present devices
  // and to arm the notification.
  for (base::mac::ScopedIOObject<io_service_t> object(IOIteratorNext(*service));
       object;
       object.reset(IOIteratorNext(*service))) {};
}

}  // namespace

DeviceMonitorMac::DeviceMonitorMac() {
  // Add the notification port to the run loop.
  notification_port_ = IONotificationPortCreate(kIOMasterPortDefault);
  DCHECK(notification_port_);

  RegisterAudioServices();
  RegisterVideoServices();

  CFRunLoopAddSource(CFRunLoopGetCurrent(),
                     IONotificationPortGetRunLoopSource(notification_port_),
                     kCFRunLoopCommonModes);
}

DeviceMonitorMac::~DeviceMonitorMac() {
  // Stop the notifications and free the objects.
  for (size_t i = 0; i < notification_iterators_.size(); ++i) {
    IOObjectRelease(*notification_iterators_[i]);
  }
  notification_iterators_.clear();

  // Remove the notification port from the message runloop.
  CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
                        IONotificationPortGetRunLoopSource(notification_port_),
                        kCFRunLoopCommonModes);

  // Destroy the notification port allocated by IONotificationPortCreate.
  IONotificationPortDestroy(notification_port_);
}

void DeviceMonitorMac::RegisterAudioServices() {
  CFMutableDictionaryRef dictionary =
      IOServiceMatching(kIOAudioDeviceClassName);
  RegisterServices(dictionary, &AudioDeviceCallback);
}

void DeviceMonitorMac::RegisterVideoServices() {
  CFMutableDictionaryRef dictionary = CreateMatchingDictionaryForUSBDevices(
      kUSBVideoInterfaceClass, kUSBVideoControlSubClass);
  RegisterServices(dictionary, &VideoDeviceCallback);
}

void DeviceMonitorMac::RegisterServices(CFMutableDictionaryRef dictionary,
                                        IOServiceMatchingCallback callback) {
  // Add callback to the service.
  for (size_t i = 0; i < arraysize(kServices); ++i) {
    // |dictionary| comes in with a reference count as 1. Since each call to
    // IOServiceAddMatchingNotification consumes one reference, we need to
    // retain |arraysize(kServices) -1| additional dictionary references.
    if (i < (arraysize(kServices) - 1))
      CFRetain(dictionary);

    // Register callback to each service.
    io_iterator_t service;
    RegisterCallbackToIOService(notification_port_,
                                kServices[i],
                                dictionary,
                                callback,
                                this,
                                &service);

    // Store the pointer of the object to release the memory when shutting
    // down the services.
    notification_iterators_.push_back(&service);
  }
}

void DeviceMonitorMac::AudioDeviceCallback(void *context,
                                           io_iterator_t iterator) {
 for (base::mac::ScopedIOObject<io_service_t> object(IOIteratorNext(iterator));
      object;
      object.reset(IOIteratorNext(iterator))) {
    if (context) {
      reinterpret_cast<DeviceMonitorMac*>(context)->NotifyDeviceChanged(
          base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
    }
  }
}

void DeviceMonitorMac::VideoDeviceCallback(void *context,
                                           io_iterator_t iterator) {
  for (base::mac::ScopedIOObject<io_service_t> object(IOIteratorNext(iterator));
       object;
       object.reset(IOIteratorNext(iterator))) {
    if (context) {
      reinterpret_cast<DeviceMonitorMac*>(context)->NotifyDeviceChanged(
          base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
    }
  }
}

void DeviceMonitorMac::NotifyDeviceChanged(
    base::SystemMonitor::DeviceType type) {
  // TODO(xians): Remove the global variable for SystemMonitor.
  base::SystemMonitor::Get()->ProcessDevicesChanged(type);
}

}  // namespace content