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

#ifndef BASE_TEST_VALUES_TEST_UTIL_H_
#define BASE_TEST_VALUES_TEST_UTIL_H_

#include <iosfwd>
#include <memory>
#include <string>
#include <string_view>

#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base::test {
namespace internal {

// Default parsing options for the json util functions. By default, the content
// will be parsed with the default set of Chromium-specific behaviours
// implemented in `JSONReader`, and additionally allowing trailing commas.
inline constexpr int kDefaultJsonParseOptions =
    JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS;

class DictionaryHasValueMatcher {
 public:
  DictionaryHasValueMatcher(std::string key, const Value& expected_value);
  DictionaryHasValueMatcher(std::string key, Value&& expected_value);

  DictionaryHasValueMatcher(const DictionaryHasValueMatcher&);
  DictionaryHasValueMatcher& operator=(const DictionaryHasValueMatcher&);
  DictionaryHasValueMatcher(DictionaryHasValueMatcher&&) = default;
  DictionaryHasValueMatcher& operator=(DictionaryHasValueMatcher&&) = default;

  ~DictionaryHasValueMatcher();

  bool MatchAndExplain(const DictValue& value,
                       testing::MatchResultListener* listener) const;
  bool MatchAndExplain(const Value& dict,
                       testing::MatchResultListener* listener) const;

  void DescribeTo(std::ostream* os) const;

  void DescribeNegationTo(std::ostream* os) const;

 private:
  std::string key_;
  Value expected_value_;
};

class DictionaryHasValuesMatcher {
 public:
  explicit DictionaryHasValuesMatcher(const DictValue& template_dict);
  explicit DictionaryHasValuesMatcher(DictValue&& template_dict);

  DictionaryHasValuesMatcher(const DictionaryHasValuesMatcher&);
  DictionaryHasValuesMatcher& operator=(const DictionaryHasValuesMatcher&);
  DictionaryHasValuesMatcher(DictionaryHasValuesMatcher&&) = default;
  DictionaryHasValuesMatcher& operator=(DictionaryHasValuesMatcher&&) = default;

  ~DictionaryHasValuesMatcher();

  bool MatchAndExplain(const DictValue& dict,
                       testing::MatchResultListener* listener) const;
  bool MatchAndExplain(const Value& dict,
                       testing::MatchResultListener* listener) const;

  void DescribeTo(std::ostream* os) const;

  void DescribeNegationTo(std::ostream* os) const;

 private:
  DictValue template_dict_;
};

class IsSupersetOfValueMatcher {
 public:
  explicit IsSupersetOfValueMatcher(std::string_view json);
  explicit IsSupersetOfValueMatcher(const Value& template_value);
  explicit IsSupersetOfValueMatcher(const DictValue& template_value);
  explicit IsSupersetOfValueMatcher(const ListValue& template_value);
  explicit IsSupersetOfValueMatcher(Value&& template_value);
  explicit IsSupersetOfValueMatcher(DictValue&& template_value);
  explicit IsSupersetOfValueMatcher(ListValue&& template_value);

  IsSupersetOfValueMatcher(const IsSupersetOfValueMatcher&);
  IsSupersetOfValueMatcher& operator=(const IsSupersetOfValueMatcher&);
  IsSupersetOfValueMatcher(IsSupersetOfValueMatcher&&) = default;
  IsSupersetOfValueMatcher& operator=(IsSupersetOfValueMatcher&&) = default;

  ~IsSupersetOfValueMatcher();

  bool MatchAndExplain(const Value& value,
                       testing::MatchResultListener* listener) const;
  bool MatchAndExplain(const DictValue& value,
                       testing::MatchResultListener* listener) const;
  bool MatchAndExplain(const ListValue& value,
                       testing::MatchResultListener* listener) const;

  void DescribeTo(std::ostream* os) const;

  void DescribeNegationTo(std::ostream* os) const;

 private:
  Value template_value_;
};

// A custom GMock matcher.  For details, see
// https://github.com/google/googletest/blob/644319b9f06f6ca9bf69fe791be399061044bc3d/googlemock/docs/CookBook.md#writing-new-polymorphic-matchers
class IsJsonMatcher {
 public:
  explicit IsJsonMatcher(std::string_view json);
  explicit IsJsonMatcher(const Value& value);
  explicit IsJsonMatcher(const DictValue& value);
  explicit IsJsonMatcher(const ListValue& value);
  explicit IsJsonMatcher(Value&& value);
  explicit IsJsonMatcher(DictValue&& value);
  explicit IsJsonMatcher(ListValue&& value);

  IsJsonMatcher(const IsJsonMatcher& other);
  IsJsonMatcher& operator=(const IsJsonMatcher& other);

  ~IsJsonMatcher();

  bool MatchAndExplain(std::string_view json,
                       testing::MatchResultListener* listener) const;
  bool MatchAndExplain(const Value& value,
                       testing::MatchResultListener* listener) const;
  bool MatchAndExplain(const DictValue& dict,
                       testing::MatchResultListener* listener) const;
  bool MatchAndExplain(const ListValue& list,
                       testing::MatchResultListener* listener) const;
  void DescribeTo(std::ostream* os) const;
  void DescribeNegationTo(std::ostream* os) const;

