// Copyright 2013 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/sounds/audio_stream_handler.h"

#include <stdint.h>
#include <string>
#include <utility>

#include "base/cancelable_callback.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/sounds/wav_audio_handler.h"
#include "media/base/channel_layout.h"

namespace media {

namespace {

// Volume percent.
const double kOutputVolumePercent = 0.8;

// The number of frames each OnMoreData() call will request.
const int kDefaultFrameCount = 1024;

// Keep alive timeout for audio stream.
const int kKeepAliveMs = 1500;

AudioStreamHandler::TestObserver* g_observer_for_testing = NULL;
AudioOutputStream::AudioSourceCallback* g_audio_source_for_testing = NULL;

}  // namespace

class AudioStreamHandler::AudioStreamContainer
    : public AudioOutputStream::AudioSourceCallback {
 public:
  explicit AudioStreamContainer(std::unique_ptr<WavAudioHandler> wav_audio)
      : audio_manager_(AudioManager::Get()),
        started_(false),
        stream_(NULL),
        cursor_(0),
        delayed_stop_posted_(false),
        wav_audio_(std::move(wav_audio)) {
    DCHECK(audio_manager_);
    DCHECK(wav_audio_);
  }

  ~AudioStreamContainer() override {
    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
  }

  void Play() {
    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());

    if (!stream_) {
      const AudioParameters params(
          AudioParameters::AUDIO_PCM_LOW_LATENCY,
          GuessChannelLayout(wav_audio_->num_channels()),
          wav_audio_->sample_rate(), wav_audio_->bits_per_sample(),
          kDefaultFrameCount);
      stream_ =
          audio_manager_->MakeAudioOutputStreamProxy(params, std::string());
      if (!stream_ || !stream_->Open()) {
        LOG(ERROR) << "Failed to open an output stream.";
        return;
      }
      stream_->SetVolume(kOutputVolumePercent);
    }

    {
      base::AutoLock al(state_lock_);

      delayed_stop_posted_ = false;
      stop_closure_.Reset(base::Bind(&AudioStreamContainer::StopStream,
                                     base::Unretained(this)));

      if (started_) {
        if (wav_audio_->AtEnd(cursor_))
          cursor_ = 0;
        return;
      }

      cursor_ = 0;
    }

    started_ = true;
    if (g_audio_source_for_testing)
      stream_->Start(g_audio_source_for_testing);
    else
      stream_->Start(this);

    if (g_observer_for_testing)
      g_observer_for_testing->OnPlay();
  }

  void Stop() {
    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());

    StopStream();
    if (stream_)
      stream_->Close();
    stream_ = NULL;
    stop_closure_.Cancel();
  }

 private:
  // AudioOutputStream::AudioSourceCallback overrides:
  // Following methods could be called from *ANY* thread.
  int OnMoreData(base::TimeDelta /* delay */,
                 base::TimeTicks /* delay_timestamp */,
                 int /* prior_frames_skipped */,
                 AudioBus* dest) override {
    base::AutoLock al(state_lock_);
    size_t bytes_written = 0;

    if (wav_audio_->AtEnd(cursor_) ||
        !wav_audio_->CopyTo(dest, cursor_, &bytes_written)) {
      if (delayed_stop_posted_)
        return 0;
      delayed_stop_posted_ = true;
      audio_manager_->GetTaskRunner()->PostDelayedTask(
          FROM_HERE, stop_closure_.callback(),
          base::TimeDelta::FromMilliseconds(kKeepAliveMs));
      return 0;
    }
    cursor_ += bytes_written;
    return dest->frames();
  }

  void OnError(AudioOutputStream* /* stream */) override {
    LOG(ERROR) << "Error during system sound reproduction.";
    audio_manager_->GetTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&AudioStreamContainer::Stop, base::Unretained(this)));
  }

  void StopStream() {
    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());

    if (stream_ && started_) {
      // Do not hold the |state_lock_| while stopping the output stream.
      stream_->Stop();
      if (g_observer_for_testing)
        g_observer_for_testing->OnStop(cursor_);
    }

    started_ = false;
  }

  // Must only be accessed on the AudioManager::GetTaskRunner() thread.
  AudioManager* const audio_manager_;
  bool started_;
  AudioOutputStream* stream_;

  // All variables below must be accessed under |state_lock_| when |started_|.
  base::Lock state_lock_;
  size_t cursor_;
  bool delayed_stop_posted_;
  std::unique_ptr<WavAudioHandler> wav_audio_;
  base::CancelableClosure stop_closure_;

  DISALLOW_COPY_AND_ASSIGN(AudioStreamContainer);
};

AudioStreamHandler::AudioStreamHandler(const base::StringPiece& wav_data) {
  AudioManager* manager = AudioManager::Get();
  if (!manager) {
    LOG(ERROR) << "Can't get access to audio manager.";
    return;
  }

  std::unique_ptr<WavAudioHandler> wav_audio =
      WavAudioHandler::Create(wav_data);
  if (!wav_audio) {
    LOG(ERROR) << "wav_data is not valid";
    return;
  }

  const AudioParameters params(
      AudioParameters::AUDIO_PCM_LOW_LATENCY,
      GuessChannelLayout(wav_audio->num_channels()), wav_audio->sample_rate(),
      wav_audio->bits_per_sample(), kDefaultFrameCount);
  if (!params.IsValid()) {
    LOG(ERROR) << "Audio params are invalid.";
    return;
  }

  // Store the duration of the WAV data then pass the handler to |stream_|.
  duration_ = wav_audio->GetDuration();
  stream_.reset(new AudioStreamContainer(std::move(wav_audio)));
}

AudioStreamHandler::~AudioStreamHandler() {
  DCHECK(CalledOnValidThread());
  if (IsInitialized()) {
    AudioManager::Get()->GetTaskRunner()->PostTask(
        FROM_HERE, base::Bind(&AudioStreamContainer::Stop,
                              base::Unretained(stream_.get())));
    AudioManager::Get()->GetTaskRunner()->DeleteSoon(FROM_HERE,
                                                     stream_.release());
  }
}

bool AudioStreamHandler::IsInitialized() const {
  DCHECK(CalledOnValidThread());
  return !!stream_;
}

bool AudioStreamHandler::Play() {
  DCHECK(CalledOnValidThread());

  if (!IsInitialized())
    return false;

  AudioManager::Get()->GetTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(base::IgnoreResult(&AudioStreamContainer::Play),
                 base::Unretained(stream_.get())));
  return true;
}

void AudioStreamHandler::Stop() {
  DCHECK(CalledOnValidThread());

  if (!IsInitialized())
    return;

  AudioManager::Get()->GetTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(&AudioStreamContainer::Stop, base::Unretained(stream_.get())));
}

base::TimeDelta AudioStreamHandler::duration() const {
  DCHECK(CalledOnValidThread());
  return duration_;
}

// static
void AudioStreamHandler::SetObserverForTesting(TestObserver* observer) {
  g_observer_for_testing = observer;
}

// static
void AudioStreamHandler::SetAudioSourceForTesting(
    AudioOutputStream::AudioSourceCallback* source) {
  g_audio_source_for_testing = source;
}

}  // namespace media
