/******************************************************************************
 *                       ____    _    _____                                   *
 *                      / ___|  / \  |  ___|    C++                           *
 *                     | |     / _ \ | |_       Actor                         *
 *                     | |___ / ___ \|  _|      Framework                     *
 *                      \____/_/   \_|_|                                      *
 *                                                                            *
 * Copyright 2011-2020 Dominik Charousset                                     *
 *                                                                            *
 * Distributed under the terms and conditions of the BSD 3-Clause License or  *
 * (at your option) under the terms and conditions of the Boost Software      *
 * License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE.       *
 *                                                                            *
 * If you did not receive a copy of the license files, see                    *
 * http://opensource.org/licenses/BSD-3-Clause and                            *
 * http://www.boost.org/LICENSE_1_0.txt.                                      *
 ******************************************************************************/

#pragma once

#include <chrono>
#include <memory>
#include <tuple>
#include <utility>

#include "caf/allowed_unsafe_message_type.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/fwd.hpp"
#include "caf/inspector_access_type.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/optional.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/sum_type_access.hpp"
#include "caf/variant.hpp"

namespace caf::detail {

// -- predicates ---------------------------------------------------------------

/// Utility class for predicates that always return `true`.
struct always_true_t {
  template <class... Ts>
  [[nodiscard]] constexpr bool operator()(Ts&&...) const noexcept {
    return true;
  }
};

/// Predicate that always returns `true`.
constexpr auto always_true = always_true_t{};

// -- traits -------------------------------------------------------------------

template <class>
constexpr bool assertion_failed_v = false;

// -- const conversions --------------------------------------------------------

/// Returns a mutable reference to `x`.
template <class T>
T& as_mutable_ref(T& x) {
  return x;
}

/// Returns a mutable reference to `x`.
template <class T>
T& as_mutable_ref(const T& x) {
  return const_cast<T&>(x);
}

// -- loading ------------------------------------------------------------------

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::inspect) {
  return inspect(f, x);
}

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::integral) {
  auto tmp = detail::squashed_int_t<T>{0};
  if (f.value(tmp)) {
    x = static_cast<T>(tmp);
    return true;
  }
  return false;
}

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::builtin) {
  return f.value(x);
}

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::enumeration) {
  auto tmp = detail::squashed_int_t<std::underlying_type_t<T>>{0};
  if (f.value(tmp)) {
    x = static_cast<T>(tmp);
    return true;
  }
  return false;
}

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::empty) {
  return f.object(x).fields();
}

template <class Inspector, class T>
bool load_value(Inspector& f, T&, inspector_access_type::unsafe) {
  f.emplace_error(sec::unsafe_type);
  return false;
}

template <class Inspector, class T, size_t N>
bool load_value(Inspector& f, T (&xs)[N], inspector_access_type::array) {
  if (!f.begin_tuple(N))
    return false;
  for (size_t index = 0; index < N; ++index)
    if (!inspect_value(f, xs[index]))
      return false;
  return f.end_tuple();
}

template <class Inspector, class T, size_t... Ns>
bool load_tuple(Inspector& f, T& xs, std::index_sequence<Ns...>) {
  return f.begin_tuple(sizeof...(Ns))              //
         && (inspect_value(f, get<Ns>(xs)) && ...) //
         && f.end_tuple();
}

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::tuple) {
  return load_tuple(f, x,
                    std::make_index_sequence<std::tuple_size<T>::value>{});
}

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::map) {
  x.clear();
  size_t size = 0;
  if (!f.begin_sequence(size))
    return false;
  for (size_t i = 0; i < size; ++i) {
    auto key = typename T::key_type{};
    auto val = typename T::mapped_type{};
    if (!(f.begin_tuple(2) && inspect_value(f, key) && inspect_value(f, val)
          && f.end_tuple()))
      return false;
    auto added = x.emplace(std::move(key), std::move(val)).second;
    if (!added) {
      f.emplace_error(sec::runtime_error, "multiple key definitions");
      return false;
    }
  }
  return f.end_sequence();
}