 private:
  Value expected_value_;
};

}  // namespace internal

// A custom GMock matcher which matches if a `base::Value` or
// `base::DictValue` has a key `key` that is equal to `expected_value`.
//
// Example usage:
//
//   base::Value expected_value = ...;
//   EXPECT_THAT(actual, DictionaryHasValue(key, expected_value));
template <typename T>
inline testing::PolymorphicMatcher<internal::DictionaryHasValueMatcher>
DictionaryHasValue(std::string key, T&& expected_value) {
  return testing::MakePolymorphicMatcher(internal::DictionaryHasValueMatcher(
      key, std::forward<T>(expected_value)));
}

// A custom GMock matcher which matches if a `base::Value` or
// `base::DictValue` contains all key/value pairs from `template_dict`.
//
// Example usage:
//
//   base::DictValue template_dict = ...;
//   EXPECT_THAT(actual, DictionaryHasValues(template_dict));
template <typename T>
inline testing::PolymorphicMatcher<internal::DictionaryHasValuesMatcher>
DictionaryHasValues(T&& template_dict) {
  return testing::MakePolymorphicMatcher(
      internal::DictionaryHasValuesMatcher(std::forward<T>(template_dict)));
}

// Matches when a `base::Value` or `base::DictValue` or `base::ListValue` is
// a superset of `template_value`, ignoring unexpected Dict keys and list items.
// Uses `testing::DoubleEq` when comparing doubles.
//
// Example usage:
//
//   base::Value template_value = ...;
//   EXPECT_THAT(actual, IsSupersetOfValue(template_value));
//
//   base::ListValue template_list = ...;
//   EXPECT_THAT(actual, IsSupersetOfValue(template_list));
//
//   base::DictValue template_dict = ...;
//   EXPECT_THAT(actual, IsSupersetOfValue(template_dict));
//
//   EXPECT_THAT(actual, IsSupersetOfValue(R"({"goats": 3})"));
template <typename T>
inline testing::PolymorphicMatcher<internal::IsSupersetOfValueMatcher>
IsSupersetOfValue(T&& template_value) {
  return testing::MakePolymorphicMatcher(
      internal::IsSupersetOfValueMatcher(std::forward<T>(template_value)));
}

// Creates a GMock matcher for testing equivalence of JSON values represented as
// either JSON strings or base::Value objects.  Parsing of the expected value
// uses ParseJson(), which allows trailing commas for convenience.  Parsing of
// the actual value follows the JSON spec strictly.
//
// It possible to use this matcher when the actual and expected values are both
// base::Value objects, in place of GMock's normal equality semantics. The
// advantage in doing so is that the IsJson matcher is copyable, which avoids
// the need for using something like `testing::Eq(std::cref(expected_value))`.
//
// Example usage:
//
//   base::Value value = ...;
//   EXPECT_THAT(actual, IsJson(value));
//
//   base::DictValue value_dict = ...;
//   EXPECT_THAT(actual, IsJson(value_dict));
//
//   base::ListValue value_list = ...;
//   EXPECT_THAT(actual, IsJson(value_list));
//
//   EXPECT_THAT(actual, IsJson(R"({"goats": 3})"));
template <typename T>
inline testing::PolymorphicMatcher<internal::IsJsonMatcher> IsJson(T&& value) {
  return testing::MakePolymorphicMatcher(
      internal::IsJsonMatcher(std::forward<T>(value)));
}

// Parses `json` as JSON, using the provided `options`, and returns the
// resulting value. If `json` fails to parse, causes an EXPECT failure and
// returns the Null Value.
Value ParseJson(std::string_view json,
                int options = internal::kDefaultJsonParseOptions);

// Just like ParseJson(), except returns Dicts/Lists. If `json` fails to parse
// or is not of the expected type, causes an EXPECT failure and returns an empty
// container.
DictValue ParseJsonDict(std::string_view json,
                        int options = internal::kDefaultJsonParseOptions);
ListValue ParseJsonList(std::string_view json,
                        int options = internal::kDefaultJsonParseOptions);

// Similar to `ParseJsonDict`, however it loads its contents from a file.
// Returns the parsed `DictValue` when successful. Otherwise, it causes an
// EXPECT failure, and returns an empty dict.
DictValue ParseJsonDictFromFile(const FilePath& json_file_path);

// An enumaration with the possible types of errors when calling
// `WriteJsonFile`.
enum class WriteJsonError {
  // Failed to generate a json string with the value provided.
  kGenerateJsonFailure,

  // Failed to write the json string into a file.
  kWriteFileFailure,
};

// Serialises `root` as a json string to a file. Returns a empty expected when
// successful. Otherwise returns an error.
expected<void, WriteJsonError> WriteJsonFile(const FilePath& json_file_path,
                                             ValueView root);

}  // namespace base::test

#endif  // BASE_TEST_VALUES_TEST_UTIL_H_
