/*
 *
 *    Copyright (c) 2020-2021 Project CHIP Authors
 *    Copyright (c) 2013-2017 Nest Labs, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

/**
 *    @file
 *      This file defines functions for manipulating Boolean flags in
 *      a bitfield.
 *
 */

#pragma once

#include <stdint.h>

#include <type_traits>
#include <utility>

namespace chip {

/**
 * Stores bit flags in a type safe manner.
 *
 * @tparam FlagsEnum is an `enum` or (preferably) `enum class` type.
 * @tparam StorageType is the underlying storage type (like uint16_t, uint32_t etc.)
 *         and defaults to the underlying storage type of `FlagsEnum`.
 */
template <typename FlagsEnum, typename StorageType = typename std::underlying_type_t<FlagsEnum>>
class BitFlags
{
public:
    static_assert(sizeof(StorageType) >= sizeof(FlagsEnum), "All flags should fit in the storage type");
    using IntegerType = StorageType;

    constexpr BitFlags() : mValue(0) {}
    BitFlags(const BitFlags & other)       = default;
    BitFlags & operator=(const BitFlags &) = default;

    explicit constexpr BitFlags(FlagsEnum value) : mValue(static_cast<IntegerType>(value)) {}
    explicit constexpr BitFlags(IntegerType value) : mValue(value) {}

    template <typename... Args>
    constexpr BitFlags(FlagsEnum flag, Args &&... args) : mValue(Or(flag, std::forward<Args>(args)...))
    {}

    template <typename... Args>
    constexpr BitFlags(const BitFlags<FlagsEnum> & flags, Args &&... args) : mValue(Or(flags, std::forward<Args>(args)...))
    {}

    template <typename... Args>
    constexpr BitFlags(IntegerType value, Args &&... args) : mValue(value | Or(std::forward<Args>(args)...))
    {}

    /**
     * Set flag(s).
     *
     * @param other     Flag(s) to set. Any flags not set in @a other are unaffected.
     */
    BitFlags & Set(const BitFlags & other)
    {
        mValue |= other.mValue;
        return *this;
    }

    /**
     * Set flag(s).
     *
     * @param flag      Typed flag(s) to set. Any flags not in @a v are unaffected.
     */
    constexpr BitFlags & Set(FlagsEnum flag)
    {
        mValue |= static_cast<IntegerType>(flag);
        return *this;
    }

    /**
     * Set or clear flag(s).
     *
     * @param flag      Typed flag(s) to set or clear. Any flags not in @a flag are unaffected.
     * @param isSet     If true, set the flag; if false, clear it.
     */
    constexpr BitFlags & Set(FlagsEnum flag, bool isSet) { return isSet ? Set(flag) : Clear(flag); }

    /**
     * Clear flag(s).
     *
     * @param other     Typed flag(s) to clear. Any flags not in @a other are unaffected.
     */
    BitFlags & Clear(const BitFlags & other)
    {
        mValue &= static_cast<IntegerType>(~static_cast<IntegerType>(other.mValue));
        return *this;
    }

    /**
     * Clear flag(s).
     *
     * @param flag  Typed flag(s) to clear. Any flags not in @a flag are unaffected.
     */
    constexpr BitFlags & Clear(FlagsEnum flag)
    {
        mValue &= static_cast<IntegerType>(~static_cast<IntegerType>(flag));
        return *this;
    }

    /**
     * Clear all flags.
     */
    BitFlags & ClearAll()
    {
        mValue = 0;
        return *this;
    }

    /**
     * Check whether flag(s) are set.
     *
     * @param flag      Flag(s) to test.
     * @returns         True if any flag in @a flag is set, otherwise false.
     */
    constexpr bool Has(FlagsEnum flag) const { return (mValue & static_cast<IntegerType>(flag)) != 0; }

    /**
     * Check that no flags outside the arguments are set.
     *
     * @param args      Flags to test. Arguments can be BitFlags<FlagsEnum>, BitFlags<FlagsEnum>, or FlagsEnum.
     * @returns         True if no flag is set other than those passed.
     *                  False if any flag is set other than those passed.
     *
     * @note            Flags passed need not be set; this test only requires that no *other* flags be set.
     */
    template <typename... Args>
    bool HasOnly(Args &&... args) const
    {
        return (mValue & Or(std::forward<Args>(args)...)) == mValue;
    }

    /**
     * Check that all given flags are set.
     *
     * @param args      Flags to test. Arguments can be BitFlags<FlagsEnum>, BitFlags<FlagsEnum>, or FlagsEnum.
     * @returns         True if all given flags are set.
     *                  False if any given flag is not set.
     */
    template <typename... Args>
    bool HasAll(Args &&... args) const
    {
        const IntegerType all = Or(std::forward<Args>(args)...);
        return (mValue & all) == all;
    }

    /**
     * Check that at least one of the given flags is set.
     *
     * @param args      Flags to test. Arguments can be BitFlags<FlagsEnum>, BitFlags<FlagsEnum>, or FlagsEnum.
     * @returns         True if all given flags are set.
     *                  False if any given flag is not set.
     */
    template <typename... Args>
    bool HasAny(Args &&... args) const
    {
        return (mValue & Or(std::forward<Args>(args)...)) != 0;
    }

    /**
     * Check that at least one flag is set.
     *
     * @returns         True if any flag is set, false otherwise.
     */
    bool HasAny() const { return mValue != 0; }

    /**
     * Find the logical intersection of flags.
     *
     * @param lhs       Some flags.
     * @param rhs       Some flags.
     * @returns         Flags set in both @a lhs and @a rhs.
     *
     * @note: A multi-argument `BitFlags` constructor serves the function of `operator|`.
     */
    friend BitFlags<FlagsEnum> operator&(BitFlags<FlagsEnum> lhs, const BitFlags<FlagsEnum> & rhs)
    {
        return BitFlags<FlagsEnum>(lhs.mValue & rhs.mValue);
    }

    /**
     * Get the flags as the type FlagsEnum.
     *
     * @note            This allows easily storing flags as a base FlagsEnum in a POD type,
     *                  and enables equality comparisons.
     */
    operator FlagsEnum() const { return static_cast<FlagsEnum>(mValue); }

    /**
     * Set and/or clear all flags with a value of the underlying storage type.
     *
     * @param value     New storage value.
     */
    BitFlags & SetRaw(IntegerType value)
    {
        mValue = value;
        return *this;
    }

    /**
     * Get the flags as the underlying integer type.
     *
     * @note            This is intended to be used only to store flags into a raw binary record.
     */
    constexpr IntegerType Raw() const { return mValue; }

    /**
     * Get the address of the flags as a pointer to the underlying integer type.
     *
     * @note            This is intended to be used only to read flags from a raw binary record.
     */
    StorageType * RawStorage() { return &mValue; }

private:
    // Find the union of BitFlags and/or FlagsEnum values.
    template <typename... Args>
    static constexpr IntegerType Or(FlagsEnum flag, Args &&... args)
    {
        return static_cast<IntegerType>(flag) | Or(std::forward<Args>(args)...);
    }
    template <typename... Args>
    static constexpr IntegerType Or(const BitFlags<FlagsEnum> & flags, Args &&... args)
    {
        return flags.mValue | Or(std::forward<Args>(args)...);
    }
    static constexpr IntegerType Or(FlagsEnum value) { return static_cast<IntegerType>(value); }
    static constexpr IntegerType Or(const BitFlags<FlagsEnum> & flags) { return flags.Raw(); }

    StorageType mValue = 0;
};

} // namespace chip
