// Copyright (c) 2014 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 "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "content/browser/media/webrtc_internals.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/webrtc_content_browsertest_base.h"
#include "media/audio/audio_manager.h"
#include "net/test/embedded_test_server/embedded_test_server.h"

namespace {

const int kExpectedConsumerId = 0;

// Get the ID for the render process host when there should only be one.
bool GetRenderProcessHostId(base::ProcessId* id) {
  content::RenderProcessHost::iterator it(
      content::RenderProcessHost::AllHostsIterator());
  *id = base::GetProcId(it.GetCurrentValue()->GetHandle());
  EXPECT_NE(base::kNullProcessId, *id);
  if (*id == base::kNullProcessId)
    return false;
  it.Advance();
  EXPECT_TRUE(it.IsAtEnd());
  return it.IsAtEnd();
}

}  // namespace

namespace content {

class WebRtcAecDumpBrowserTest : public WebRtcContentBrowserTest {
 public:
  WebRtcAecDumpBrowserTest() {}
  ~WebRtcAecDumpBrowserTest() override {}
};

#if defined(OS_WIN)
#define IntToStringType base::IntToString16
#else
#define IntToStringType base::IntToString
#endif

#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
// Timing out on ARM linux bot: http://crbug.com/238490
#define MAYBE_CallWithAecDump DISABLED_CallWithAecDump
#elif defined(OS_ANDROID) && defined(ADDRESS_SANITIZER)
// Renderer crashes under Android ASAN: https://crbug.com/408496.
#define MAYBE_CallWithAecDump DISABLED_CallWithAecDump
#else
#define MAYBE_CallWithAecDump CallWithAecDump
#endif

// This tests will make a complete PeerConnection-based call, verify that
// video is playing for the call, and verify that a non-empty AEC dump file
// exists. The AEC dump is enabled through webrtc-internals. The HTML and
// Javascript is bypassed since it would trigger a file picker dialog. Instead,
// the dialog callback FileSelected() is invoked directly. In fact, there's
// never a webrtc-internals page opened at all since that's not needed.
IN_PROC_BROWSER_TEST_F(WebRtcAecDumpBrowserTest, MAYBE_CallWithAecDump) {
  if (!media::AudioManager::Get()->HasAudioOutputDevices()) {
    LOG(INFO) << "Missing output devices: skipping test...";
    return;
  }

  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());

  // We must navigate somewhere first so that the render process is created.
  NavigateToURL(shell(), GURL(""));

  base::FilePath dump_file;
  ASSERT_TRUE(CreateTemporaryFile(&dump_file));
  base::DeleteFile(dump_file, false);

  // This fakes the behavior of another open tab with webrtc-internals, and
  // enabling AEC dump in that tab.
  WebRTCInternals::GetInstance()->FileSelected(dump_file, -1, NULL);

  GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
  NavigateToURL(shell(), url);
  ExecuteJavascriptAndWaitForOk("call({video: true, audio: true});");

  EXPECT_FALSE(base::PathExists(dump_file));

  // Add file extensions that we expect to be added. The dump name will be
  // <temporary path>.<render process id>.<consumer id>, for example
  // "/tmp/.com.google.Chrome.Z6UC3P.12345.0".
  base::ProcessId render_process_id = base::kNullProcessId;
  EXPECT_TRUE(GetRenderProcessHostId(&render_process_id));
  dump_file = dump_file.AddExtension(IntToStringType(render_process_id))
                       .AddExtension(IntToStringType(kExpectedConsumerId));

  EXPECT_TRUE(base::PathExists(dump_file));
  int64 file_size = 0;
  EXPECT_TRUE(base::GetFileSize(dump_file, &file_size));
  EXPECT_GT(file_size, 0);

  base::DeleteFile(dump_file, false);
}

// TODO(grunell): Add test for multiple dumps when re-use of
// MediaStreamAudioProcessor in AudioCapturer has been removed.

#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
// Timing out on ARM linux bot: http://crbug.com/238490
#define MAYBE_CallWithAecDumpEnabledThenDisabled DISABLED_CallWithAecDumpEnabledThenDisabled
#elif defined(OS_ANDROID) && defined(ADDRESS_SANITIZER)
// Renderer crashes under Android ASAN: https://crbug.com/408496.
#define MAYBE_CallWithAecDumpEnabledThenDisabled DISABLED_CallWithAecDumpEnabledThenDisabled
#else
#define MAYBE_CallWithAecDumpEnabledThenDisabled CallWithAecDumpEnabledThenDisabled
#endif

