/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_SyncedContext_h
#define mozilla_dom_SyncedContext_h

#include "mozilla/dom/MaybeDiscarded.h"
#include "mozilla/EnumSet.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Tuple.h"
#include <utility>

class PickleIterator;

namespace IPC {
class Message;
}  // namespace IPC

namespace mozilla {
namespace ipc {
class IProtocol;
class IPCResult;
template <typename T>
struct IPDLParamTraits;
}  // namespace ipc

namespace dom {
class ContentParent;
class ContentChild;

namespace syncedcontext {

template <size_t I>
using Index = typename std::integral_constant<size_t, I>;

using IndexSet = EnumSet<size_t, uint64_t>;

template <typename Context>
class Transaction {
 public:
  // Set a field at the given index in this `Transaction`. Creating a
  // `Transaction` object and setting multiple fields on it allows for
  // multiple mutations to be performed atomically.
  template <size_t I, typename U>
  void Set(U&& aValue) {
    auto& field = mozilla::Get<I>(mMaybeFields);
    field.emplace(std::forward<U>(aValue));
  }

  // Apply the changes from this transaction to the specified Context in all
  // processes. This method will call the correct `CanSet` and `DidSet` methods,
  // as well as move the value.
  //
  // If the target has been discarded, changes will be ignored.
  //
  // NOTE: This method mutates `this`, resetting all members to `Nothing()`
  nsresult Commit(Context* aOwner);

  // Called from `ContentParent` in response to a transaction from content.
  mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner,
                                        ContentParent* aSource);

  // Called from `ContentChild` in response to a transaction from the parent.
  mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner,
                                        uint64_t aEpoch, ContentChild* aSource);

  void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor) const;
  bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
            mozilla::ipc::IProtocol* aActor);

 private:
  friend struct mozilla::ipc::IPDLParamTraits<Transaction<Context>>;

  // You probably don't want to directly call this method - instead call
  // `Commit`, which will perform the necessary synchronization.
  //
  // `Validate` must be called before calling this method.
  void Apply(Context* aOwner);

  // Returns the set of fields which failed to validate, or an empty set if
  // there were no validation errors.
  IndexSet Validate(Context* aOwner, ContentParent* aSource);

  using FieldStorage = typename Context::FieldStorage;
  static FieldStorage& GetFieldStorage(Context* aContext) {
    return aContext->mFields;
  }

  // Call a generic lambda with a `Index<I>` for each index less than the number
  // of elements in `FieldStorage`.
  template <typename F>
  static void EachIndexInner(Index<0>, F&&) {}
  template <size_t I, typename F>
  static void EachIndexInner(Index<I>, F&& aCallback) {
    aCallback(Index<I - 1>());
    EachIndexInner(Index<I - 1>(), aCallback);
  }
  template <typename F>
  static void EachIndex(F&& aCallback) {
    EachIndexInner(Index<FieldStorage::fieldCount>(), aCallback);
  }

  // Helper for invoking `std::get` or `mozilla::Get` using a
  // `SyncedFieldIndex<I>` value.
  template <size_t I, typename... Ts>
  static auto& GetAt(Index<I>, mozilla::Tuple<Ts...>& aTarget) {
    return mozilla::Get<I>(aTarget);
  }
  template <size_t I, typename T, size_t L>
  static auto& GetAt(Index<I>, std::array<T, L>& aTarget) {
    return std::get<I>(aTarget);
  }

  typename FieldStorage::MaybeFieldTuple mMaybeFields;
};

// Storage related to synchronized context fields. Contains both a tuple of
// individual field values, and epoch information for field synchronization.
template <typename, typename... Ts>
class FieldStorage {
 public:
  using FieldTuple = mozilla::Tuple<Ts...>;

  static constexpr size_t fieldCount = sizeof...(Ts);
  static_assert(fieldCount < 64,
                "At most 64 synced fields are supported. Please file a bug if "
                "you need to additional fields.");

  const FieldTuple& Fields() const { return mFields; }

  // Get an individual field by index.
  template <size_t I>
  const auto& Get() const {
    return mozilla::Get<I>(mFields);
  }

