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

#include "tools/json_schema_compiler/test/choices.h"

#include <stddef.h>

#include <utility>

#include "base/values.h"
#include "extensions/buildflags/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/json_schema_compiler/test/test_util.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace {

namespace choices = test::api::choices;
namespace TakesIntegers = choices::TakesIntegers;
using choices::NestedChoice;
using json_schema_compiler::test_util::Dictionary;
using json_schema_compiler::test_util::List;
using json_schema_compiler::test_util::ReadJson;
using json_schema_compiler::test_util::Vector;

TEST(JsonSchemaCompilerChoicesTest, TakesIntegersParamsCreate) {
  {
    std::optional<TakesIntegers::Params> params(
        TakesIntegers::Params::Create(List(base::Value(true)).GetList()));

    static_assert(!std::is_copy_constructible_v<TakesIntegers::Params>);
    static_assert(!std::is_copy_assignable_v<TakesIntegers::Params>);
    static_assert(std::is_move_constructible_v<TakesIntegers::Params>);
    static_assert(std::is_move_assignable_v<TakesIntegers::Params>);

    EXPECT_FALSE(params);
  }
  {
    std::optional<TakesIntegers::Params> params(
        TakesIntegers::Params::Create(List(base::Value(6)).GetList()));
    ASSERT_TRUE(params);
    EXPECT_FALSE(params->nums.as_integers);
    EXPECT_EQ(6, *params->nums.as_integer);
    EXPECT_EQ(6, *params->nums.Clone().as_integer);
  }
  {
    std::optional<TakesIntegers::Params> params(TakesIntegers::Params::Create(
        List(List(base::Value(2), base::Value(6), base::Value(8))).GetList()));
    ASSERT_TRUE(params);
    ASSERT_TRUE(params->nums.as_integers);
    EXPECT_EQ(Vector(2, 6, 8), *params->nums.as_integers);
    EXPECT_EQ(Vector(2, 6, 8), *params->nums.Clone().as_integers);
  }
}

TEST(JsonSchemaCompilerChoicesTest, ObjectWithChoicesParamsCreate) {
  {
    std::optional<choices::ObjectWithChoices::Params> params(
        choices::ObjectWithChoices::Params::Create(
            List(Dictionary("strings", base::Value("asdf"))).GetList()));
    ASSERT_TRUE(params);
    EXPECT_FALSE(params->string_info.strings.as_strings);
    EXPECT_EQ("asdf", *params->string_info.strings.as_string);
    EXPECT_EQ("asdf", *params->string_info.Clone().strings.as_string);
    EXPECT_FALSE(params->string_info.integers);
  }
  {
    std::optional<choices::ObjectWithChoices::Params> params(
        choices::ObjectWithChoices::Params::Create(
            List(Dictionary("strings", base::Value("asdf"), "integers",
                            base::Value(6)))
                .GetList()));
    ASSERT_TRUE(params);
    EXPECT_FALSE(params->string_info.strings.as_strings);
    EXPECT_EQ("asdf", *params->string_info.strings.as_string);
    EXPECT_EQ("asdf", *params->string_info.Clone().strings.as_string);
    ASSERT_TRUE(params->string_info.integers);
    EXPECT_FALSE(params->string_info.integers->as_integers);
    EXPECT_EQ(6, *params->string_info.integers->as_integer);
    EXPECT_EQ(6, *params->string_info.Clone().integers->as_integer);
  }
}

// TODO(kalman): Clean up the rest of these tests to use the
// Vector/List/Dictionary helpers.

