// 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/renderer_host/media/video_capture_manager.h"

#include <set>

#include "base/bind.h"
#include "base/stl_util.h"
#include "content/browser/renderer_host/media/video_capture_controller.h"
#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
#include "content/public/browser/browser_thread.h"
#include "media/video/capture/fake_video_capture_device.h"
#include "media/video/capture/video_capture_device.h"

using content::BrowserThread;

namespace media_stream {

// Starting id for the first capture session.
// VideoCaptureManager::kStartOpenSessionId is used as default id without
// explicitly calling open device.
enum { kFirstSessionId = VideoCaptureManager::kStartOpenSessionId + 1 };

struct VideoCaptureManager::Controller {
  Controller(
      VideoCaptureController* vc_controller,
      VideoCaptureControllerEventHandler* handler)
      : controller(vc_controller),
        ready_to_delete(false) {
    handlers.push_front(handler);
  }
  ~Controller() {}

  scoped_refptr<VideoCaptureController> controller;
  bool ready_to_delete;
  Handlers handlers;
};

VideoCaptureManager::VideoCaptureManager()
  : vc_device_thread_("VideoCaptureManagerThread"),
    listener_(NULL),
    new_capture_session_id_(kFirstSessionId),
    use_fake_device_(false) {
  vc_device_thread_.Start();
}

VideoCaptureManager::~VideoCaptureManager() {
  vc_device_thread_.Stop();
  DCHECK(devices_.empty());
  DCHECK(controllers_.empty());
}

void VideoCaptureManager::Register(MediaStreamProviderListener* listener) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(!listener_);
  listener_ = listener;
}

void VideoCaptureManager::Unregister() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(listener_);
  listener_ = NULL;
}

void VideoCaptureManager::EnumerateDevices() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(listener_);

  vc_device_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&VideoCaptureManager::OnEnumerateDevices, this));
}

int VideoCaptureManager::Open(const StreamDeviceInfo& device) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(listener_);

  // Generate a new id for this device.
  int video_capture_session_id = new_capture_session_id_++;

  vc_device_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&VideoCaptureManager::OnOpen, this, video_capture_session_id,
                 device));

  return video_capture_session_id;
}

void VideoCaptureManager::Close(int capture_session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(listener_);

  vc_device_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&VideoCaptureManager::OnClose, this, capture_session_id));
}

void VideoCaptureManager::Start(
    const media::VideoCaptureParams& capture_params,
    media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  vc_device_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&VideoCaptureManager::OnStart, this, capture_params,
                 video_capture_receiver));
}

void VideoCaptureManager::Stop(
    const media::VideoCaptureSessionId& capture_session_id,
    base::Closure stopped_cb) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  vc_device_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&VideoCaptureManager::OnStop, this, capture_session_id,
                 stopped_cb));
}

void VideoCaptureManager::Error(
    const media::VideoCaptureSessionId& capture_session_id) {
  PostOnError(capture_session_id, kDeviceNotAvailable);
}

void VideoCaptureManager::UseFakeDevice() {
  use_fake_device_ = true;
}

MessageLoop* VideoCaptureManager::GetMessageLoop() {
  return vc_device_thread_.message_loop();
}

void VideoCaptureManager::OnEnumerateDevices() {
  DCHECK(IsOnCaptureDeviceThread());

  media::VideoCaptureDevice::Names device_names;
  GetAvailableDevices(&device_names);

  StreamDeviceInfoArray devices;
  for (media::VideoCaptureDevice::Names::iterator it =
           device_names.begin(); it != device_names.end(); ++it) {
    bool opened = DeviceOpened(*it);
    devices.push_back(StreamDeviceInfo(
        content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE, it->device_name,
        it->unique_id, opened));
  }

  PostOnDevicesEnumerated(devices);
}

