// Copyright 2020 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_COMMON_REFBASE_H_
#define SRC_DAWN_COMMON_REFBASE_H_

#include <concepts>
#include <cstddef>
#include <utility>

#include "src/dawn/common/Assert.h"
#include "src/dawn/common/Compiler.h"

namespace dawn {

// A common class for various smart-pointers acting on referenceable/releasable pointer-like
// objects. Logic for each specialization can be customized using a Traits type that looks
// like the following:
//
//   struct {
//      static constexpr T kNullValue = ...;
//      static void AddRef(T value) { ... }
//      static void Release(T value) { ... }
//   };
//
// RefBase supports
template <typename T, typename Traits>
class RefBase {
  public:
    // Default constructor and destructor.
    RefBase() : mValue(Traits::kNullValue) {}

    ~RefBase() { Release(mValue); }

    // Constructors from nullptr.
    // NOLINTNEXTLINE(runtime/explicit)
    constexpr RefBase(std::nullptr_t) : RefBase() {}

    RefBase<T, Traits>& operator=(std::nullptr_t) {
        Set(Traits::kNullValue);
        return *this;
    }

    // Constructors from a value T.
    // NOLINTNEXTLINE(runtime/explicit)
    RefBase(T value) : mValue(value) { AddRef(value); }

    RefBase<T, Traits>& operator=(const T& value) {
        Set(value);
        return *this;
    }

    // Constructors from a RefBase<T>
    RefBase(const RefBase<T, Traits>& other) : mValue(other.mValue) { AddRef(other.mValue); }

    RefBase<T, Traits>& operator=(const RefBase<T, Traits>& other) {
        Set(other.mValue);
        return *this;
    }

    RefBase(RefBase<T, Traits>&& other) { mValue = other.Detach(); }

    RefBase<T, Traits>& operator=(RefBase<T, Traits>&& other) {
        if (&other != this) {
            Release(mValue);
            mValue = other.Detach();
        }
        return *this;
    }

    // Constructors from a RefBase<U>. Note that in the *-assignment operators this cannot be the
    // same as `other` because overload resolution rules would have chosen the *-assignement
    // operators defined with `other` == RefBase<T, Traits>.
    template <typename U, typename UTraits>
        requires std::convertible_to<U, T>
    RefBase(const RefBase<U, UTraits>& other) : mValue(other.mValue) {
        AddRef(other.mValue);
    }

    template <typename U, typename UTraits>
        requires std::convertible_to<U, T>
    RefBase<T, Traits>& operator=(const RefBase<U, UTraits>& other) {
        Set(other.mValue);
        return *this;
    }

    template <typename U, typename UTraits>
        requires std::convertible_to<U, T>
    RefBase(RefBase<U, UTraits>&& other) {
        mValue = other.Detach();
    }

    template <typename U, typename UTraits>
        requires std::convertible_to<U, T>
    RefBase<T, Traits>& operator=(RefBase<U, UTraits>&& other) {
        Release(mValue);
        mValue = other.Detach();
        return *this;
    }

    explicit operator bool() const { return !!mValue; }

    // Comparison operators.
    bool operator==(const T& other) const { return mValue == other; }
    bool operator!=(const T& other) const { return mValue != other; }

    bool operator==(const RefBase<T, Traits>& other) const = default;

    const T operator->() const { return mValue; }
    T operator->() { return mValue; }

    bool operator<(const RefBase<T, Traits>& other) const { return mValue < other.mValue; }

    // Smart pointer methods.
    const T& Get() const { return mValue; }
    T& Get() { return mValue; }

    [[nodiscard]] T Detach() {
        T value{std::move(mValue)};
        mValue = Traits::kNullValue;
        return value;
    }

    void Acquire(T value) {
        Release(mValue);
        mValue = value;
    }

    void Reset() { Set(Traits::kNullValue); }

    [[nodiscard]] T* InitializeInto() {
        DAWN_ASSERT(mValue == Traits::kNullValue);
        return &mValue;
    }

    // Cast operator.
    template <typename Other>
    Other Cast() && {
        Other other;
        CastImpl(this, &other);
        return other;
    }

  private:
    // Friend is needed so that instances of RefBase<U> can call AddRef and Release on
    // RefBase<T>.
    template <typename U, typename UTraits>
    friend class RefBase;

    template <typename U, typename UTraits>
        requires std::convertible_to<U, T>
    static void CastImpl(RefBase<T, Traits>* ref, RefBase<U, UTraits>* other) {
        other->Acquire(static_cast<U>(ref->Detach()));
    }

    static void AddRef(T value) {
        if (value != Traits::kNullValue) {
            Traits::AddRef(value);
        }
    }
    static void Release(T value) {
        if (value != Traits::kNullValue) {
            Traits::Release(value);
        }
    }

    void Set(T value) {
        if (mValue != value) {
            // Ensure that the new value is referenced before the old is released to prevent any
            // transitive frees that may affect the new value.
            AddRef(value);
            Release(mValue);
            mValue = value;
        }
    }

    T mValue;
};

}  // namespace dawn

#endif  // SRC_DAWN_COMMON_REFBASE_H_