template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::list) {
  x.clear();
  size_t size = 0;
  if (!f.begin_sequence(size))
    return false;
  for (size_t i = 0; i < size; ++i) {
    auto val = typename T::value_type{};
    if (!inspect_value(f, val))
      return false;
    x.insert(x.end(), std::move(val));
  }
  return f.end_sequence();
}

template <class Inspector, class T>
std::enable_if_t<accepts_opaque_value<Inspector, T>::value, bool>
load_value(Inspector& f, T& x, inspector_access_type::none) {
  return f.opaque_value(x);
}

template <class Inspector, class T>
std::enable_if_t<!accepts_opaque_value<Inspector, T>::value, bool>
load_value(Inspector&, T&, inspector_access_type::none) {
  static_assert(
    detail::assertion_failed_v<T>,
    "please provide an inspect overload for T or specialize inspector_access");
  return false;
}

// -- saving -------------------------------------------------------------------

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::inspect) {
  return inspect(f, x);
}

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::integral) {
  auto tmp = static_cast<detail::squashed_int_t<T>>(x);
  return f.value(tmp);
}

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::builtin) {
  return f.value(x);
}

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::enumeration) {
  auto tmp = static_cast<detail::squashed_int_t<std::underlying_type_t<T>>>(x);
  return f.value(tmp);
}

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::empty) {
  return f.object(x).fields();
}

template <class Inspector, class T>
bool save_value(Inspector& f, T&, inspector_access_type::unsafe) {
  f.emplace_error(sec::unsafe_type);
  return false;
}

template <class Inspector, class T, size_t N>
bool save_value(Inspector& f, T (&xs)[N], inspector_access_type::array) {
  if (!f.begin_tuple(N))
    return false;
  for (size_t index = 0; index < N; ++index)
    if (!inspect_value(f, as_mutable_ref(xs[index])))
      return false;
  return f.end_tuple();
}

template <class Inspector, class T, size_t... Ns>
bool save_tuple(Inspector& f, T& xs, std::index_sequence<Ns...>) {
  return f.begin_tuple(sizeof...(Ns))                                      //
         && (inspect_value(f, detail::as_mutable_ref(get<Ns>(xs))) && ...) //
         && f.end_tuple();
}

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::tuple) {
  return save_tuple(f, x,
                    std::make_index_sequence<std::tuple_size<T>::value>{});
}

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::map) {
  auto size = x.size();
  if (!f.begin_sequence(size))
    return false;
  for (auto&& kvp : x) {
    if (!(f.begin_tuple(2) && inspect_value(f, as_mutable_ref(kvp.first))
          && inspect_value(f, as_mutable_ref(kvp.second)) && f.end_tuple()))
      return false;
  }
  return f.end_sequence();
}

template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::list) {
  auto size = x.size();
  if (!f.begin_sequence(size))
    return false;
  for (auto&& val : x)
    if (!inspect_value(f, detail::as_mutable_ref(val)))
      return false;
  return f.end_sequence();
}

template <class Inspector, class T>
std::enable_if_t<accepts_opaque_value<Inspector, T>::value, bool>
save_value(Inspector& f, T& x, inspector_access_type::none) {
  return f.opaque_value(x);
}

template <class Inspector, class T>
std::enable_if_t<!accepts_opaque_value<Inspector, T>::value, bool>
save_value(Inspector&, T&, inspector_access_type::none) {
  static_assert(
    detail::assertion_failed_v<T>,
    "please provide an inspect overload for T or specialize inspector_access");
  return false;
}

} // namespace caf::detail

