// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef SRC_DAWN_NATIVE_CHAINUTILS_H_
#define SRC_DAWN_NATIVE_CHAINUTILS_H_

#include <bitset>
#include <string>
#include <tuple>
#include <type_traits>

#include "absl/strings/str_format.h"
#include "dawn/native/ChainUtils_autogen.h"
#include "src/dawn/common/Math.h"
#include "src/dawn/native/Error.h"

namespace dawn::native {
namespace detail {
// Gets the chain type for an extensible type.
template <typename T>
struct ChainTypeFor {
    using Type = typename std::conditional_t<ExtensibilityFor<T> == Extensibility::In,
                                             const wgpu::ChainedStruct*,
                                             wgpu::ChainedStructOut*>;
};
}  // namespace detail

template <typename T>
class UnpackedPtr;

namespace detail {
// Converts to the expected pointer types depending on the extensibility of the structure.
template <typename UnpackedPtrT, typename U>
struct PtrTypeFor;
template <typename T, typename U>
struct PtrTypeFor<UnpackedPtr<T>, U> {
    using Type =
        typename std::conditional_t<ExtensibilityFor<T> == Extensibility::In, const U*, U*>;
};
}  // namespace detail

// Unpacks chained structures in a best effort manner (skipping unknown chains) without applying
// validation. If the same structure is duplicated in the chain, it is unspecified which one the
// result of Get will be. These are the effective constructors for the wrapper types. Note that
// these are implemented in the generated ChainUtils_autogen.cpp file.
template <typename T,
          typename = std::enable_if_t<detail::ExtensibilityFor<T> == detail::Extensibility::In>>
UnpackedPtr<T> Unpack(const T* chain);
template <typename T,
          typename = std::enable_if_t<detail::ExtensibilityFor<T> == detail::Extensibility::Out>>
UnpackedPtr<T> Unpack(T* chain);

// Unpacks chained structures into UnpackedPtr<T> while applying validation.
template <typename T,
          typename = std::enable_if_t<detail::ExtensibilityFor<T> == detail::Extensibility::In>>
ResultOrError<UnpackedPtr<T>> ValidateAndUnpack(const T* chain);
template <typename T,
          typename = std::enable_if_t<detail::ExtensibilityFor<T> == detail::Extensibility::Out>>
ResultOrError<UnpackedPtr<T>> ValidateAndUnpack(T* chain);

//
// Wrapper class for unpacked pointers. The classes essentially acts like a const T* or T* with
// the additional capabilities to validate and retrieve chained structures.
//
template <typename T>
class UnpackedPtr {
  public:
    using ChainType =
        typename std::conditional_t<detail::ExtensibilityFor<T> == detail::Extensibility::In,
                                    const wgpu::ChainedStruct*,
                                    wgpu::ChainedStructOut*>;
    using PtrType = typename detail::PtrTypeFor<UnpackedPtr<T>, T>::Type;
    using TupleType = typename detail::UnpackedPtrTypeFor<T>::Type;
    using BitsetType = typename std::bitset<std::tuple_size_v<TupleType>>;

    UnpackedPtr() : mStruct(nullptr) {}

    explicit operator bool() const { return mStruct != nullptr; }
    PtrType operator->() const { return mStruct; }
    PtrType operator*() const { return mStruct; }

    // Returns true iff every allowed chain in this unpacked type is nullptr.
    bool Empty() const;
    // Returns a string of the non-nullptr STypes from an unpacked chain.
    std::string ToString() const;

    template <typename In>
    auto Get() const;
    template <typename In>
    bool Has() const;

    // Validation functions. See implementations of these below for usage, details, and examples.
    template <typename... Branches>
    ResultOrError<wgpu::SType> ValidateBranches() const;
    template <typename... Allowed>
    MaybeError ValidateSubset() const;

  private:
    friend UnpackedPtr<T> Unpack<T>(PtrType chain);
    friend ResultOrError<UnpackedPtr<T>> ValidateAndUnpack<T>(PtrType chain);

    explicit UnpackedPtr(PtrType packed) : mStruct(packed) {}