TEST(JsonSchemaCompilerChoicesTest, ObjectWithChoicesParamsCreateFail) {
  {
    base::DictValue object_param;
    object_param.Set("strings", 5);
    base::ListValue params_value;
    params_value.Append(std::move(object_param));
    std::optional<choices::ObjectWithChoices::Params> params(
        choices::ObjectWithChoices::Params::Create(
            base::Value(std::move(params_value)).GetList()));
    EXPECT_FALSE(params.has_value());
  }
  {
    base::DictValue object_param;
    object_param.Set("strings", "asdf");
    object_param.Set("integers", "asdf");
    base::ListValue params_value;
    params_value.Append(std::move(object_param));
    std::optional<choices::ObjectWithChoices::Params> params(
        choices::ObjectWithChoices::Params::Create(
            base::Value(std::move(params_value)).GetList()));
    EXPECT_FALSE(params.has_value());
  }
  {
    base::DictValue object_param;
    object_param.Set("integers", 6);
    base::ListValue params_value;
    params_value.Append(std::move(object_param));
    std::optional<choices::ObjectWithChoices::Params> params(
        choices::ObjectWithChoices::Params::Create(
            base::Value(std::move(params_value)).GetList()));
    EXPECT_FALSE(params.has_value());
  }
}

TEST(JsonSchemaCompilerChoicesTest, PopulateChoiceType) {
  std::vector<std::string> strings = Vector(std::string("list"),
                                            std::string("of"),
                                            std::string("strings"));

  base::ListValue strings_value;
  for (const auto& string : strings)
    strings_value.Append(string);

  base::DictValue dict;
  dict.Set("integers", 4);
  dict.Set("strings", std::move(strings_value));

  auto out = choices::ChoiceType::FromValue(dict);
  ASSERT_TRUE(out);

  static_assert(!std::is_copy_constructible_v<choices::ChoiceType>);
  static_assert(!std::is_copy_assignable_v<choices::ChoiceType>);
  static_assert(std::is_move_constructible_v<choices::ChoiceType>);
  static_assert(std::is_move_assignable_v<choices::ChoiceType>);

  ASSERT_TRUE(out->integers.as_integer);
  EXPECT_FALSE(out->integers.as_integers);
  EXPECT_EQ(4, *out->integers.as_integer);

  EXPECT_FALSE(out->strings->as_string);
  ASSERT_TRUE(out->strings->as_strings);
  EXPECT_EQ(strings, *out->strings->as_strings);
}

TEST(JsonSchemaCompilerChoicesTest, ChoiceTypeToValue) {
  base::ListValue strings_value;
  strings_value.Append("list");
  strings_value.Append("of");
  strings_value.Append("strings");

  base::DictValue dict;
  dict.Set("integers", 5);
  dict.Set("strings", std::move(strings_value));

  auto out = choices::ChoiceType::FromValue(dict);
  ASSERT_TRUE(out);

  EXPECT_EQ(dict, out->ToValue());
}

TEST(JsonSchemaCompilerChoicesTest, ReturnChoices) {
  {
    choices::ReturnChoices::Results::Result results;
    results.as_integers = Vector<int>(1, 2);

    base::Value results_value(results.ToValue());

    base::ListValue expected;
    expected.Append(1);
    expected.Append(2);

    EXPECT_EQ(expected, results_value);
  }
  {
    choices::ReturnChoices::Results::Result results;
    results.as_integer = 5;

    base::Value results_value(results.ToValue());

    base::Value expected(5);

    EXPECT_EQ(expected, results_value);
  }
}

