// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_

#include <any>
#include <cassert>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <variant>
#include <vector>

// Unless overridden, attempt to detect the RTTI state from the compiler.
#ifndef FLUTTER_ENABLE_RTTI
#if defined(_MSC_VER)
#ifdef _CPPRTTI
#define FLUTTER_ENABLE_RTTI 1
#endif
#elif defined(__clang__)
#if __has_feature(cxx_rtti)
#define FLUTTER_ENABLE_RTTI 1
#endif
#elif defined(__GNUC__)
#ifdef __GXX_RTTI
#define FLUTTER_ENABLE_RTTI 1
#endif
#endif
#endif  // #ifndef FLUTTER_ENABLE_RTTI

namespace flutter {

static_assert(sizeof(double) == 8, "EncodableValue requires a 64-bit double");

// A container for arbitrary types in EncodableValue.
//
// This is used in conjunction with StandardCodecExtension to allow using other
// types with a StandardMethodCodec/StandardMessageCodec. It is implicitly
// convertible to EncodableValue, so constructing an EncodableValue from a
// custom type can generally be written as:
//   CustomEncodableValue(MyType(...))
// rather than:
//   EncodableValue(CustomEncodableValue(MyType(...)))
//
// For extracting received custom types, it is implicitly convertible to
// std::any. For example:
//   const MyType& my_type_value =
//        std::any_cast<MyType>(std::get<CustomEncodableValue>(value));
//
// If RTTI is enabled, different extension types can be checked with type():
//   if (custom_value->type() == typeid(SomeData)) { ... }
// Clients that wish to disable RTTI would need to decide on another approach
// for distinguishing types (e.g., in StandardCodecExtension::WriteValueOfType)
// if multiple custom types are needed. For instance, wrapping all of the
// extension types in an EncodableValue-style variant, and only ever storing
// that variant in CustomEncodableValue.
class CustomEncodableValue {
 public:
  explicit CustomEncodableValue(const std::any& value) : value_(value) {}
  ~CustomEncodableValue() = default;

  // Allow implicit conversion to std::any to allow direct use of any_cast.
  // NOLINTNEXTLINE(google-explicit-constructor)
  operator std::any&() { return value_; }
  // NOLINTNEXTLINE(google-explicit-constructor)
  operator const std::any&() const { return value_; }

#if defined(FLUTTER_ENABLE_RTTI) && FLUTTER_ENABLE_RTTI
  // Passthrough to std::any's type().
  const std::type_info& type() const noexcept { return value_.type(); }
#endif

  // This operator exists only to provide a stable ordering for use as a
  // std::map key, to satisfy the compiler requirements for EncodableValue.
  // It does not attempt to provide useful ordering semantics, and using a
  // custom value as a map key is not recommended.
  bool operator<(const CustomEncodableValue& other) const {
    return this < &other;
  }
  bool operator==(const CustomEncodableValue& other) const {
    return this == &other;
  }

 private:
  std::any value_;
};

class EncodableValue;

// Convenience type aliases.
using EncodableList = std::vector<EncodableValue>;
using EncodableMap = std::map<EncodableValue, EncodableValue>;

namespace internal {
// The base class for EncodableValue. Do not use this directly; it exists only
// for EncodableValue to inherit from.
//
// Do not change the order or indexes of the items here; see the comment on
// EncodableValue
using EncodableValueVariant = std::variant<std::monostate,
                                           bool,
                                           int32_t,
                                           int64_t,
                                           double,
                                           std::string,
                                           std::vector<uint8_t>,
                                           std::vector<int32_t>,
                                           std::vector<int64_t>,
                                           std::vector<double>,
                                           EncodableList,
                                           EncodableMap,
                                           CustomEncodableValue,
                                           std::vector<float>>;
}  // namespace internal

// An object that can contain any value or collection type supported by
// Flutter's standard method codec.
//
// For details, see:
// https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html
//
// As an example, the following Dart structure:
//   {
//     'flag': true,
//     'name': 'Thing',
//     'values': [1, 2.0, 4],
//   }
// would correspond to:
//   EncodableValue(EncodableMap{
//       {EncodableValue("flag"), EncodableValue(true)},
//       {EncodableValue("name"), EncodableValue("Thing")},
//       {EncodableValue("values"), EncodableValue(EncodableList{
//                                      EncodableValue(1),
//                                      EncodableValue(2.0),
//                                      EncodableValue(4),
//                                  })},
//   })
//
// The primary API surface for this object is std::variant. For instance,
// getting a string value from an EncodableValue, with type checking:
//   if (std::holds_alternative<std::string>(value)) {
//     std::string some_string = std::get<std::string>(value);
//   }
//
// The order/indexes of the variant types is part of the API surface, and is
// guaranteed not to change.
//
// The variant types are mapped with Dart types in following ways:
// std::monostate       -> null
// bool                 -> bool
// int32_t              -> int
// int64_t              -> int
// double               -> double
// std::string          -> String
// std::vector<uint8_t> -> Uint8List
// std::vector<int32_t> -> Int32List
// std::vector<int64_t> -> Int64List
// std::vector<float>   -> Float32List
// std::vector<double>  -> Float64List
// EncodableList        -> List
// EncodableMap         -> Map
class EncodableValue : public internal::EncodableValueVariant {
 public:
  // Rely on std::variant for most of the constructors/operators.
  using super = internal::EncodableValueVariant;
  using super::super;
  using super::operator=;

  explicit EncodableValue() = default;

  // Avoid the C++17 pitfall of conversion from char* to bool. Should not be
  // needed for C++20.
  explicit EncodableValue(const char* string) : super(std::string(string)) {}
  EncodableValue& operator=(const char* other) {
    *this = std::string(other);
    return *this;
  }

  // Allow implicit conversion from CustomEncodableValue; the only reason to
  // make a CustomEncodableValue (which can only be constructed explicitly) is
  // to use it with EncodableValue, so the risk of unintended conversions is
  // minimal, and it avoids the need for the verbose:
  //   EncodableValue(CustomEncodableValue(...)).
  // NOLINTNEXTLINE(google-explicit-constructor)
  EncodableValue(const CustomEncodableValue& v) : super(v) {}

  // Override the conversion constructors from std::variant to make them
  // explicit, to avoid implicit conversion.
  //
  // While implicit conversion can be convenient in some cases, it can have very
  // surprising effects. E.g., calling a function that takes an EncodableValue
  // but accidentally passing an EncodableValue* would, instead of failing to
  // compile, go through a pointer->bool->EncodableValue(bool) chain and
  // silently call the function with a temp-constructed EncodableValue(true).
  template <class T>
  constexpr explicit EncodableValue(T&& t) noexcept : super(t) {}

  // Returns true if the value is null. Convenience wrapper since unlike the
  // other types, std::monostate uses aren't self-documenting.
  bool IsNull() const { return std::holds_alternative<std::monostate>(*this); }

  // Convenience method to simplify handling objects received from Flutter
  // where the values may be larger than 32-bit, since they have the same type
  // on the Dart side, but will be either 32-bit or 64-bit here depending on
  // the value.
  //
  // Calling this method if the value doesn't contain either an int32_t or an
  // int64_t will throw an exception.
  int64_t LongValue() const {
    if (std::holds_alternative<int32_t>(*this)) {
      return std::get<int32_t>(*this);
    }
    return std::get<int64_t>(*this);
  }
};

}  // namespace flutter

#endif  // FLUTTER_SHELL_PLATFORM_COMMON_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_
