// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/audio/local_muter.h"

#include <memory>

#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/unguessable_token.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "services/audio/loopback_coordinator.h"
#include "services/audio/loopback_source.h"
#include "services/audio/test/mock_loopback_source.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::UnguessableToken;

using testing::InvokeWithoutArgs;
using testing::Mock;
using testing::StrictMock;

namespace audio {
namespace {

TEST(LocalMuterTest, MutesExistingMembers) {
  LoopbackCoordinator coordinator;

  // Create a group with two members.
  const UnguessableToken group_id = UnguessableToken::Create();
  StrictMock<MockLoopbackSource> member1;
  StrictMock<MockLoopbackSource> member2;

  // Create another group with one member, which should never have its mute
  // state changed.
  const UnguessableToken other_group_id = UnguessableToken::Create();
  ASSERT_NE(group_id, other_group_id);
  StrictMock<MockLoopbackSource> non_member;
  EXPECT_CALL(non_member, StartMuting()).Times(0);
  EXPECT_CALL(non_member, StopMuting()).Times(0);

  // When the members join the group, no mute change should occur.
  coordinator.AddMember(group_id, &member1);
  coordinator.AddMember(group_id, &member2);

  // When the muter is created, both members should be muted.
  EXPECT_CALL(member1, StartMuting());
  EXPECT_CALL(member2, StartMuting());
  auto muter = std::make_unique<LocalMuter>(&coordinator, group_id);
  Mock::VerifyAndClearExpectations(&member1);
  Mock::VerifyAndClearExpectations(&member2);

  // When the muter is destroyed, both members should be un-muted.
  EXPECT_CALL(member1, StopMuting());
  EXPECT_CALL(member2, StopMuting());
  muter = nullptr;
  Mock::VerifyAndClearExpectations(&member1);
  Mock::VerifyAndClearExpectations(&member2);

  coordinator.RemoveMember(&member1);
  coordinator.RemoveMember(&member2);
}

TEST(LocalMuterTest, MutesJoiningMembers) {
  LoopbackCoordinator coordinator;
  const UnguessableToken group_id = UnguessableToken::Create();

  LocalMuter muter(&coordinator, group_id);

  StrictMock<MockLoopbackSource> member;

  // Since muting is in-effect, the group member is immediately muted when
  // joining the group.
  EXPECT_CALL(member, StartMuting());
  coordinator.AddMember(group_id, &member);
  Mock::VerifyAndClearExpectations(&member);

  // Leaving the group should have no effect on the mute state of the member.
  EXPECT_CALL(member, StartMuting()).Times(0);
  EXPECT_CALL(member, StopMuting()).Times(0);
  coordinator.RemoveMember(&member);
  Mock::VerifyAndClearExpectations(&member);
}

TEST(LocalMuter, UnmutesWhenLastBindingIsLost) {
  base::test::TaskEnvironment task_environment;
  LoopbackCoordinator coordinator;
  const UnguessableToken group_id = UnguessableToken::Create();

  // Later in this test, once both bindings have been closed, the following
  // callback should be run. The callback will delete the LocalMuter in the same
  // stack as the mojo connection error handler, just as would take place in the
  // live build.
  auto muter = std::make_unique<LocalMuter>(&coordinator, group_id);
  base::MockCallback<base::RepeatingClosure> callback;
  EXPECT_CALL(callback, Run()).WillOnce(InvokeWithoutArgs([&muter]() {
    muter.reset();
  }));
  muter->SetAllBindingsLostCallback(callback.Get());

  // Create two bindings to the muter.
  mojo::AssociatedRemote<media::mojom::LocalMuter> remote_muter1;
  muter->AddReceiver(remote_muter1.BindNewEndpointAndPassReceiver());
  mojo::AssociatedRemote<media::mojom::LocalMuter> remote_muter2;
  muter->AddReceiver(remote_muter2.BindNewEndpointAndPassReceiver());

  // A member joins the group and should be muted.
  StrictMock<MockLoopbackSource> member;
  EXPECT_CALL(member, StartMuting());
  coordinator.AddMember(group_id, &member);
  Mock::VerifyAndClearExpectations(&member);

  // Nothing happens to the member when one of the bindings is closed.
  EXPECT_CALL(member, StopMuting()).Times(0);
  remote_muter1.reset();
  task_environment.RunUntilIdle();  // Propagate mojo tasks.
  Mock::VerifyAndClearExpectations(&member);

  // The member is unmuted once the second binding is closed.
  EXPECT_CALL(member, StopMuting());
  remote_muter2.reset();
  task_environment.RunUntilIdle();  // Propagate mojo tasks.
  Mock::VerifyAndClearExpectations(&member);

  // At this point, the LocalMuter should have been destroyed.
  EXPECT_FALSE(muter);

  coordinator.RemoveMember(&member);
}

}  // namespace
}  // namespace audio
