/* 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 DOM_CANVAS_TIED_FIELDS_H
#define DOM_CANVAS_TIED_FIELDS_H

#include "TupleUtils.h"

namespace mozilla {

// -

/**
 * TiedFields(T&) -> std::tuple<Fields&...>
 * TiedFields(const T&) -> std::tuple<const Fields&...>
 *
 * You can also overload TiedFields without adding T::MutTiedFields:
 * template<>
 * inline auto TiedFields<gfx::IntSize>(gfx::IntSize& a) {
 *   return std::tie(a.width, a.height);
 * }
 */
template <class T>
constexpr auto TiedFields(T& t) {
  const auto fields = t.MutTiedFields();
  return fields;
}
template <class T, class... Args, class Tup = std::tuple<Args&...>>
constexpr auto TiedFields(const T& t) {
  // Uncast const to get mutable-fields tuple, but reapply const to tuple args.
  // We should do better than this when C++ gets a solution other than macros.
  const auto mutFields = TiedFields(const_cast<T&>(t));
  return ToTupleOfConstRefs(mutFields);
}

/**
 * Returns true if all bytes in T are accounted for via size of all tied fields.
 * Returns false if there's bytes unaccounted for, which might indicate either
 * unaccounted-for padding or missing fields.
 * The goal is to check that TiedFields returns every field in T, and this
 * returns false if it suspects there are bytes that are not accounted for by
 * TiedFields.
 *
 * `constexpr` effectively cannot do math on pointers, so it's not possible to
 * figure out via `constexpr` whether fields are consecutive or dense.
 * However, we can at least compare `sizeof(T)` to the sum of `sizeof(Args...)`
 * for `TiedFields(T) -> std::tuple<Args...>`.
 *
 * See TiedFieldsExamples.
 */
template <class T>
constexpr bool AreAllBytesTiedFields() {
  using fieldsT = decltype(TiedFields(std::declval<T>()));
  const auto fields_size_sum = SizeofTupleArgs<fieldsT>::value;
  const auto t_size = sizeof(T);
  return fields_size_sum == t_size;
}

// It's also possible to determine AreAllBytesRecursiveTiedFields:
// https://hackmd.io/@jgilbert/B16qa0Fa9

// -

/**
 * Padding<T> can be used to pad out a struct so that it's not implicitly
 * padded by struct rules.
 * You can also just add your padding to TiedFields, but by explicitly typing
 * padding like this, serialization can make a choice whether to copy Padding,
 * or instead to omit the copy.
 *
 * Omitting the copy isn't always faster.
 * struct Entry {
 *   uint16_t key;
 *   Padding<uint16_t> padding;
 *   uint32_t val;
 *   auto MutTiedFields() { return std::tie(key, padding, val); }
 * };
 * If you serialize Padding, the serialized size is 8, and the compiler will
 * optimize serialization to a single 8-byte memcpy.
 * If your serialization omits Padding, the serialized size of Entry shrinks
 * by 25%. If you have a big list of Entrys, maybe this is a big savings!
 * However, by optimizing for size here you sacrifice speed, because this splits
 * the single memcpy into two: a 2-byte memcpy and a 4-byte memcpy.
 *
 * Explicitly marking padding gives callers the option of choosing.
 */
template <class T>
struct Padding {
  T ignored;

  friend constexpr bool operator==(const Padding&, const Padding&) {
    return true;
  }
  friend constexpr bool operator<(const Padding&, const Padding&) {
    return false;
  }
};
static_assert(sizeof(Padding<bool>) == 1);
static_assert(sizeof(Padding<bool[2]>) == 2);
static_assert(sizeof(Padding<int>) == 4);

// -

namespace TiedFieldsExamples {

struct Cat {
  int i;
  bool b;

  constexpr auto MutTiedFields() { return std::tie(i, b); }
};
static_assert(sizeof(Cat) == 8);
static_assert(!AreAllBytesTiedFields<Cat>());

struct Dog {
  bool b;
  int i;

  constexpr auto MutTiedFields() { return std::tie(i, b); }
};
static_assert(sizeof(Dog) == 8);
static_assert(!AreAllBytesTiedFields<Dog>());

struct Fish {
  bool b;
  bool padding[3];
  int i;

  constexpr auto MutTiedFields() { return std::tie(i, b, padding); }
};
static_assert(sizeof(Fish) == 8);
static_assert(AreAllBytesTiedFields<Fish>());

struct Eel {  // Like a Fish, but you can skip serializing the padding.
  bool b;
  Padding<bool> padding[3];
  int i;

  constexpr auto MutTiedFields() { return std::tie(i, b, padding); }
};
static_assert(sizeof(Eel) == 8);
static_assert(AreAllBytesTiedFields<Eel>());

// -

// #define LETS_USE_BIT_FIELDS
#ifdef LETS_USE_BIT_FIELDS
#  undef LETS_USE_BIT_FIELDS

struct Platypus {
  short s : 1;
  short s2 : 1;
  int i;

  constexpr auto MutTiedFields() {
    return std::tie(s, s2, i);  // Error: Can't take reference to bit-field.
  }
};

#endif

// -

struct FishTank {
  Fish f;
  int i2;

  constexpr auto MutTiedFields() { return std::tie(f, i2); }
};
static_assert(sizeof(FishTank) == 12);
static_assert(AreAllBytesTiedFields<FishTank>());

struct CatCarrier {
  Cat c;
  int i2;

  constexpr auto MutTiedFields() { return std::tie(c, i2); }
};
static_assert(sizeof(CatCarrier) == 12);
static_assert(AreAllBytesTiedFields<CatCarrier>());
static_assert(
    !AreAllBytesTiedFields<decltype(CatCarrier::c)>());  // BUT BEWARE THIS!
// For example, if we had AreAllBytesRecursiveTiedFields:
// static_assert(!AreAllBytesRecursiveTiedFields<CatCarrier>());

}  // namespace TiedFieldsExamples
}  // namespace mozilla

#endif  // DOM_CANVAS_TIED_FIELDS_H
