// 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 "media/audio/audio_output_dispatcher_impl.h"

#include <algorithm>

#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_output_proxy.h"

namespace media {

AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
    AudioManager* audio_manager,
    const AudioParameters& params,
    const std::string& output_device_id,
    const std::string& input_device_id,
    const base::TimeDelta& close_delay)
    : AudioOutputDispatcher(audio_manager,
                            params,
                            output_device_id,
                            input_device_id),
      idle_proxies_(0),
      close_timer_(FROM_HERE,
                   close_delay,
                   this,
                   &AudioOutputDispatcherImpl::CloseAllIdleStreams),
      audio_log_(
          audio_manager->CreateAudioLog(AudioLogFactory::AUDIO_OUTPUT_STREAM)),
      audio_stream_id_(0) {}

AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl() {
  DCHECK_EQ(idle_proxies_, 0u);
  DCHECK(proxy_to_physical_map_.empty());
  DCHECK(idle_streams_.empty());
}

bool AudioOutputDispatcherImpl::OpenStream() {
  DCHECK(message_loop_->BelongsToCurrentThread());

  // Ensure that there is at least one open stream.
  if (idle_streams_.empty() && !CreateAndOpenStream())
    return false;

  ++idle_proxies_;
  close_timer_.Reset();
  return true;
}

bool AudioOutputDispatcherImpl::StartStream(
    AudioOutputStream::AudioSourceCallback* callback,
    AudioOutputProxy* stream_proxy) {
  DCHECK(message_loop_->BelongsToCurrentThread());
  DCHECK(proxy_to_physical_map_.find(stream_proxy) ==
         proxy_to_physical_map_.end());

  if (idle_streams_.empty() && !CreateAndOpenStream())
    return false;

  AudioOutputStream* physical_stream = idle_streams_.back();
  idle_streams_.pop_back();

  DCHECK_GT(idle_proxies_, 0u);
  --idle_proxies_;

  double volume = 0;
  stream_proxy->GetVolume(&volume);
  physical_stream->SetVolume(volume);
  const int stream_id = audio_stream_ids_[physical_stream];
  audio_log_->OnSetVolume(stream_id, volume);
  physical_stream->Start(callback);
  audio_log_->OnStarted(stream_id);
  proxy_to_physical_map_[stream_proxy] = physical_stream;

  close_timer_.Reset();
  return true;
}

void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy) {
  DCHECK(message_loop_->BelongsToCurrentThread());

  AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
  DCHECK(it != proxy_to_physical_map_.end());
  AudioOutputStream* physical_stream = it->second;
  proxy_to_physical_map_.erase(it);

  physical_stream->Stop();
  audio_log_->OnStopped(audio_stream_ids_[physical_stream]);
  ++idle_proxies_;
  idle_streams_.push_back(physical_stream);

  close_timer_.Reset();
}

void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
                                                double volume) {
  DCHECK(message_loop_->BelongsToCurrentThread());
  AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
  if (it != proxy_to_physical_map_.end()) {
    AudioOutputStream* physical_stream = it->second;
    physical_stream->SetVolume(volume);
    audio_log_->OnSetVolume(audio_stream_ids_[physical_stream], volume);
  }
}

void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
  DCHECK(message_loop_->BelongsToCurrentThread());

  DCHECK_GT(idle_proxies_, 0u);
  --idle_proxies_;

  // Leave at least a single stream running until the close timer fires to help
  // cycle time when streams are opened and closed repeatedly.
  CloseIdleStreams(std::max(idle_proxies_, static_cast<size_t>(1)));
  close_timer_.Reset();
}

void AudioOutputDispatcherImpl::Shutdown() {
  DCHECK(message_loop_->BelongsToCurrentThread());

  // Close all idle streams immediately.  The |close_timer_| will handle
  // invalidating any outstanding tasks upon its destruction.
  CloseAllIdleStreams();
}

bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
  DCHECK(message_loop_->BelongsToCurrentThread());
  AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
      params_, output_device_id_, input_device_id_);
  if (!stream)
    return false;

  if (!stream->Open()) {
    stream->Close();
    return false;
  }

  const int stream_id = audio_stream_id_++;
  audio_stream_ids_[stream] = stream_id;
  audio_log_->OnCreated(
      stream_id, params_, input_device_id_, output_device_id_);

  idle_streams_.push_back(stream);
  return true;
}

void AudioOutputDispatcherImpl::CloseAllIdleStreams() {
  DCHECK(message_loop_->BelongsToCurrentThread());
  CloseIdleStreams(0);
}

void AudioOutputDispatcherImpl::CloseIdleStreams(size_t keep_alive) {
  DCHECK(message_loop_->BelongsToCurrentThread());
  if (idle_streams_.size() <= keep_alive)
    return;
  for (size_t i = keep_alive; i < idle_streams_.size(); ++i) {
    AudioOutputStream* stream = idle_streams_[i];
    stream->Close();

    AudioStreamIDMap::iterator it = audio_stream_ids_.find(stream);
    DCHECK(it != audio_stream_ids_.end());
    audio_log_->OnClosed(it->second);
    audio_stream_ids_.erase(it);
  }
  idle_streams_.erase(idle_streams_.begin() + keep_alive, idle_streams_.end());
}

void AudioOutputDispatcherImpl::CloseStreamsForWedgeFix() {
  DCHECK(message_loop_->BelongsToCurrentThread());
  CloseAllIdleStreams();
}

void AudioOutputDispatcherImpl::RestartStreamsForWedgeFix() {
  DCHECK(message_loop_->BelongsToCurrentThread());

  // Should only be called when the dispatcher is used with fake streams which
  // don't need to be shutdown or restarted.
  CHECK_EQ(params_.format(), AudioParameters::AUDIO_FAKE);
}

}  // namespace media