// 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 "content/browser/media/media_internals.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_reader.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/audio/audio_parameters.h"
#include "media/base/channel_layout.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
const int kTestComponentID = 0;
const char kTestInputDeviceID[] = "test-input-id";
const char kTestOutputDeviceID[] = "test-output-id";
}  // namespace

namespace content {

class MediaInternalsTest
    : public testing::TestWithParam<media::AudioLogFactory::AudioComponent> {
 public:
  MediaInternalsTest()
      : media_internals_(MediaInternals::GetInstance()),
        update_cb_(base::Bind(&MediaInternalsTest::UpdateCallbackImpl,
                              base::Unretained(this))),
        test_params_(media::AudioParameters::AUDIO_PCM_LINEAR,
                     media::CHANNEL_LAYOUT_MONO,
                     48000,
                     16,
                     128),
        test_component_(GetParam()),
        audio_log_(media_internals_->CreateAudioLog(test_component_)) {
    media_internals_->AddUpdateCallback(update_cb_);
  }

  virtual ~MediaInternalsTest() {
    media_internals_->RemoveUpdateCallback(update_cb_);
  }

 protected:
  // Extracts and deserializes the JSON update data; merges into |update_data_|.
  void UpdateCallbackImpl(const base::string16& update) {
    // Each update string looks like "<JavaScript Function Name>({<JSON>});", to
    // use the JSON reader we need to strip out the JavaScript code.
    std::string utf8_update = base::UTF16ToUTF8(update);
    const std::string::size_type first_brace = utf8_update.find('{');
    const std::string::size_type last_brace = utf8_update.rfind('}');
    scoped_ptr<base::Value> output_value(base::JSONReader::Read(
        utf8_update.substr(first_brace, last_brace - first_brace + 1)));
    CHECK(output_value);

    base::DictionaryValue* output_dict = NULL;
    CHECK(output_value->GetAsDictionary(&output_dict));
    update_data_.MergeDictionary(output_dict);
  }

  void ExpectInt(const std::string& key, int expected_value) {
    int actual_value = 0;
    ASSERT_TRUE(update_data_.GetInteger(key, &actual_value));
    EXPECT_EQ(expected_value, actual_value);
  }

  void ExpectString(const std::string& key, const std::string& expected_value) {
    std::string actual_value;
    ASSERT_TRUE(update_data_.GetString(key, &actual_value));
    EXPECT_EQ(expected_value, actual_value);
  }

  void ExpectStatus(const std::string& expected_value) {
    ExpectString("status", expected_value);
  }

  TestBrowserThreadBundle thread_bundle_;
  MediaInternals* const media_internals_;
  MediaInternals::UpdateCallback update_cb_;
  base::DictionaryValue update_data_;
  const media::AudioParameters test_params_;
  const media::AudioLogFactory::AudioComponent test_component_;
  scoped_ptr<media::AudioLog> audio_log_;
};

TEST_P(MediaInternalsTest, AudioLogCreateStartStopErrorClose) {
  audio_log_->OnCreated(
      kTestComponentID, test_params_, kTestInputDeviceID, kTestOutputDeviceID);
  base::RunLoop().RunUntilIdle();

  ExpectString("channel_layout",
               media::ChannelLayoutToString(test_params_.channel_layout()));
  ExpectInt("sample_rate", test_params_.sample_rate());
  ExpectInt("frames_per_buffer", test_params_.frames_per_buffer());
  ExpectInt("channels", test_params_.channels());
  ExpectInt("input_channels", test_params_.input_channels());
  ExpectString("output_device_id", kTestOutputDeviceID);
  ExpectString("input_device_id", kTestInputDeviceID);
  ExpectInt("component_id", kTestComponentID);
  ExpectInt("component_type", test_component_);
  ExpectStatus("created");

  // Verify OnStarted().
  audio_log_->OnStarted(kTestComponentID);
  base::RunLoop().RunUntilIdle();
  ExpectStatus("started");

  // Verify OnStopped().
  audio_log_->OnStopped(kTestComponentID);
  base::RunLoop().RunUntilIdle();
  ExpectStatus("stopped");

  // Verify OnError().
  const char kErrorKey[] = "error_occurred";
  std::string no_value;
  ASSERT_FALSE(update_data_.GetString(kErrorKey, &no_value));
  audio_log_->OnError(kTestComponentID);
  base::RunLoop().RunUntilIdle();
  ExpectString(kErrorKey, "true");

  // Verify OnClosed().
  audio_log_->OnClosed(kTestComponentID);
  base::RunLoop().RunUntilIdle();
  ExpectStatus("closed");
}

TEST_P(MediaInternalsTest, AudioLogCreateClose) {
  audio_log_->OnCreated(
      kTestComponentID, test_params_, kTestInputDeviceID, kTestOutputDeviceID);
  base::RunLoop().RunUntilIdle();
  ExpectStatus("created");

  audio_log_->OnClosed(kTestComponentID);
  base::RunLoop().RunUntilIdle();
  ExpectStatus("closed");
}

INSTANTIATE_TEST_CASE_P(
    MediaInternalsTest, MediaInternalsTest, testing::Values(
        media::AudioLogFactory::AUDIO_INPUT_CONTROLLER,
        media::AudioLogFactory::AUDIO_OUTPUT_CONTROLLER,
        media::AudioLogFactory::AUDIO_OUTPUT_STREAM));

}  // namespace content