/*
 * libjingle
 * Copyright 2004 Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "talk/session/phone/macdevicemanager.h"

#include <CoreAudio/CoreAudio.h>
#include <QuickTime/QuickTime.h>

#include "talk/base/logging.h"
#include "talk/base/stringutils.h"
#include "talk/base/thread.h"
#include "talk/session/phone/mediacommon.h"

class DeviceWatcherImpl;

namespace cricket {

DeviceManagerInterface* DeviceManagerFactory::Create() {
  return new MacDeviceManager();
}

class MacDeviceWatcher : public DeviceWatcher {
 public:
  explicit MacDeviceWatcher(DeviceManagerInterface* dm);
  virtual ~MacDeviceWatcher();
  virtual bool Start();
  virtual void Stop();

 private:
  DeviceManagerInterface* manager_;
  DeviceWatcherImpl* impl_;
};

static const char* kFilteredAudioDevicesName[] = {
    NULL,
};
// TODO: Try to get hold of a copy of Final Cut to understand why we
//               crash while scanning their components on OS X.
static const char* const kFilteredVideoDevicesName[] =  {
    "Google Camera Adapter",   // Our own magiccams
    "DVCPRO HD",               // Final cut
    "Sonix SN9C201p",          // Crashes in OpenAComponent and CloseComponent
    NULL,
};
static const int kVideoDeviceOpenAttempts = 3;
static const UInt32 kAudioDeviceNameLength = 64;
// Obj-C functions defined in macdevicemanagermm.mm
// TODO: have a shared header for these function defines.
extern DeviceWatcherImpl* CreateDeviceWatcherCallback(
    DeviceManagerInterface* dm);
extern void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* impl);
extern bool GetQTKitVideoDevices(std::vector<Device>* out);
static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);

MacDeviceManager::MacDeviceManager() {
  set_watcher(new MacDeviceWatcher(this));
}

MacDeviceManager::~MacDeviceManager() {
}

bool MacDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
  devices->clear();
  if (!GetQTKitVideoDevices(devices)) {
    return false;
  }
  return FilterDevices(devices, kFilteredVideoDevicesName);
}

bool MacDeviceManager::GetAudioDevices(bool input,
                                       std::vector<Device>* devs) {
  devs->clear();
  std::vector<AudioDeviceID> dev_ids;
  bool ret = GetAudioDeviceIDs(input, &dev_ids);
  if (!ret) {
    return false;
  }
  for (size_t i = 0; i < dev_ids.size(); ++i) {
    std::string name;
    if (GetAudioDeviceName(dev_ids[i], input, &name)) {
      devs->push_back(Device(name, dev_ids[i]));
    }
  }
  return FilterDevices(devs, kFilteredAudioDevicesName);
}

static bool GetAudioDeviceIDs(bool input,
                              std::vector<AudioDeviceID>* out_dev_ids) {
  UInt32 propsize;
  OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
                                           &propsize, NULL);
  if (0 != err) {
    LOG(LS_ERROR) << "Couldn't get information about property, "
                  << "so no device list acquired.";
    return false;
  }

  size_t num_devices = propsize / sizeof(AudioDeviceID);
  talk_base::scoped_array<AudioDeviceID> device_ids(
      new AudioDeviceID[num_devices]);

  err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
                                 &propsize, device_ids.get());
  if (0 != err) {
    LOG(LS_ERROR) << "Failed to get device ids, "
                  << "so no device listing acquired.";
    return false;
  }

  for (size_t i = 0; i < num_devices; ++i) {
    AudioDeviceID an_id = device_ids[i];
    // find out the number of channels for this direction
    // (input/output) on this device -
    // we'll ignore anything with no channels.
    err = AudioDeviceGetPropertyInfo(an_id, 0, input,
                                     kAudioDevicePropertyStreams,
                                     &propsize, NULL);
    if (0 == err) {
      unsigned num_channels = propsize / sizeof(AudioStreamID);
      if (0 < num_channels) {
        out_dev_ids->push_back(an_id);
      }
    } else {
      LOG(LS_ERROR) << "No property info for stream property for device id "
                    << an_id << "(is_input == " << input
                    << "), so not including it in the list.";
    }
  }

  return true;
}

static bool GetAudioDeviceName(AudioDeviceID id,
                               bool input,
                               std::string* out_name) {
  UInt32 nameLength = kAudioDeviceNameLength;
  char name[kAudioDeviceNameLength + 1];
  OSErr err = AudioDeviceGetProperty(id, 0, input,
                                     kAudioDevicePropertyDeviceName,
                                     &nameLength, name);
  if (0 != err) {
    LOG(LS_ERROR) << "No name acquired for device id " << id;
    return false;
  }

  *out_name = name;
  return true;
}

MacDeviceWatcher::MacDeviceWatcher(DeviceManagerInterface* manager)
    : DeviceWatcher(manager),
      manager_(manager),
      impl_(NULL) {
}

MacDeviceWatcher::~MacDeviceWatcher() {
}

bool MacDeviceWatcher::Start() {
  if (!impl_) {
    impl_ = CreateDeviceWatcherCallback(manager_);
  }
  return impl_ != NULL;
}

void MacDeviceWatcher::Stop() {
  if (impl_) {
    ReleaseDeviceWatcherCallback(impl_);
    impl_ = NULL;
  }
}

};  // namespace cricket
