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

#include "chrome/browser/devtools/serialize_host_descriptions.h"

#include <array>
#include <optional>
#include <utility>
#include <vector>

#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::Optional;
using ::testing::UnorderedElementsAre;

namespace {

HostDescriptionNode GetNodeWithLabel(const char* name, int label) {
  HostDescriptionNode node = {name, std::string(),
                              base::Value(base::Value::Type::DICT)};
  node.representation.GetDict().Set("label", label);
  return node;
}

// Returns the list of children of |arg|.
std::optional<base::ListValue> GetChildren(const base::Value& arg) {
  EXPECT_TRUE(arg.is_dict());
  const base::DictValue& dict = arg.GetDict();

  const base::Value* children = dict.Find("children");
  if (!children)
    return std::nullopt;
  EXPECT_EQ(base::Value::Type::LIST, children->type());
  return children->GetList().Clone();
}

// Checks that |arg| is a description of a node with label |l|.
bool CheckLabel(const base::Value& arg, int l) {
  EXPECT_TRUE(arg.is_dict());
  const base::DictValue& dict = arg.GetDict();
  std::optional<int> result = dict.FindInt("label");
  if (!result)
    return false;
  return l == *result;
}

// Matches every |arg| with label |label| and checks that it has no children.
MATCHER_P(EmptyNode, label, "") {
  if (!CheckLabel(arg, label))
    return false;
  EXPECT_EQ(GetChildren(arg), std::nullopt);
  return true;
}

}  // namespace

TEST(SerializeHostDescriptionTest, Empty) {
  base::ListValue result =
      SerializeHostDescriptions(std::vector<HostDescriptionNode>(), "123");
  EXPECT_THAT(result, ::testing::IsEmpty());
}

// Test serializing a forest of stubs (no edges).
TEST(SerializeHostDescriptionTest, Stubs) {
  std::vector<HostDescriptionNode> nodes;
  nodes.emplace_back(GetNodeWithLabel("1", 1));
  nodes.emplace_back(GetNodeWithLabel("2", 2));
  nodes.emplace_back(GetNodeWithLabel("3", 3));
  base::ListValue result =
      SerializeHostDescriptions(std::move(nodes), "children");
  EXPECT_THAT(result,
              UnorderedElementsAre(EmptyNode(1), EmptyNode(2), EmptyNode(3)));
}

// Test handling multiple nodes sharing the same name.
TEST(SerializeHostDescriptionTest, SameNames) {
  std::vector<HostDescriptionNode> nodes;
  nodes.emplace_back(GetNodeWithLabel("A", 1));
  nodes.emplace_back(GetNodeWithLabel("A", 2));
  nodes.emplace_back(GetNodeWithLabel("A", 3));
  nodes.emplace_back(GetNodeWithLabel("B", 4));
  nodes.emplace_back(GetNodeWithLabel("C", 5));

  base::ListValue result =
      SerializeHostDescriptions(std::move(nodes), "children");

  // Only the first node called "A", and both nodes "B" and "C" should be
  // returned.
  EXPECT_THAT(result,
              UnorderedElementsAre(EmptyNode(1), EmptyNode(4), EmptyNode(5)));
}

// Test serializing a small forest, of this structure:
// 5 -- 2 -- 4
// 0 -- 6
//   \ 1
//   \ 3

namespace {

// Matchers for non-empty nodes specifically in this test:
MATCHER(Node2, "") {
  if (!CheckLabel(arg, 2))
    return false;
  EXPECT_THAT(GetChildren(arg), Optional(UnorderedElementsAre(EmptyNode(4))));
  return true;
}

MATCHER(Node5, "") {
  if (!CheckLabel(arg, 5))
    return false;
  EXPECT_THAT(GetChildren(arg), Optional(UnorderedElementsAre(Node2())));
  return true;
}

MATCHER(Node0, "") {
  if (!CheckLabel(arg, 0))
    return false;
  EXPECT_THAT(GetChildren(arg), Optional(UnorderedElementsAre(
                                    EmptyNode(1), EmptyNode(3), EmptyNode(6))));
  return true;
}

}  // namespace

TEST(SerializeHostDescriptionTest, Forest) {
  std::vector<HostDescriptionNode> nodes(7);
  auto kNames = std::to_array<const char*>({"0", "1", "2", "3", "4", "5", "6"});
  for (size_t i = 0; i < 7; ++i) {
    nodes[i] = GetNodeWithLabel(kNames[i], i);
  }
  nodes[2].parent_name = "5";
  nodes[4].parent_name = "2";
  nodes[6].parent_name = "0";
  nodes[1].parent_name = "0";
  nodes[3].parent_name = "0";

  base::ListValue result =
      SerializeHostDescriptions(std::move(nodes), "children");

  EXPECT_THAT(result, UnorderedElementsAre(Node0(), Node5()));
}