    PtrType mStruct = nullptr;
    TupleType mUnpacked;
    BitsetType mBitset;
};

// Tuple type of a Branch and an optional list of corresponding Extensions.
template <typename B, typename... Exts>
struct Branch;

// ------------------------------------------------------------------------------------------------
// Implementation details start here so that the headers are terse.
// ------------------------------------------------------------------------------------------------

namespace detail {

// Helpers to get the index in an unpacked tuple type for a particular type.
template <typename UnpackedPtrT, typename Ext>
inline size_t UnpackedPtrTupleIndexOf;
template <typename Ext, typename... Exts>
constexpr inline size_t UnpackedPtrTupleIndexOf<std::tuple<Ext, Exts...>, Ext> = 0;
template <typename Ext, typename Other, typename... Exts>
constexpr inline size_t UnpackedPtrTupleIndexOf<std::tuple<Other, Exts...>, Ext> =
    1 + UnpackedPtrTupleIndexOf<std::tuple<Exts...>, Ext>;

template <typename UnpackedPtrT, typename Ext>
inline size_t UnpackedPtrIndexOf;
template <typename T, typename Ext>
constexpr inline size_t UnpackedPtrIndexOf<UnpackedPtr<T>, Ext> =
    UnpackedPtrTupleIndexOf<typename UnpackedPtr<T>::TupleType,
                            typename PtrTypeFor<UnpackedPtr<T>, std::remove_pointer_t<Ext>>::Type>;

// Currently using an internal 64-bit unsigned int for internal representation. This is necessary
// because std::bitset::operator| is not constexpr until C++23.
template <typename UnpackedPtrT, typename... Exts>
constexpr inline auto UnpackedPtrBitsetForExts = typename UnpackedPtrT::BitsetType(
    ((uint64_t(1) << UnpackedPtrIndexOf<UnpackedPtrT, Exts>) | ...));
template <typename UnpackedPtrT>
constexpr inline auto UnpackedPtrBitsetForExts<UnpackedPtrT> = typename UnpackedPtrT::BitsetType(0);

template <typename UnpackedPtrT, typename...>
struct OneBranchValidator;
template <typename UnpackedPtrT, typename R, typename... Exts>
struct OneBranchValidator<UnpackedPtrT, Branch<R, Exts...>> {
    using BitsetType = typename UnpackedPtrT::BitsetType;

    static bool Validate(const UnpackedPtrT& unpacked,
                         const BitsetType& actual,
                         wgpu::SType& match) {
        // Only check the full bitset when the main branch matches.
        if (unpacked.template Get<R>() != nullptr) {
            // Allowed set of extensions includes the branch root as well.
            constexpr auto allowed =
                UnpackedPtrBitsetForExts<UnpackedPtrT,
                                         typename detail::PtrTypeFor<UnpackedPtrT, R>::Type,
                                         typename detail::PtrTypeFor<UnpackedPtrT, Exts>::Type...>;

            // The configuration is allowed if the actual available chains are a subset.
            if (IsSubset(actual, allowed)) {
                match = STypeFor<R>;
                return true;
            }
        }
        return false;
    }

    static std::string ToString() {
        if constexpr (sizeof...(Exts) > 0) {
            return absl::StrFormat("[ %s -> (%s) ]", STypesToString<R>(),
                                   STypesToString<Exts...>());
        } else {
            return absl::StrFormat("[ %s ]", STypesToString<R>());
        }
    }
};

template <typename UnpackedPtrT, typename... Branches>
struct BranchesValidator {
    using BitsetType = typename UnpackedPtrT::BitsetType;

    static bool Validate(const UnpackedPtrT& unpacked,
                         const BitsetType& actual,
                         wgpu::SType& match) {
        return ((OneBranchValidator<UnpackedPtrT, Branches>::Validate(unpacked, actual, match)) ||
                ...);
    }

    static std::string ToString() {
        return (
            (absl::StrFormat("  - %s\n", OneBranchValidator<UnpackedPtrT, Branches>::ToString())) +
            ...);
    }
};

template <typename UnpackedPtrT, typename... Allowed>
struct SubsetValidator {
    using BitsetType = typename UnpackedPtrT::BitsetType;

