#ifndef TILE_BASE_HANDLE_H
#define TILE_BASE_HANDLE_H

#pragma once

#include "tile/base/logging.h"

#include <unistd.h>

namespace tile {
namespace detail {
struct HandleDeleter {
    void operator()(int fd) const noexcept
    {
        if (fd == 0 || fd == -1) { return; }
        TILE_PCHECK(::close(fd) == 0);
    }
};

namespace handle {
template<typename T, typename... Args>
constexpr T
GetFirstArg(T first, Args...)
{
    return first;
}

template<typename T>
constexpr bool
NotEqualToAnd(T val, T first)
{
    return val != first;
}

template<typename T, typename... Args>
constexpr bool
NotEqualToAnd(T val, T first, Args... args)
{
    return val != first && NotEqualToAnd(val, std::forward<Args>(args)...);
}

}// namespace handle

template<typename T, typename Deleter, T... kInvalidHandles>
class GenericHandle : private Deleter {
    static constexpr T kDefaultInvalid = ::tile::detail::handle::GetFirstArg(kInvalidHandles...);

public:
    GenericHandle() = default;

    constexpr explicit GenericHandle(T handle) noexcept : handle_(handle) {}

    ~GenericHandle() { Reset(); }

    GenericHandle(GenericHandle &&h) noexcept
    {
        handle_   = h.handle_;
        h.handle_ = kDefaultInvalid;
    }

    GenericHandle &operator=(GenericHandle &&h) noexcept
    {
        if (&h == this) { return *this; }

        handle_   = h.handle_;
        h.handle_ = kDefaultInvalid;
        return *this;
    }

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

    T *Retrieve() noexcept { return &handle_; }

    constexpr T Get() const noexcept { return handle_; }

    constexpr explicit operator bool() const noexcept
    {
        return ::tile::detail::handle::NotEqualToAnd(handle_, kInvalidHandles...);
    }

    TILE_NODISCARD T Leak() noexcept
    {
        int rc  = handle_;
        handle_ = kDefaultInvalid;
        return rc;
    }

    void Reset(T new_value = kDefaultInvalid) noexcept
    {
        if (operator bool()) { Deleter::operator()(handle_); }
        handle_ = new_value;
    }

private:
    T handle_ = kDefaultInvalid;
};

}// namespace detail

using Handle = detail::GenericHandle<int, detail::HandleDeleter, -1, 0>;
}// namespace tile

#endif// TILE_BASE_HANDLE_H