void VideoCaptureManager::OnOpen(int capture_session_id,
                                 const StreamDeviceInfo& device) {
  DCHECK(IsOnCaptureDeviceThread());
  DCHECK(devices_.find(capture_session_id) == devices_.end());
  DVLOG(1) << "VideoCaptureManager::OnOpen, id " << capture_session_id;

  // Check if another session has already opened this device. If so, just
  // use that opened device.
  media::VideoCaptureDevice* video_capture_device = GetOpenedDevice(device);
  if (video_capture_device) {
    devices_[capture_session_id] = video_capture_device;
    PostOnOpened(capture_session_id);
    return;
  }

  // Open the device.
  media::VideoCaptureDevice::Name vc_device_name;
  vc_device_name.device_name = device.name;
  vc_device_name.unique_id = device.device_id;

  if (!use_fake_device_) {
    video_capture_device = media::VideoCaptureDevice::Create(vc_device_name);
  } else {
    video_capture_device =
        media::FakeVideoCaptureDevice::Create(vc_device_name);
  }
  if (!video_capture_device) {
    PostOnError(capture_session_id, kDeviceNotAvailable);
    return;
  }

  devices_[capture_session_id] = video_capture_device;
  PostOnOpened(capture_session_id);
}

void VideoCaptureManager::OnClose(int capture_session_id) {
  DCHECK(IsOnCaptureDeviceThread());
  DVLOG(1) << "VideoCaptureManager::OnClose, id " << capture_session_id;

  media::VideoCaptureDevice* video_capture_device = NULL;
  VideoCaptureDevices::iterator device_it = devices_.find(capture_session_id);
  if (device_it != devices_.end()) {
    video_capture_device = device_it->second;
    devices_.erase(device_it);

    Controllers::iterator cit = controllers_.find(video_capture_device);
    if (cit != controllers_.end()) {
      BrowserThread::PostTask(
          BrowserThread::IO, FROM_HERE,
          base::Bind(&VideoCaptureController::StopSession,
                     cit->second->controller, capture_session_id));
    }

    if (!DeviceInUse(video_capture_device)) {
      // No other users of this device, deallocate (if not done already) and
      // delete the device. No need to take care of the controller, that is done
      // by |OnStop|.
      video_capture_device->DeAllocate();
      Controllers::iterator cit = controllers_.find(video_capture_device);
      if (cit != controllers_.end()) {
        delete cit->second;
        controllers_.erase(cit);
      }
      delete video_capture_device;
    }
  }
  PostOnClosed(capture_session_id);
}

void VideoCaptureManager::OnStart(
    const media::VideoCaptureParams capture_params,
    media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
  DCHECK(IsOnCaptureDeviceThread());
  DCHECK(video_capture_receiver != NULL);
  DVLOG(1) << "VideoCaptureManager::OnStart, (" << capture_params.width
           << ", " << capture_params.height
           << ", " << capture_params.frame_per_second
           << ", " << capture_params.session_id
           << ")";

  media::VideoCaptureDevice* video_capture_device =
      GetDeviceInternal(capture_params.session_id);
  if (!video_capture_device) {
    // Invalid session id.
    video_capture_receiver->OnError();
    return;
  }
  Controllers::iterator cit = controllers_.find(video_capture_device);
  if (cit != controllers_.end()) {
    cit->second->ready_to_delete = false;
  }

  // Possible errors are signaled to video_capture_receiver by
  // video_capture_device. video_capture_receiver to perform actions.
  video_capture_device->Allocate(capture_params.width, capture_params.height,
                                 capture_params.frame_per_second,
                                 video_capture_receiver);
  video_capture_device->Start();
}

