/*
 * Copyright 2021 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkEnumBitMask_DEFINED
#define SkEnumBitMask_DEFINED

#include "include/private/base/SkAttributes.h"

#include <type_traits>

/**
 * Wraps an enum that is used for flags, and enables masking with type safety. Example:
 *
 *   enum class MyFlags {
 *       kNone = 0,
 *       kA = 1,
 *       kB = 2,
 *       kC = 4,
 *   };
 *
 *   SK_MAKE_BITMASK_OPS(MyFlags)
 *
 *   ...
 *
 *       SkEnumBitMask<MyFlags> flags = MyFlags::kA | MyFlags::kB;
 *
 *       if (flags & MyFlags::kB) {}
 *
 *   ...
 */
template<typename E>
class SkEnumBitMask {
    using I = std::underlying_type_t<E>;
public:
    SK_ALWAYS_INLINE constexpr SkEnumBitMask() : SkEnumBitMask(I(0)) {}
    SK_ALWAYS_INLINE constexpr SkEnumBitMask(E e) : SkEnumBitMask(static_cast<I>(e)) {}

    SK_ALWAYS_INLINE constexpr explicit operator bool() const { return fValue; }
    SK_ALWAYS_INLINE constexpr I value() const                { return fValue; }

    SK_ALWAYS_INLINE constexpr bool operator==(SkEnumBitMask m) const { return fValue == m.fValue; }
    SK_ALWAYS_INLINE constexpr bool operator!=(SkEnumBitMask m) const { return fValue != m.fValue; }

    SK_ALWAYS_INLINE constexpr SkEnumBitMask operator|(SkEnumBitMask m) const {
        return SkEnumBitMask(fValue | m.fValue);
    }
    SK_ALWAYS_INLINE constexpr SkEnumBitMask operator&(SkEnumBitMask m) const {
        return SkEnumBitMask(fValue & m.fValue);
    }
    SK_ALWAYS_INLINE constexpr SkEnumBitMask operator^(SkEnumBitMask m) const {
        return SkEnumBitMask(fValue ^ m.fValue);
    }
    SK_ALWAYS_INLINE constexpr SkEnumBitMask operator~() const { return SkEnumBitMask(~fValue); }

    SK_ALWAYS_INLINE SkEnumBitMask& operator|=(SkEnumBitMask m) { return *this = *this | m; }
    SK_ALWAYS_INLINE SkEnumBitMask& operator&=(SkEnumBitMask m) { return *this = *this & m; }
    SK_ALWAYS_INLINE SkEnumBitMask& operator^=(SkEnumBitMask m) { return *this = *this ^ m; }

private:
    SK_ALWAYS_INLINE constexpr explicit SkEnumBitMask(I value) : fValue(value) {}

    I fValue;
};

/**
 * Defines functions that make it possible to use bitwise operators on an enum.
 */
#define SK_MAKE_BITMASK_OPS(E)                                        \
    [[maybe_unused]] constexpr SkEnumBitMask<E> operator|(E a, E b) { \
        return SkEnumBitMask<E>(a) | b;                               \
    }                                                                 \
    [[maybe_unused]] constexpr SkEnumBitMask<E> operator&(E a, E b) { \
        return SkEnumBitMask<E>(a) & b;                               \
    }                                                                 \
    [[maybe_unused]] constexpr SkEnumBitMask<E> operator^(E a, E b) { \
        return SkEnumBitMask<E>(a) ^ b;                               \
    }                                                                 \
    [[maybe_unused]] constexpr SkEnumBitMask<E> operator~(E e) {      \
        return ~SkEnumBitMask<E>(e);                                  \
    }

#define SK_DECL_BITMASK_OPS_FRIENDS(E)                 \
    friend constexpr SkEnumBitMask<E> operator|(E, E); \
    friend constexpr SkEnumBitMask<E> operator&(E, E); \
    friend constexpr SkEnumBitMask<E> operator^(E, E); \
    friend constexpr SkEnumBitMask<E> operator~(E);

#endif  // SkEnumBitMask_DEFINED