namespace caf {

/// Expands to an `inspector_access_type` tag.
template <class Inspector, class T>
constexpr auto inspector_access_tag_v
  = decltype(guess_inspector_access_type<Inspector, T>()){};

/// Calls `f.value(x)` if this is a valid expression,
/// `inspector_access<T>::apply_value(f, x)` otherwise.
template <class Inspector, class T>
bool inspect_value(Inspector& f, T& x) {
  static_assert(!std::is_const<T>::value);
  return inspector_access<T>::apply_value(f, x);
}

template <class Inspector, class Get, class Set>
bool inspect_value(Inspector& f, Get&& get, Set&& set) {
  using value_type = std::decay_t<decltype(get())>;
  if constexpr (Inspector::is_loading) {
    auto tmp = value_type{};
    if (inspect_value(f, tmp))
      return set(std::move(tmp));
    return false;
  } else {
    auto&& x = get();
    return inspect_value(f, detail::as_mutable_ref(x));
  }
}

/// Provides default implementations for `save_field`, `load_field`, and
/// `apply_value`.
template <class T>
struct inspector_access_base {
  /// Saves a mandatory field to `f`.
  template <class Inspector>
  static bool save_field(Inspector& f, string_view field_name, T& x) {
    return f.begin_field(field_name) && inspect_value(f, x) && f.end_field();
  }

  /// Saves an optional field to `f`.
  template <class Inspector, class IsPresent, class Get>
  static bool save_field(Inspector& f, string_view field_name,
                         IsPresent& is_present, Get& get) {
    if (is_present()) {
      auto&& x = get();
      return f.begin_field(field_name, true)                //
             && inspect_value(f, detail::as_mutable_ref(x)) //
             && f.end_field();
    }
    return f.begin_field(field_name, false) && f.end_field();
  }

  /// Loads a mandatory field from `f`.
  template <class Inspector, class IsValid, class SyncValue>
  static bool load_field(Inspector& f, string_view field_name, T& x,
                         IsValid& is_valid, SyncValue& sync_value) {
    if (f.begin_field(field_name) && inspect_value(f, x)) {
      if (!is_valid(x)) {
        f.emplace_error(sec::field_invariant_check_failed,
                        to_string(field_name));
        return false;
      }
      if (!sync_value()) {
        f.emplace_error(sec::field_value_synchronization_failed,
                        to_string(field_name));
        return false;
      }
      return f.end_field();
    }
    return false;
  }

  /// Loads an optional field from `f`, calling `set_fallback` if the source
  /// contains no value for `x`.
  template <class Inspector, class IsValid, class SyncValue, class SetFallback>
  static bool load_field(Inspector& f, string_view field_name, T& x,
                         IsValid& is_valid, SyncValue& sync_value,
                         SetFallback& set_fallback) {
    bool is_present = false;
    if (!f.begin_field(field_name, is_present))
      return false;
    if (is_present) {
      if (!inspect_value(f, x))
        return false;
      if (!is_valid(x)) {
        f.emplace_error(sec::field_invariant_check_failed,
                        to_string(field_name));
        return false;
      }
      if (!sync_value()) {
        f.emplace_error(sec::field_value_synchronization_failed,
                        to_string(field_name));
        return false;
      }
      return f.end_field();
    }
    set_fallback();
    return f.end_field();
  }
};

/// Default implementation for @ref inspector_access.
template <class T>
struct default_inspector_access : inspector_access_base<T> {
  // -- interface functions ----------------------------------------------------

  /// Applies `x` as an object to `f`.
  template <class Inspector>
  [[nodiscard]] static bool apply_object(Inspector& f, T& x) {
    // Dispatch to user-provided `inspect` overload or assume a trivial type.
    if constexpr (detail::is_inspectable<Inspector, T>::value) {
      using result_type = decltype(inspect(f, x));
      if constexpr (std::is_same<result_type, bool>::value)
        return inspect(f, x);
      else
        return apply_deprecated(f, x);
    } else if constexpr (std::is_empty<T>::value) {
      return f.object(x).fields();
    } else {
      return f.object(x).fields(f.field("value", x));
    }
  }

