#ifndef _TILE_BASE_ERASED_PTR_H
#define _TILE_BASE_ERASED_PTR_H

#pragma once

#include "tile/base/internal/macro.h"
#include "tile/base/likely.h"

#include <utility>

namespace tile {
// RAII wrapper ptr
class ErasedPtr final {
public:
    using Deleter = void (*)(void *);

    constexpr ErasedPtr(std::nullptr_t = nullptr) : ptr_(nullptr), deleter_(nullptr) {}

    template<typename T>
    constexpr ErasedPtr(T *ptr) noexcept
        : ptr_(ptr),
          deleter_([](void *ptr) { delete static_cast<T *>(ptr); })
    {}

    template<typename T, typename D>
    constexpr ErasedPtr(T *ptr, D deleter) noexcept
        : ptr_(ptr),
          deleter_(deleter)
    {}

    ErasedPtr(ErasedPtr &&ptr) noexcept : ptr_(ptr.ptr_), deleter_(ptr.deleter_) { ptr.ptr_ = nullptr; }

    ErasedPtr &operator=(ErasedPtr &&ptr) noexcept
    {
        if (TILE_LIKELY(this != &ptr)) { Reset(); }
        return *this;
    }

    ErasedPtr(const ErasedPtr &)            = delete;
    ErasedPtr &operator=(const ErasedPtr &) = delete;

    ~ErasedPtr()
    {
        if (ptr_) { deleter_(ptr_); }
    }

    constexpr void *Get() const noexcept { return ptr_; }

    template<typename T>
    T *UncheckedGet() const noexcept
    {
        return static_cast<T *>(ptr_);
    }

    constexpr explicit operator bool() const noexcept { return !!ptr_; }

    void Reset(std::nullptr_t = nullptr) noexcept
    {
        if (ptr_) {
            deleter_(ptr_);
            ptr_ = nullptr;
        }
    }

    TILE_NODISCARD void *Leak() noexcept
    {
        void *rc = nullptr;
        std::swap(rc, ptr_);
        return rc;
    }

    constexpr Deleter GetDeleter() const noexcept { return deleter_; }

private:
    void *ptr_;
    Deleter deleter_;
};
}// namespace tile

#endif// _TILE_BASE_ERASED_PTR_H
