// 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 <stdint.h>

#include <string>

#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "content/common/media/audio_messages.h"
#include "content/renderer/media/audio_message_filter.h"
#include "media/audio/audio_output_ipc.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {
namespace {

const int kRenderFrameId = 2;
const char kEmptyMatchedDeviceId[] = "";

class MockAudioDelegate : public media::AudioOutputIPCDelegate {
 public:
  MockAudioDelegate() {
    Reset();
  }
  ~MockAudioDelegate() override {
    if (handle_.IsValid())
      handle_.Close();
  }

  void OnError() override { error_received_ = true; }

  void OnDeviceAuthorized(media::OutputDeviceStatus device_status,
                          const media::AudioParameters& output_params,
                          const std::string& matched_device_id) override {
    device_authorized_received_ = true;
    device_status_ = device_status;
    output_params_ = output_params;
    matched_device_id_ = matched_device_id;
  }

  void OnStreamCreated(base::SharedMemoryHandle handle,
                       base::SyncSocket::Handle,
                       int length) override {
    created_received_ = true;
    handle_ = handle;
    length_ = length;
  }

  void OnIPCClosed() override {}

  void Reset() {
    error_received_ = false;

    device_authorized_received_ = false;
    output_params_ = media::AudioParameters();
    device_status_ = media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL;

    created_received_ = false;
    if (handle_.IsValid())
      handle_.Close();
    handle_ = base::SharedMemoryHandle();
    length_ = 0;

    volume_received_ = false;
    volume_ = 0;
  }

  bool error_received() { return error_received_; }

  bool device_authorized_received() { return device_authorized_received_; }
  media::AudioParameters output_params() { return output_params_; }
  media::OutputDeviceStatus device_status() { return device_status_; }

  bool created_received() { return created_received_; }
  base::SharedMemoryHandle handle() { return handle_; }
  uint32_t length() { return length_; }

 private:
  bool error_received_;

  bool device_authorized_received_;
  media::AudioParameters output_params_;
  media::OutputDeviceStatus device_status_;
  std::string matched_device_id_;

  bool created_received_;
  base::SharedMemoryHandle handle_;
  int length_;

  bool volume_received_;
  double volume_;

  DISALLOW_COPY_AND_ASSIGN(MockAudioDelegate);
};

media::AudioParameters MockOutputParams() {
  return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                                media::CHANNEL_LAYOUT_STEREO,
                                media::AudioParameters::kAudioCDSampleRate, 16,
                                100);
}

}  // namespace

TEST(AudioMessageFilterTest, Basic) {
  base::MessageLoopForIO message_loop;

  scoped_refptr<AudioMessageFilter> filter(
      new AudioMessageFilter(message_loop.task_runner()));

  MockAudioDelegate delegate;
  const std::unique_ptr<media::AudioOutputIPC> ipc =
      filter->CreateAudioOutputIPC(kRenderFrameId);
  static const int kSessionId = 0;
  static const std::string kDeviceId;
  static const url::Origin kSecurityOrigin;
  ipc->RequestDeviceAuthorization(&delegate, kSessionId, kDeviceId,
                                  kSecurityOrigin);
  ipc->CreateStream(&delegate, media::AudioParameters());
  static const int kStreamId = 1;
  EXPECT_EQ(&delegate, filter->delegates_.Lookup(kStreamId));

  // AudioMsg_NotifyDeviceAuthorized
  EXPECT_FALSE(delegate.device_authorized_received());
  filter->OnMessageReceived(AudioMsg_NotifyDeviceAuthorized(
      kStreamId, media::OUTPUT_DEVICE_STATUS_OK, MockOutputParams(),
      kEmptyMatchedDeviceId));
  EXPECT_TRUE(delegate.device_authorized_received());
  EXPECT_TRUE(delegate.output_params().Equals(MockOutputParams()));
  delegate.Reset();

  // AudioMsg_NotifyStreamCreated
  base::SyncSocket::TransitDescriptor socket_descriptor;
  const uint32_t kLength = 1024;
  EXPECT_FALSE(delegate.created_received());
  filter->OnMessageReceived(AudioMsg_NotifyStreamCreated(
      kStreamId, base::SharedMemoryHandle(), socket_descriptor, kLength));
  EXPECT_TRUE(delegate.created_received());
  EXPECT_FALSE(base::SharedMemory::IsHandleValid(delegate.handle()));
  EXPECT_EQ(kLength, delegate.length());
  delegate.Reset();

  // AudioMsg_NotifyStreamStateChanged
  EXPECT_FALSE(delegate.error_received());
  filter->OnMessageReceived(AudioMsg_NotifyStreamError(kStreamId));
  EXPECT_TRUE(delegate.error_received());
  delegate.Reset();

  ipc->CloseStream();
  EXPECT_EQ(static_cast<media::AudioOutputIPCDelegate*>(NULL),
            filter->delegates_.Lookup(kStreamId));
}

TEST(AudioMessageFilterTest, Delegates) {
  base::MessageLoopForIO message_loop;

  scoped_refptr<AudioMessageFilter> filter(
      new AudioMessageFilter(message_loop.task_runner()));

  MockAudioDelegate delegate1;
  MockAudioDelegate delegate2;
  const std::unique_ptr<media::AudioOutputIPC> ipc1 =
      filter->CreateAudioOutputIPC(kRenderFrameId);
  const std::unique_ptr<media::AudioOutputIPC> ipc2 =
      filter->CreateAudioOutputIPC(kRenderFrameId);
  ipc1->CreateStream(&delegate1, media::AudioParameters());
  ipc2->CreateStream(&delegate2, media::AudioParameters());
  static const int kStreamId1 = 1;
  static const int kStreamId2 = 2;
  EXPECT_EQ(&delegate1, filter->delegates_.Lookup(kStreamId1));
  EXPECT_EQ(&delegate2, filter->delegates_.Lookup(kStreamId2));

  // Send an IPC message. Make sure the correct delegate gets called.
  EXPECT_FALSE(delegate1.error_received());
  EXPECT_FALSE(delegate2.error_received());
  filter->OnMessageReceived(AudioMsg_NotifyStreamError(kStreamId1));
  EXPECT_TRUE(delegate1.error_received());
  EXPECT_FALSE(delegate2.error_received());
  delegate1.Reset();

  EXPECT_FALSE(delegate1.error_received());
  EXPECT_FALSE(delegate2.error_received());
  filter->OnMessageReceived(AudioMsg_NotifyStreamError(kStreamId2));
  EXPECT_FALSE(delegate1.error_received());
  EXPECT_TRUE(delegate2.error_received());
  delegate2.Reset();

  base::RunLoop().RunUntilIdle();

  ipc1->CloseStream();
  ipc2->CloseStream();
  EXPECT_EQ(static_cast<media::AudioOutputIPCDelegate*>(NULL),
            filter->delegates_.Lookup(kStreamId1));
  EXPECT_EQ(static_cast<media::AudioOutputIPCDelegate*>(NULL),
            filter->delegates_.Lookup(kStreamId2));
}

}  // namespace content