  /// Applies `x` as a single value to `f`.
  template <class Inspector>
  [[nodiscard]] static bool apply_value(Inspector& f, T& x) {
    if constexpr (Inspector::is_loading)
      return detail::load_value(f, x, inspector_access_tag_v<Inspector, T>);
    else
      return detail::save_value(f, x, inspector_access_tag_v<Inspector, T>);
  }

  // -- deprecated API ---------------------------------------------------------

  template <class Inspector>
  [[deprecated("inspect() overloads should return bool")]] //
  [[nodiscard]] static bool
  apply_deprecated(Inspector& f, T& x) {
    if (auto err = inspect(f, x)) {
      f.emplace_error(std::move(err));
      return false;
    }
    return true;
  }
};

/// Customization point for adding support for a custom type. The default
/// implementation requires an `inspect` overload for `T` that is available via
/// ADL.
template <class T>
struct inspector_access : default_inspector_access<T> {};

/// Inspects `x` using the inspector `f`.
template <class Inspector, class T>
[[nodiscard]] bool inspect_object(Inspector& f, T& x) {
  if constexpr (detail::is_trivially_inspectable<Inspector, T>::value)
    return f.value(x);
  else
    return inspector_access<T>::apply_object(f, x);
}

/// Inspects `x` using the inspector `f`.
template <class Inspector, class T>
[[nodiscard]] bool inspect_object(Inspector& f, const T& x) {
  static_assert(!Inspector::is_loading);
  return inspect_object(f, detail::as_mutable_ref(x));
}

/// Inspects all `xs` using the inspector `f`.
template <class Inspector, class... Ts>
[[nodiscard]] bool inspect_objects(Inspector& f, Ts&... xs) {
  return (inspect_object(f, xs) && ...);
}

// -- inspection support for optional values -----------------------------------

template <class T>
struct optional_inspector_traits;

template <class T>
struct optional_inspector_traits<optional<T>> {
  using container_type = optional<T>;

  using value_type = T;

  template <class... Ts>
  static void emplace(container_type& container, Ts&&... xs) {
    container.emplace(std::forward<Ts>(xs)...);
  }
};

template <class T>
struct optional_inspector_traits<intrusive_ptr<T>> {
  using container_type = intrusive_ptr<T>;

  using value_type = T;

  template <class... Ts>
  static void emplace(container_type& container, Ts&&... xs) {
    container.reset(new T(std::forward<Ts>(xs)...));
  }
};

template <class T>
struct optional_inspector_traits<std::unique_ptr<T>> {
  using container_type = std::unique_ptr<T>;

  using value_type = T;

  template <class... Ts>
  static void emplace(container_type& container, Ts&&... xs) {
    container = std::make_unique<T>(std::forward<Ts>(xs)...);
  }
};

template <class T>
struct optional_inspector_traits<std::shared_ptr<T>> {
  using container_type = std::shared_ptr<T>;

  using value_type = T;

  template <class... Ts>
  static void emplace(container_type& container, Ts&&... xs) {
    container = std::make_shared<T>(std::forward<Ts>(xs)...);
  }
};

/// Provides inspector access for types that represent optional values.
template <class T>
struct optional_inspector_access {
  using traits = optional_inspector_traits<T>;

  using container_type = typename traits::container_type;

  using value_type = typename traits::value_type;

  using value_type_access = inspector_access<value_type>;

  template <class Inspector>
  [[nodiscard]] static bool apply_object(Inspector& f, container_type& x) {
    return f.object(x).fields(f.field("value", x));
  }

  template <class Inspector>
  [[nodiscard]] static bool apply_value(Inspector& f, container_type&) {
    f.emplace_error(sec::runtime_error, "apply_value called on an optional");
    return false;
  }

  template <class Inspector>
  static bool
  save_field(Inspector& f, string_view field_name, container_type& x) {
    auto is_present = [&x] { return static_cast<bool>(x); };
    auto get = [&x]() -> decltype(auto) { return *x; };
    return value_type_access::save_field(f, field_name, is_present, get);
  }

