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

#include "components/performance_manager/public/voting/voting.h"

#include "base/test/gtest_util.h"
#include "components/performance_manager/test_support/voting.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace performance_manager {

namespace {

using ::testing::AssertionFailure;
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;

using TestVote = voting::Vote<void, int, 0>;
using TestVotingChannel = voting::VotingChannel<TestVote>;
using TestVotingChannelFactory = voting::VotingChannelFactory<TestVote>;

using DummyVoteObserver = voting::test::DummyVoteObserver<TestVote>;

// Some dummy contexts.
const void* kDummyContext1 = reinterpret_cast<const void*>(0xDEADBEEF);
const void* kDummyContext2 = reinterpret_cast<const void*>(0xBAADF00D);

static const char kReason[] = "reason";

}  // namespace

TEST(VotingTest, SimpleVoter) {
  DummyVoteObserver observer;

  TestVotingChannel voting_channel = observer.BuildVotingChannel();
  voting::VoterId<TestVote> voter_id = voting_channel.voter_id();

  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));

  voting_channel.SubmitVote(kDummyContext1, TestVote(5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));

  voting_channel.ChangeVote(kDummyContext1, TestVote(10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 10, kReason));

  voting_channel.InvalidateVote(kDummyContext1);
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
}

// Tests that an observer can receive votes for different contexts from the same
// voting channel.
TEST(VotingTest, OneVoterMultipleContexts) {
  DummyVoteObserver observer;

  TestVotingChannel voting_channel = observer.BuildVotingChannel();
  voting::VoterId<TestVote> voter_id = voting_channel.voter_id();

  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));

  voting_channel.SubmitVote(kDummyContext1, TestVote(5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext2));

  voting_channel.SubmitVote(kDummyContext2, TestVote(100, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext2, 100, kReason));

  voting_channel.ChangeVote(kDummyContext1, TestVote(10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext2, 100, kReason));

  voting_channel.InvalidateVote(kDummyContext1);
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext2, 100, kReason));

  voting_channel.InvalidateVote(kDummyContext2);
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext2));
}

// Tests that an observer can receive votes from more than one voting channel.
TEST(VotingTest, TwoVoter) {
  DummyVoteObserver observer;

  TestVotingChannel voting_channel_1 = observer.BuildVotingChannel();
  voting::VoterId<TestVote> voter_id_1 = voting_channel_1.voter_id();

  TestVotingChannel voting_channel_2 = observer.BuildVotingChannel();
  voting::VoterId<TestVote> voter_id_2 = voting_channel_2.voter_id();

  EXPECT_FALSE(observer.HasVote(voter_id_1, kDummyContext1));
  EXPECT_FALSE(observer.HasVote(voter_id_2, kDummyContext1));

  voting_channel_1.SubmitVote(kDummyContext1, TestVote(5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id_1, kDummyContext1, 5, kReason));
  EXPECT_FALSE(observer.HasVote(voter_id_2, kDummyContext1));

  voting_channel_2.SubmitVote(kDummyContext1, TestVote(5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id_1, kDummyContext1, 5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id_2, kDummyContext1, 5, kReason));

  voting_channel_1.ChangeVote(kDummyContext1, TestVote(10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id_1, kDummyContext1, 10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id_2, kDummyContext1, 5, kReason));

  voting_channel_2.ChangeVote(kDummyContext1, TestVote(10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id_1, kDummyContext1, 10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id_2, kDummyContext1, 10, kReason));

  voting_channel_1.InvalidateVote(kDummyContext1);
  EXPECT_FALSE(observer.HasVote(voter_id_1, kDummyContext1));
  EXPECT_TRUE(observer.HasVote(voter_id_2, kDummyContext1, 10, kReason));

  voting_channel_2.InvalidateVote(kDummyContext1);
  EXPECT_FALSE(observer.HasVote(voter_id_1, kDummyContext1));
  EXPECT_FALSE(observer.HasVote(voter_id_2, kDummyContext1));
}

TEST(VotingTest, ResetVotingChannel) {
  DummyVoteObserver observer;

  TestVotingChannel voting_channel = observer.BuildVotingChannel();
  EXPECT_TRUE(voting_channel.IsValid());

  voting_channel.Reset();

  EXPECT_FALSE(voting_channel.IsValid());
}

// Tests that VotingChannel supports move sementics.
TEST(VotingTest, MoveVotingChannel) {
  DummyVoteObserver observer;

  // Build the voting channel.
  TestVotingChannel voting_channel_1 = observer.BuildVotingChannel();
  voting::VoterId<TestVote> voter_id = voting_channel_1.voter_id();
  EXPECT_TRUE(voting_channel_1.IsValid());

  // Cast a vote with that voting channel.
  voting_channel_1.SubmitVote(kDummyContext1, TestVote(5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));

  // Move the voting channel.
  TestVotingChannel voting_channel_2 = std::move(voting_channel_1);
  EXPECT_TRUE(voting_channel_2.IsValid());

  // Use the second variable to change the vote.
  voting_channel_2.ChangeVote(kDummyContext1, TestVote(10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 10, kReason));

  // Move the voting channel back using the move assignment operator.
  voting_channel_1 = std::move(voting_channel_2);
  EXPECT_TRUE(voting_channel_1.IsValid());

  // Invalidate the vote.
  voting_channel_1.InvalidateVote(kDummyContext1);
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
}

// Tests that VotingChannel::SetVote can submit, change, and remove votes.
TEST(VotingTest, SetVote) {
  DummyVoteObserver observer;

  TestVotingChannel voting_channel = observer.BuildVotingChannel();
  voting::VoterId<TestVote> voter_id = voting_channel.voter_id();

  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));

  // Setting a vote on a context without a vote.
  voting_channel.SetVote(kDummyContext1, TestVote(5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));

  // Setting the exact same vote again is a safe no-op.
  voting_channel.SetVote(kDummyContext1, TestVote(5, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));

  // Updating the vote for an existing context.
  voting_channel.SetVote(kDummyContext1, TestVote(10, kReason));
  EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 10, kReason));

  // Removing the vote with std::nullopt.
  voting_channel.SetVote(kDummyContext1, std::nullopt);
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));

  // Setting std::nullopt for a non-existing vote is a safe no-op.
  voting_channel.SetVote(kDummyContext1, std::nullopt);
  EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
}

// Tests that destroying a VotingChannelFactory before all of its VotingChannels
// results in a DCHECK.
TEST(VotingTest, DestroyFactoryBeforeChannel) {
  TestVotingChannel voting_channel;

  auto observer = std::make_unique<DummyVoteObserver>();

  voting_channel = observer->BuildVotingChannel();

  EXPECT_DCHECK_DEATH(observer.reset());

  // Clean up.
  voting_channel.Reset();
}

}  // namespace performance_manager