  // Set the value of a field without telling other processes about the change.
  //
  // This is only sound in specific code which is already messaging other
  // processes, and doesn't need to worry about epochs or other properties of
  // field synchronization.
  template <size_t I, typename U>
  void SetWithoutSyncing(U&& aValue) {
    mozilla::Get<I>(mFields) = std::move(aValue);
  }

  // Get a reference to a field that can modify without telling other
  // processes about the change.
  //
  // This is only sound in specific code which is already messaging other
  // processes, and doesn't need to worry about epochs or other properties of
  // field synchronization.
  template <size_t I>
  auto& GetNonSyncingReference() {
    return mozilla::Get<I>(mFields);
  }

  FieldStorage() = default;
  explicit FieldStorage(FieldTuple&& aInit) : mFields(std::move(aInit)) {}

 private:
  template <typename Context>
  friend class Transaction;

  // Helper type for `Transaction`.
  using MaybeFieldTuple = mozilla::Tuple<mozilla::Maybe<Ts>...>;

  // Data Members
  std::array<uint64_t, sizeof...(Ts)> mEpochs{};
  FieldTuple mFields;
};

#define MOZ_DECL_SYNCED_CONTEXT_FIELD_TYPE(name, type) , type
#define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name,
#define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type)                       \
  const type& Get##name() const { return mFields.template Get<IDX_##name>(); } \
                                                                               \
  template <typename U>                                                        \
  void Set##name(U&& aValue) {                                                 \
    Transaction txn;                                                           \
    txn.template Set<IDX_##name>(std::forward<U>(aValue));                     \
    txn.Commit(this);                                                          \
  }
#define MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET(name, type)  \
  template <typename U>                                      \
  void Set##name(U&& aValue) {                               \
    this->template Set<IDX_##name>(std::forward<U>(aValue)); \
  }
#define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \
  case IDX_##name:                                        \
    return #name;

// Declare a type as a synced context type.
//
// clazz is the name of the type being declared, and `eachfield` is a macro
// which, when called with the name of the macro, will call that macro once for
// each field in the synced context.
#define MOZ_DECL_SYNCED_CONTEXT(clazz, eachfield)                            \
 protected:                                                                  \
  friend class ::mozilla::dom::syncedcontext::Transaction<clazz>;            \
  enum FieldIndexes { eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) };      \
  using FieldStorage =                                                       \
      typename ::mozilla::dom::syncedcontext::FieldStorage<void eachfield(   \
          MOZ_DECL_SYNCED_CONTEXT_FIELD_TYPE)>;                              \
  FieldStorage mFields;                                                      \
                                                                             \
 public:                                                                     \
  /* Helper for overloading methods like `CanSet` and `DidSet` */            \
  template <size_t I>                                                        \
  using FieldIndex = typename ::mozilla::dom::syncedcontext::Index<I>;       \
                                                                             \
  /* Field tuple type for use by initializers */                             \
  using FieldTuple = typename FieldStorage::FieldTuple;                      \
                                                                             \
  /* Transaction types for bulk mutations */                                 \
  using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction<clazz>; \
  class Transaction final : public BaseTransaction {                         \
   public:                                                                   \
    eachfield(MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET)                       \
  };                                                                         \
                                                                             \
  /* Field name getter by field index */                                     \
  static const char* FieldIndexToName(size_t aIndex) {                       \
    switch (aIndex) { eachfield(MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME) }     \
    return "<unknown>";                                                      \
  }                                                                          \
  eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET)

}  // namespace syncedcontext
}  // namespace dom

namespace ipc {

template <typename Context>
struct IPDLParamTraits<dom::syncedcontext::Transaction<Context>> {
  typedef dom::syncedcontext::Transaction<Context> paramType;

  static void Write(IPC::Message* aMsg, IProtocol* aActor,
                    const paramType& aParam) {
    aParam.Write(aMsg, aActor);
  }

  static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
                   IProtocol* aActor, paramType* aResult) {
    return aResult->Read(aMsg, aIter, aActor);
  }
};

}  // namespace ipc
}  // namespace mozilla

#endif  // !defined(mozilla_dom_SyncedContext_h)