  template <class Inspector, class IsPresent, class Get>
  static bool save_field(Inspector& f, string_view field_name,
                         IsPresent& is_present, Get& get) {
    return value_type_access::save_field(f, field_name, is_present, get);
  }

  template <class Inspector, class IsValid, class SyncValue>
  static bool load_field(Inspector& f, string_view field_name,
                         container_type& x, IsValid& is_valid,
                         SyncValue& sync_value) {
    traits::emplace(x);
    auto set_x = [&] { return sync_value(); };
    auto reset = [&x] { x.reset(); };
    return value_type_access::load_field(f, field_name, *x, is_valid, set_x,
                                         reset);
  }

  template <class Inspector, class IsValid, class SyncValue, class SetFallback>
  static bool load_field(Inspector& f, string_view field_name,
                         container_type& x, IsValid& is_valid,
                         SyncValue& sync_value, SetFallback& set_fallback) {
    traits::emplace(x);
    auto set_x = [&] {
      return sync_value();
    };
    return value_type_access::load_field(f, field_name, *x, is_valid, set_x,
                                         set_fallback);
  }
};

// -- inspection support for optional<T> ---------------------------------------

template <class T>
struct inspector_access<optional<T>> : optional_inspector_access<optional<T>> {
  // nop
};

// -- inspection support for variant<Ts...> ------------------------------------

template <class... Ts>
struct inspector_access<variant<Ts...>> {
  static_assert(
    (has_type_id<Ts> && ...),
    "inspectors requires that each type in a variant has a type_id");

  using value_type = variant<Ts...>;

  static constexpr type_id_t allowed_types_arr[] = {type_id_v<Ts>...};

  template <class Inspector>
  [[nodiscard]] static bool apply_object(Inspector& f, value_type& x) {
    return f.object(x).fields(f.field("value", x));
  }

  template <class Inspector>
  [[nodiscard]] static bool apply_value(Inspector& f, value_type&) {
    f.emplace_error(sec::runtime_error, "apply_value called on a variant");
    return false;
  }

  template <class Inspector>
  static bool save_field(Inspector& f, string_view field_name, value_type& x) {
    auto g = [&f](auto& y) { return inspect_value(f, y); };
    return f.begin_field(field_name, make_span(allowed_types_arr), x.index()) //
           && visit(g, x)                                                     //
           && f.end_field();
  }

  template <class Inspector, class IsPresent, class Get>
  static bool save_field(Inspector& f, string_view field_name,
                         IsPresent& is_present, Get& get) {
    auto allowed_types = make_span(allowed_types_arr);
    if (is_present()) {
      auto&& x = get();
      auto g = [&f](auto& y) {
        return inspect_value(f, detail::as_mutable_ref(y));
      };
      return f.begin_field(field_name, true, allowed_types, x.index()) //
             && visit(g, x)                                            //
             && f.end_field();
    }
    return f.begin_field(field_name, false, allowed_types, 0) //
           && f.end_field();
  }

  template <class Inspector>
  static bool load_variant_value(Inspector& f, string_view field_name,
                                 value_type&, type_id_t, detail::type_list<>) {
    f.emplace_error(sec::invalid_field_type, to_string(field_name));
    return false;
  }

  template <class Inspector, class U, class... Us>
  static bool load_variant_value(Inspector& f, string_view field_name,
                                 value_type& x, type_id_t runtime_type,
                                 detail::type_list<U, Us...>) {
    if (type_id_v<U> == runtime_type) {
      auto tmp = U{};
      if (!inspect_value(f, tmp))
        return false;
      x = std::move(tmp);
      return true;
    }
    detail::type_list<Us...> token;
    return load_variant_value(f, field_name, x, runtime_type, token);
  }