void VideoCaptureManager::OnStop(
    const media::VideoCaptureSessionId capture_session_id,
    base::Closure stopped_cb) {
  DCHECK(IsOnCaptureDeviceThread());
  DVLOG(1) << "VideoCaptureManager::OnStop, id " << capture_session_id;

  VideoCaptureDevices::iterator it = devices_.find(capture_session_id);
  if (it != devices_.end()) {
    media::VideoCaptureDevice* video_capture_device = it->second;
    // Possible errors are signaled to video_capture_receiver by
    // video_capture_device. video_capture_receiver to perform actions.
    video_capture_device->Stop();
    video_capture_device->DeAllocate();
    Controllers::iterator cit = controllers_.find(video_capture_device);
    if (cit != controllers_.end()) {
      cit->second->ready_to_delete = true;
      if (cit->second->handlers.empty()) {
        delete cit->second;
        controllers_.erase(cit);
      }
    }
  }

  if (!stopped_cb.is_null())
    stopped_cb.Run();

  if (capture_session_id == kStartOpenSessionId) {
    // This device was opened from Start(), not Open(). Close it!
    OnClose(capture_session_id);
  }
}

void VideoCaptureManager::OnOpened(int capture_session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!listener_) {
    // Listener has been removed.
    return;
  }
  listener_->Opened(content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE,
                    capture_session_id);
}

void VideoCaptureManager::OnClosed(int capture_session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!listener_) {
    // Listener has been removed.
    return;
  }
  listener_->Closed(content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE,
                    capture_session_id);
}

void VideoCaptureManager::OnDevicesEnumerated(
    const StreamDeviceInfoArray& devices) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!listener_) {
    // Listener has been removed.
    return;
  }
  listener_->DevicesEnumerated(content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE,
                               devices);
}

void VideoCaptureManager::OnError(int capture_session_id,
                                  MediaStreamProviderError error) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!listener_) {
    // Listener has been removed.
    return;
  }
  listener_->Error(content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE,
                   capture_session_id, error);
}

void VideoCaptureManager::PostOnOpened(int capture_session_id) {
  DCHECK(IsOnCaptureDeviceThread());
  BrowserThread::PostTask(BrowserThread::IO,
                          FROM_HERE,
                          base::Bind(&VideoCaptureManager::OnOpened, this,
                                     capture_session_id));
}

void VideoCaptureManager::PostOnClosed(int capture_session_id) {
  DCHECK(IsOnCaptureDeviceThread());
  BrowserThread::PostTask(BrowserThread::IO,
                          FROM_HERE,
                          base::Bind(&VideoCaptureManager::OnClosed, this,
                                     capture_session_id));
}

void VideoCaptureManager::PostOnDevicesEnumerated(
    const StreamDeviceInfoArray& devices) {
  DCHECK(IsOnCaptureDeviceThread());
  BrowserThread::PostTask(BrowserThread::IO,
                          FROM_HERE,
                          base::Bind(&VideoCaptureManager::OnDevicesEnumerated,
                                     this, devices));
}

void VideoCaptureManager::PostOnError(int capture_session_id,
                                      MediaStreamProviderError error) {
  // Don't check thread here, can be called from both IO thread and device
  // thread.
  BrowserThread::PostTask(BrowserThread::IO,
                          FROM_HERE,
                          base::Bind(&VideoCaptureManager::OnError, this,
                                     capture_session_id, error));
}

bool VideoCaptureManager::IsOnCaptureDeviceThread() const {
  return MessageLoop::current() == vc_device_thread_.message_loop();
}

void VideoCaptureManager::GetAvailableDevices(
    media::VideoCaptureDevice::Names* device_names) {
  DCHECK(IsOnCaptureDeviceThread());

  if (!use_fake_device_) {
    media::VideoCaptureDevice::GetDeviceNames(device_names);
  } else {
    media::FakeVideoCaptureDevice::GetDeviceNames(device_names);
  }
}

bool VideoCaptureManager::DeviceOpened(
    const media::VideoCaptureDevice::Name& device_name) {
  DCHECK(IsOnCaptureDeviceThread());

  for (VideoCaptureDevices::iterator it = devices_.begin();
       it != devices_.end(); ++it) {
    if (device_name.unique_id == it->second->device_name().unique_id) {
      // We've found the device!
      return true;
    }
  }
  return false;
}