TEST(JsonSchemaCompilerChoicesTest, NestedChoices) {
  // These test both ToValue and FromValue for every legitimate configuration of
  // NestedChoices.
  {
    // The plain integer choice.
    base::Value value = ReadJson("42");
    std::optional<NestedChoice> obj = NestedChoice::FromValue(value);

    ASSERT_TRUE(obj);
    ASSERT_TRUE(obj->as_integer);
    EXPECT_FALSE(obj->as_choice1);
    EXPECT_FALSE(obj->as_choice2);
    EXPECT_EQ(42, *obj->as_integer);

    EXPECT_EQ(value, obj->ToValue());
  }

  {
    // The string choice within the first choice.
    base::Value value = ReadJson("\"foo\"");
    std::optional<NestedChoice> obj = NestedChoice::FromValue(value);

    ASSERT_TRUE(obj);
    EXPECT_FALSE(obj->as_integer);
    ASSERT_TRUE(obj->as_choice1);
    EXPECT_FALSE(obj->as_choice2);
    ASSERT_TRUE(obj->as_choice1->as_string);
    EXPECT_FALSE(obj->as_choice1->as_boolean);
    EXPECT_EQ("foo", *obj->as_choice1->as_string);

    EXPECT_EQ(value, obj->ToValue());
  }

  {
    // The boolean choice within the first choice.
    base::Value value = ReadJson("true");
    std::optional<NestedChoice> obj = NestedChoice::FromValue(value);

    ASSERT_TRUE(obj);
    EXPECT_FALSE(obj->as_integer);
    ASSERT_TRUE(obj->as_choice1);
    EXPECT_FALSE(obj->as_choice2);
    EXPECT_FALSE(obj->as_choice1->as_string);
    ASSERT_TRUE(obj->as_choice1->as_boolean);
    EXPECT_TRUE(*obj->as_choice1->as_boolean);

    EXPECT_EQ(value, obj->ToValue());
  }

  {
    // The double choice within the second choice.
    base::Value value = ReadJson("42.0");
    std::optional<NestedChoice> obj = NestedChoice::FromValue(value);

    ASSERT_TRUE(obj);
    EXPECT_FALSE(obj->as_integer);
    EXPECT_FALSE(obj->as_choice1);
    ASSERT_TRUE(obj->as_choice2);
    ASSERT_TRUE(obj->as_choice2->as_double_);
    EXPECT_FALSE(obj->as_choice2->as_choice_type);
    EXPECT_FALSE(obj->as_choice2->as_choice_types);
    EXPECT_EQ(42.0, *obj->as_choice2->as_double_);

    EXPECT_EQ(value, obj->ToValue());
  }

  {
    // The ChoiceType choice within the second choice.
    base::Value value =
        ReadJson("{\"integers\": [1, 2], \"strings\": \"foo\"}");
    std::optional<NestedChoice> obj = NestedChoice::FromValue(value);

    ASSERT_TRUE(obj);
    EXPECT_FALSE(obj->as_integer);
    EXPECT_FALSE(obj->as_choice1);
    ASSERT_TRUE(obj->as_choice2);
    EXPECT_FALSE(obj->as_choice2->as_double_);
    ASSERT_TRUE(obj->as_choice2->as_choice_type);
    EXPECT_FALSE(obj->as_choice2->as_choice_types);
    {
      const choices::ChoiceType& choice_type = *obj->as_choice2->as_choice_type;
      ASSERT_TRUE(choice_type.integers.as_integers);
      EXPECT_FALSE(choice_type.integers.as_integer);
      EXPECT_EQ(Vector(1, 2), *choice_type.integers.as_integers);
      ASSERT_TRUE(choice_type.strings);
      EXPECT_FALSE(choice_type.strings->as_strings);
      ASSERT_TRUE(choice_type.strings->as_string);
      EXPECT_EQ("foo", *choice_type.strings->as_string);
    }

    EXPECT_EQ(value, obj->ToValue());
  }

  {
    // The array of ChoiceTypes within the second choice.
    base::Value value = ReadJson(
        "["
        "  {\"integers\": [1, 2], \"strings\": \"foo\"},"
        "  {\"integers\": 3, \"strings\": [\"bar\", \"baz\"]}"
        "]");
    std::optional<NestedChoice> obj = NestedChoice::FromValue(value);

    ASSERT_TRUE(obj);
    EXPECT_FALSE(obj->as_integer);
    EXPECT_FALSE(obj->as_choice1);
    ASSERT_TRUE(obj->as_choice2);
    EXPECT_FALSE(obj->as_choice2->as_double_);
    EXPECT_FALSE(obj->as_choice2->as_choice_type);
    ASSERT_TRUE(obj->as_choice2->as_choice_types);
    // Bleh too much effort to test everything.
    ASSERT_EQ(2u, obj->as_choice2->as_choice_types->size());

    EXPECT_EQ(value, obj->ToValue());
  }
}

}  // namespace