// As above, but enable and disable dump before starting a call. The file should
// be created, but should be empty.
IN_PROC_BROWSER_TEST_F(WebRtcAecDumpBrowserTest,
                       MAYBE_CallWithAecDumpEnabledThenDisabled) {
  if (!media::AudioManager::Get()->HasAudioOutputDevices()) {
    LOG(INFO) << "Missing output devices: skipping test...";
    return;
  }

  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());

  // We must navigate somewhere first so that the render process is created.
  NavigateToURL(shell(), GURL(""));

  base::FilePath dump_file;
  ASSERT_TRUE(CreateTemporaryFile(&dump_file));
  base::DeleteFile(dump_file, false);

  // This fakes the behavior of another open tab with webrtc-internals, and
  // enabling AEC dump in that tab, then disabling it.
  WebRTCInternals::GetInstance()->FileSelected(dump_file, -1, NULL);
  WebRTCInternals::GetInstance()->DisableAecDump();

  GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
  NavigateToURL(shell(), url);
  ExecuteJavascriptAndWaitForOk("call({video: true, audio: true});");

  // Add file extensions that we expect to be added.
  base::ProcessId render_process_id = base::kNullProcessId;
  EXPECT_TRUE(GetRenderProcessHostId(&render_process_id));
  dump_file = dump_file.AddExtension(IntToStringType(render_process_id))
                       .AddExtension(IntToStringType(kExpectedConsumerId));

  EXPECT_FALSE(base::PathExists(dump_file));

  base::DeleteFile(dump_file, false);
}

// Timing out on ARM linux bot: http://crbug.com/238490
// Renderer crashes under Android ASAN: https://crbug.com/408496.
// Flaky on XP and Mac: http://crbug.com/425034.
IN_PROC_BROWSER_TEST_F(WebRtcAecDumpBrowserTest, DISABLED_TwoCallsWithAecDump) {
  if (OnWinXp()) {
    LOG(INFO) << "Disabled on Win XP: skipping test...";
    return;
  }
  if (!media::AudioManager::Get()->HasAudioOutputDevices()) {
    LOG(INFO) << "Missing output devices: skipping test...";
    return;
  }

  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());

  // We must navigate somewhere first so that the render process is created.
  NavigateToURL(shell(), GURL(""));

  // Create a second window.
  Shell* shell2 = CreateBrowser();
  NavigateToURL(shell2, GURL(""));

  base::FilePath dump_file;
  ASSERT_TRUE(CreateTemporaryFile(&dump_file));
  base::DeleteFile(dump_file, false);

  // This fakes the behavior of another open tab with webrtc-internals, and
  // enabling AEC dump in that tab.
  WebRTCInternals::GetInstance()->FileSelected(dump_file, -1, NULL);

  GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));

  NavigateToURL(shell(), url);
  NavigateToURL(shell2, url);
  ExecuteJavascriptAndWaitForOk("call({video: true, audio: true});");
  std::string result;
  EXPECT_TRUE(ExecuteScriptAndExtractString(
      shell2->web_contents(),
      "call({video: true, audio: true});",
      &result));
  ASSERT_STREQ("OK", result.c_str());

  EXPECT_FALSE(base::PathExists(dump_file));

  RenderProcessHost::iterator it =
      content::RenderProcessHost::AllHostsIterator();
  for (; !it.IsAtEnd(); it.Advance()) {
    base::ProcessId render_process_id =
        base::GetProcId(it.GetCurrentValue()->GetHandle());
    EXPECT_NE(base::kNullProcessId, render_process_id);

    // Add file extensions that we expect to be added.
    base::FilePath unique_dump_file =
        dump_file.AddExtension(IntToStringType(render_process_id))
                 .AddExtension(IntToStringType(kExpectedConsumerId));

    EXPECT_TRUE(base::PathExists(unique_dump_file));
    int64 file_size = 0;
    EXPECT_TRUE(base::GetFileSize(unique_dump_file, &file_size));
    EXPECT_GT(file_size, 0);

    base::DeleteFile(unique_dump_file, false);
  }
}

}  // namespace content