media::VideoCaptureDevice* VideoCaptureManager::GetOpenedDevice(
    const StreamDeviceInfo& device_info) {
  DCHECK(IsOnCaptureDeviceThread());

  for (VideoCaptureDevices::iterator it = devices_.begin();
       it != devices_.end(); it++) {
    if (device_info.device_id == it->second->device_name().unique_id) {
      return it->second;
    }
  }
  return NULL;
}

bool VideoCaptureManager::DeviceInUse(
    const media::VideoCaptureDevice* video_capture_device) {
  DCHECK(IsOnCaptureDeviceThread());

  for (VideoCaptureDevices::iterator it = devices_.begin();
       it != devices_.end(); ++it) {
    if (video_capture_device == it->second) {
      // We've found the device!
      return true;
    }
  }
  return false;
}

void VideoCaptureManager::AddController(
    const media::VideoCaptureParams& capture_params,
    VideoCaptureControllerEventHandler* handler,
    base::Callback<void(VideoCaptureController*)> added_cb) {
  DCHECK(handler);
  vc_device_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&VideoCaptureManager::DoAddControllerOnDeviceThread,
                 this, capture_params, handler, added_cb));
}

void VideoCaptureManager::DoAddControllerOnDeviceThread(
    const media::VideoCaptureParams capture_params,
    VideoCaptureControllerEventHandler* handler,
    base::Callback<void(VideoCaptureController*)> added_cb) {
  DCHECK(IsOnCaptureDeviceThread());

  media::VideoCaptureDevice* video_capture_device =
      GetDeviceInternal(capture_params.session_id);
  scoped_refptr<VideoCaptureController> controller;
  if (video_capture_device) {
    Controllers::iterator cit = controllers_.find(video_capture_device);
    if (cit == controllers_.end()) {
      controller = new VideoCaptureController(this);
      controllers_[video_capture_device] = new Controller(controller, handler);
    } else {
      controllers_[video_capture_device]->handlers.push_front(handler);
      controller = controllers_[video_capture_device]->controller;
    }
  }
  added_cb.Run(controller);
}

void VideoCaptureManager::RemoveController(
    VideoCaptureController* controller,
    VideoCaptureControllerEventHandler* handler) {
  DCHECK(handler);
  vc_device_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&VideoCaptureManager::DoRemoveControllerOnDeviceThread, this,
                 make_scoped_refptr(controller), handler));
}

void VideoCaptureManager::DoRemoveControllerOnDeviceThread(
    VideoCaptureController* controller,
    VideoCaptureControllerEventHandler* handler) {
  DCHECK(IsOnCaptureDeviceThread());

  for (Controllers::iterator cit = controllers_.begin();
       cit != controllers_.end(); ++cit) {
    if (controller == cit->second->controller) {
      Handlers& handlers = cit->second->handlers;
      for (Handlers::iterator hit = handlers.begin();
           hit != handlers.end(); ++hit) {
        if ((*hit) == handler) {
          handlers.erase(hit);
          break;
        }
      }
      if (handlers.empty() && cit->second->ready_to_delete) {
        delete cit->second;
        controllers_.erase(cit);
      }
      return;
    }
  }
}

media::VideoCaptureDevice* VideoCaptureManager::GetDeviceInternal(
    int capture_session_id) {
  DCHECK(IsOnCaptureDeviceThread());
  VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
  if (dit != devices_.end()) {
    return dit->second;
  }

  // Solution for not using MediaStreamManager.
  // This session id won't be returned by Open().
  if (capture_session_id == kStartOpenSessionId) {
    media::VideoCaptureDevice::Names device_names;
    GetAvailableDevices(&device_names);
    if (device_names.empty()) {
      // No devices available.
      return NULL;
    }
    StreamDeviceInfo device(content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE,
                            device_names.front().device_name,
                            device_names.front().unique_id, false);

    // Call OnOpen to open using the first device in the list.
    OnOpen(capture_session_id, device);

    VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
    if (dit != devices_.end()) {
      return dit->second;
    }
  }
  return NULL;
}

}  // namespace media_stream