    static MaybeError Validate(const UnpackedPtrT& unpacked, const BitsetType& bitset) {
        // Allowed set of extensions includes the branch root as well.
        constexpr auto allowed = detail::UnpackedPtrBitsetForExts<
            UnpackedPtrT, typename detail::PtrTypeFor<UnpackedPtrT, Allowed>::Type...>;
        if (!IsSubset(bitset, allowed)) {
            if constexpr (sizeof...(Allowed)) {
                return DAWN_VALIDATION_ERROR(
                    "Expected extension set to be a subset of: %s\nInstead found: %s",
                    absl::StrFormat("[ %s ]", STypesToString<Allowed...>()), unpacked.ToString());
            } else {
                return DAWN_VALIDATION_ERROR(
                    "Expected there to be no extensions. Instead found: %s", unpacked.ToString());
            }
        }
        return {};
    }
};

}  // namespace detail

template <typename T>
template <typename In>
auto UnpackedPtr<T>::Get() const {
    return std::get<typename detail::PtrTypeFor<UnpackedPtr<T>, In>::Type>(mUnpacked);
}

template <typename T>
template <typename In>
bool UnpackedPtr<T>::Has() const {
    return std::get<typename detail::PtrTypeFor<UnpackedPtr<T>, In>::Type>(mUnpacked) != nullptr;
}

template <typename T>
bool UnpackedPtr<T>::Empty() const {
    return mBitset.none();
}

template <typename T>
std::string UnpackedPtr<T>::ToString() const {
    std::string result = "( ";
    std::apply(
        [&](auto*... args) {
            (([&](auto* arg) {
                 if (arg != nullptr) {
                     // reinterpret_cast because this chained struct might be forward-declared
                     // without a definition. The definition may only be available on a
                     // particular backend.
                     const auto* chainedStruct = reinterpret_cast<ChainType>(arg);
                     result += absl::StrFormat("%s, ", chainedStruct->sType);
                 }
             }(args)),
             ...);
        },
        mUnpacked);
    result += " )";
    return result;
}

// Validates that an unpacked chain retrieved via ValidateAndUnpack matches a valid "branch",
// where a "branch" is defined as a required "root" extension and optional follow-up
// extensions.
//
// Returns the wgpu::SType associated with the "root" extension of a "branch" if matched,
// otherwise returns an error.
//
// Example usage:
//     UnpackedPtr<T> u;
//     DAWN_TRY_ASSIGN(u, ValidateAndUnpack(desc));
//     wgpu::SType rootType;
//     DAWN_TRY_ASSIGN(rootType,
//         u.ValidateBranches<Branch<Root1>, Branch<Root2, R2Ext1>>());
//     switch (rootType) {
//         case Root1: {
//             <do something>
//         }
//         case Root2: {
//             R2Ext1 ext = u.Get<R2Ext1>(u);
//             if (ext) {
//                 <do something with optional extension(s)>
//             }
//         }
//         default:
//             DAWN_UNREACHABLE();
//     }
//
// The example above checks that the unpacked chain is either:
//     - only a Root1 extension
//     - or a Root2 extension with an optional R2Ext1 extension
// Any other configuration is deemed invalid.
template <typename T>
template <typename... Branches>
ResultOrError<wgpu::SType> UnpackedPtr<T>::ValidateBranches() const {
    using Validator = detail::BranchesValidator<UnpackedPtr<T>, Branches...>;

    wgpu::SType match = wgpu::SType(0u);
    if (Validator::Validate(*this, mBitset, match)) {
        return match;
    }
    return DAWN_VALIDATION_ERROR(
        "Expected chain root to match one of the following branch types with optional extensions:"
        "\n%sInstead found: %s",
        Validator::ToString(), ToString());
}

// Validates that an unpacked chain retrieved via ValidateAndUnpack contains a subset of the
// Allowed extensions. If there are any other extensions, returns an error.
//
// Example usage:
//     UnpackedPtr<T> u;
//     DAWN_TRY_ASSIGN(u, ValidateAndUnpack(desc));
//     DAWN_TRY(u.ValidateSubset<Ext1>());
//
// Even though "valid" extensions on descriptor may include both Ext1 and Ext2, ValidateSubset
// will further enforce that Ext2 is not on the chain in the example above.
template <typename T>
template <typename... Allowed>
MaybeError UnpackedPtr<T>::ValidateSubset() const {
    return detail::SubsetValidator<UnpackedPtr<T>, Allowed...>::Validate(*this, mBitset);
}

}  // namespace dawn::native

#endif  // SRC_DAWN_NATIVE_CHAINUTILS_H_