  template <class Inspector, class IsValid, class SyncValue>
  static bool load_field(Inspector& f, string_view field_name, value_type& x,
                         IsValid& is_valid, SyncValue& sync_value) {
    size_t type_index = std::numeric_limits<size_t>::max();
    auto allowed_types = make_span(allowed_types_arr);
    if (!f.begin_field(field_name, allowed_types, type_index))
      return false;
    if (type_index >= allowed_types.size()) {
      f.emplace_error(sec::invalid_field_type, to_string(field_name));
      return false;
    }
    auto runtime_type = allowed_types[type_index];
    detail::type_list<Ts...> types;
    if (!load_variant_value(f, field_name, x, runtime_type, types))
      return false;
    if (!is_valid(x)) {
      f.emplace_error(sec::field_invariant_check_failed, to_string(field_name));
      return false;
    }
    if (!sync_value()) {
      f.emplace_error(sec::field_value_synchronization_failed,
                      to_string(field_name));
      return false;
    }
    return f.end_field();
  }

  template <class Inspector, class IsValid, class SyncValue, class SetFallback>
  static bool load_field(Inspector& f, string_view field_name, value_type& x,
                         IsValid& is_valid, SyncValue& sync_value,
                         SetFallback& set_fallback) {
    bool is_present = false;
    auto allowed_types = make_span(allowed_types_arr);
    size_t type_index = std::numeric_limits<size_t>::max();
    if (!f.begin_field(field_name, is_present, allowed_types, type_index))
      return false;
    if (is_present) {
      if (type_index >= allowed_types.size()) {
        f.emplace_error(sec::invalid_field_type, to_string(field_name));
        return false;
      }
      auto runtime_type = allowed_types[type_index];
      detail::type_list<Ts...> types;
      if (!load_variant_value(f, field_name, x, runtime_type, types))
        return false;
      if (!is_valid(x)) {
        f.emplace_error(sec::field_invariant_check_failed,
                        to_string(field_name));
        return false;
      }
      if (!sync_value()) {
        f.emplace_error(sec::field_value_synchronization_failed,
                        to_string(field_name));
        return false;
      }
    } else {
      set_fallback();
    }
    return f.end_field();
  }
};

// -- inspection support for std::chrono types ---------------------------------

template <class Rep, class Period>
struct inspector_access<std::chrono::duration<Rep, Period>>
  : inspector_access_base<std::chrono::duration<Rep, Period>> {
  using value_type = std::chrono::duration<Rep, Period>;

  using default_impl = default_inspector_access<value_type>;

  template <class Inspector>
  static bool apply_object(Inspector& f, value_type& x) {
    auto get = [&x] { return x.count(); };
    auto set = [&x](Rep value) {
      x = std::chrono::duration<Rep, Period>{value};
      return true;
    };
    return f.object(x).fields(f.field("count", get, set));
  }

  template <class Inspector>
  static bool apply_value(Inspector& f, value_type& x) {
    auto get = [&x] { return x.count(); };
    auto set = [&x](Rep value) {
      x = std::chrono::duration<Rep, Period>{value};
      return true;
    };
    return inspect_value(f, get, set);
  }
};

template <class Clock, class Duration>
struct inspector_access<std::chrono::time_point<Clock, Duration>>
  : inspector_access_base<std::chrono::time_point<Clock, Duration>> {
  using value_type = std::chrono::time_point<Clock, Duration>;

  using default_impl = default_inspector_access<value_type>;

  template <class Inspector>
  static bool apply_object(Inspector& f, value_type& x) {
    using rep_type = typename Duration::rep;
    auto get = [&x] { return x.time_since_epoch().count(); };
    auto set = [&x](rep_type value) {
      x = value_type{Duration{value}};
      return true;
    };
    return f.object(x).fields(f.field("timestamp", get, set));
  }

  template <class Inspector>
  static bool apply_value(Inspector& f, value_type& x) {
    using rep_type = typename Duration::rep;
    auto get = [&x] { return x.time_since_epoch().count(); };
    auto set = [&x](rep_type value) {
      x = value_type{Duration{value}};
      return true;
    };
    return inspect_value(f, get, set);
  }
};

} // namespace caf
