// Copyright (c) 2011 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 "media/audio/audio_input_controller.h"

#include "base/threading/thread_restrictions.h"
#include "media/base/limits.h"

namespace {
const int kMaxInputChannels = 2;
const int kTimerResetInterval = 1; // One second.
}

namespace media {

// static
AudioInputController::Factory* AudioInputController::factory_ = NULL;

AudioInputController::AudioInputController(EventHandler* handler,
                                           SyncWriter* sync_writer)
    : handler_(handler),
      stream_(NULL),
      ALLOW_THIS_IN_INITIALIZER_LIST(no_data_timer_(
          base::TimeDelta::FromSeconds(kTimerResetInterval),
          this,
          &AudioInputController::DoReportNoDataError)),
      state_(kEmpty),
      thread_("AudioInputControllerThread"),
      sync_writer_(sync_writer) {
}

AudioInputController::~AudioInputController() {
  DCHECK(kClosed == state_ || kCreated == state_ || kEmpty == state_);
}

// static
scoped_refptr<AudioInputController> AudioInputController::Create(
    EventHandler* event_handler,
    const AudioParameters& params) {
  if (!params.IsValid() || (params.channels > kMaxInputChannels))
    return NULL;

  if (factory_) {
    return factory_->Create(event_handler, params);
  }

  scoped_refptr<AudioInputController> controller(new AudioInputController(
      event_handler, NULL));

  // Start the thread and post a task to create the audio input stream.
  controller->thread_.Start();
  controller->thread_.message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(controller.get(), &AudioInputController::DoCreate,
                        params));
  return controller;
}

// static
scoped_refptr<AudioInputController> AudioInputController::CreateLowLatency(
    EventHandler* event_handler,
    const AudioParameters& params,
    SyncWriter* sync_writer) {
  DCHECK(sync_writer);

  if (!params.IsValid() || (params.channels > kMaxInputChannels))
    return NULL;

  if (!AudioManager::GetAudioManager())
    return NULL;

  // Starts the audio controller thread.
  scoped_refptr<AudioInputController> controller(new AudioInputController(
      event_handler, sync_writer));

  // Start the thread and post a task to create the audio input stream.
  controller->thread_.Start();
  controller->thread_.message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(controller.get(), &AudioInputController::DoCreate,
                        params));
  return controller;
}

void AudioInputController::Record() {
  DCHECK(thread_.IsRunning());
  thread_.message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &AudioInputController::DoRecord));
}

void AudioInputController::Close() {
  if (!thread_.IsRunning()) {
    // If the thread is not running make sure we are stopped.
    DCHECK_EQ(kClosed, state_);
    return;
  }

  // Wait for all tasks to complete on the audio thread.
  thread_.message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &AudioInputController::DoClose));

  // A ScopedAllowIO object is required to join the thread when calling Stop.
  // This is because as joining threads may be a long operation it's now
  // not allowed in threads without IO access, which is the case of the IO
  // thread (it is missnamed) being used here. This object overrides
  // temporarily this restriction and should be used only in specific
  // infrequent cases where joining is guaranteed to be fast.
  // Bug: http://code.google.com/p/chromium/issues/detail?id=67806
  base::ThreadRestrictions::ScopedAllowIO allow_io_for_thread_join;
  thread_.Stop();
}

void AudioInputController::DoCreate(const AudioParameters& params) {
  stream_ = AudioManager::GetAudioManager()->MakeAudioInputStream(params);

  if (!stream_) {
    // TODO(satish): Define error types.
    handler_->OnError(this, 0);
    return;
  }

  if (stream_ && !stream_->Open()) {
    stream_->Close();
    stream_ = NULL;
    // TODO(satish): Define error types.
    handler_->OnError(this, 0);
    return;
  }

  thread_.message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &AudioInputController::DoResetNoDataTimer));
  state_ = kCreated;
  handler_->OnCreated(this);
}

void AudioInputController::DoRecord() {
  DCHECK_EQ(thread_.message_loop(), MessageLoop::current());

  if (state_ != kCreated)
    return;

  {
    base::AutoLock auto_lock(lock_);
    state_ = kRecording;
  }

  stream_->Start(this);
  handler_->OnRecording(this);
}

void AudioInputController::DoClose() {
  DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
  DCHECK_NE(kClosed, state_);

  // |stream_| can be null if creating the device failed in DoCreate().
  if (stream_) {
    stream_->Stop();
    stream_->Close();
    // After stream is closed it is destroyed, so don't keep a reference to it.
    stream_ = NULL;
  }

  if (LowLatencyMode()) {
    sync_writer_->Close();
  }

  // Since the stream is closed at this point there's no other threads reading
  // |state_| so we don't need to lock.
  state_ = kClosed;
}

void AudioInputController::DoReportError(int code) {
  DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
  handler_->OnError(this, code);
}

void AudioInputController::DoReportNoDataError() {
  DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
  handler_->OnError(this, 0);
}

void AudioInputController::DoResetNoDataTimer() {
  DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
  no_data_timer_.Reset();
}

void AudioInputController::OnData(AudioInputStream* stream, const uint8* data,
                                  uint32 size) {
  {
    base::AutoLock auto_lock(lock_);
    if (state_ != kRecording)
      return;
  }

  thread_.message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &AudioInputController::DoResetNoDataTimer));

  // Use SyncSocket if we are in a low-latency mode.
  if (LowLatencyMode()) {
    sync_writer_->Write(data, size);
    sync_writer_->UpdateRecordedBytes(size);
    return;
  }

  handler_->OnData(this, data, size);
}

void AudioInputController::OnClose(AudioInputStream* stream) {
  // TODO(satish): Sometimes the device driver closes the input stream without
  // us asking for it (may be if the device was unplugged?). Check how to handle
  // such cases here.
}

void AudioInputController::OnError(AudioInputStream* stream, int code) {
  // Handle error on the audio controller thread.
  thread_.message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &AudioInputController::DoReportError, code));
}

}  // namespace media
