// Copyright 2014 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 "components/storage_monitor/image_capture_device_manager.h"

#import <ImageCaptureCore/ImageCaptureCore.h>

#import "components/storage_monitor/image_capture_device.h"
#include "components/storage_monitor/storage_info.h"

namespace {

storage_monitor::ImageCaptureDeviceManager* g_image_capture_device_manager =
    NULL;

}  // namespace

// This class is the surface for the Mac ICDeviceBrowser ImageCaptureCore API.
// Owned by the ChromeBrowserParts and has browser process lifetime. Upon
// creation, it gets a list of attached media volumes (asynchronously) which
// it will eventually forward to StorageMonitor. It will also
// set up an ImageCaptureCore listener to be told when new devices/volumes
// are discovered and existing ones are removed.
@interface ImageCaptureDeviceManagerImpl
    : NSObject<ICDeviceBrowserDelegate> {
 @private
  base::scoped_nsobject<ICDeviceBrowser> deviceBrowser_;
  base::scoped_nsobject<NSMutableArray> cameras_;

  // Guaranteed to outlive this class.
  // TODO(gbillock): Update when ownership chains go up through
  // a StorageMonitor subclass.
  storage_monitor::StorageMonitor::Receiver* notifications_;
}

- (void)setNotifications:
        (storage_monitor::StorageMonitor::Receiver*)notifications;
- (void)close;

// The UUIDs passed here are available in the device attach notifications.
// They're gotten by cracking the device ID and taking the unique ID output.
- (ImageCaptureDevice*)deviceForUUID:(const std::string&)uuid;

- (ICDeviceBrowser*)deviceBrowserForTest;

@end

@implementation ImageCaptureDeviceManagerImpl

- (id)init {
  if ((self = [super init])) {
    cameras_.reset([[NSMutableArray alloc] init]);
    notifications_ = NULL;

    deviceBrowser_.reset([[ICDeviceBrowser alloc] init]);
    [deviceBrowser_ setDelegate:self];
    [deviceBrowser_ setBrowsedDeviceTypeMask:
        static_cast<ICDeviceTypeMask>(
            ICDeviceTypeMaskCamera | ICDeviceLocationTypeMaskLocal)];
    [deviceBrowser_ start];
  }
  return self;
}

- (void)setNotifications:
            (storage_monitor::StorageMonitor::Receiver*)notifications {
  notifications_ = notifications;
}

- (void)close {
  [deviceBrowser_ setDelegate:nil];
  [deviceBrowser_ stop];
  deviceBrowser_.reset();
  cameras_.reset();
}

- (ImageCaptureDevice*) deviceForUUID:(const std::string&)uuid {
  for (ICCameraDevice* camera in cameras_.get()) {
    NSString* camera_id = [camera UUIDString];
    if (base::SysNSStringToUTF8(camera_id) == uuid) {
      return [[[ImageCaptureDevice alloc]
          initWithCameraDevice:camera] autorelease];
    }
  }
  return nil;
}

- (void)deviceBrowser:(ICDeviceBrowser*)browser
         didAddDevice:(ICDevice*)addedDevice
           moreComing:(BOOL)moreComing {
  if (!(addedDevice.type & ICDeviceTypeCamera))
    return;

  // Ignore mass storage attaches -- those will be handled
  // by Mac's removable storage watcher.
  if ([addedDevice.transportType isEqualToString:ICTransportTypeMassStorage])
    return;

  ICCameraDevice* cameraDevice =
      base::mac::ObjCCastStrict<ICCameraDevice>(addedDevice);

  [cameras_ addObject:addedDevice];

  // TODO(gbillock): use [cameraDevice mountPoint] here when possible.
  storage_monitor::StorageInfo info(
      storage_monitor::StorageInfo::MakeDeviceId(
          storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE,
          base::SysNSStringToUTF8([cameraDevice UUIDString])),
      std::string(),
      base::SysNSStringToUTF16([cameraDevice name]),
      base::string16(),
      base::string16(),
      0);
  notifications_->ProcessAttach(info);
}

- (void)deviceBrowser:(ICDeviceBrowser*)browser
      didRemoveDevice:(ICDevice*)device
            moreGoing:(BOOL)moreGoing {
  if (!(device.type & ICDeviceTypeCamera))
    return;

  std::string uuid = base::SysNSStringToUTF8([device UUIDString]);

  // May delete |device|.
  [cameras_ removeObject:device];

  notifications_->ProcessDetach(storage_monitor::StorageInfo::MakeDeviceId(
      storage_monitor::StorageInfo::MAC_IMAGE_CAPTURE, uuid));
}

- (ICDeviceBrowser*)deviceBrowserForTest {
  return deviceBrowser_.get();
}

@end  // ImageCaptureDeviceManagerImpl

namespace storage_monitor {

ImageCaptureDeviceManager::ImageCaptureDeviceManager() {
  device_browser_.reset([[ImageCaptureDeviceManagerImpl alloc] init]);
  g_image_capture_device_manager = this;
}

ImageCaptureDeviceManager::~ImageCaptureDeviceManager() {
  g_image_capture_device_manager = NULL;
  [device_browser_ close];
}

void ImageCaptureDeviceManager::SetNotifications(
    StorageMonitor::Receiver* notifications) {
  [device_browser_ setNotifications:notifications];
}

void ImageCaptureDeviceManager::EjectDevice(
    const std::string& uuid,
    base::Callback<void(StorageMonitor::EjectStatus)> callback) {
  base::scoped_nsobject<ImageCaptureDevice> camera_device(
      [[device_browser_ deviceForUUID:uuid] retain]);
  [camera_device eject];
  [camera_device close];
  callback.Run(StorageMonitor::EJECT_OK);
}

// static
ImageCaptureDevice* ImageCaptureDeviceManager::deviceForUUID(
    const std::string& uuid) {
  ImageCaptureDeviceManagerImpl* manager =
      g_image_capture_device_manager->device_browser_;
  return [manager deviceForUUID:uuid];
}

id<ICDeviceBrowserDelegate>
ImageCaptureDeviceManager::device_browser_delegate() {
  return device_browser_.get();
}

ICDeviceBrowser* ImageCaptureDeviceManager::device_browser_for_test() {
  return [device_browser_ deviceBrowserForTest];
}

}  // namespace storage_monitor