#pragma once

#include <unistd.h> // for close()

namespace util
{

/**
 * @class FileDescriptor
 *
 * This class manages an open file descriptor.
 *
 * The file descriptor can be closed by calling close().  Otherwise it will be
 * closed by the destructor.
 *
 * FileDescriptor objects cannot be copied, but they can be moved.  This enables
 * them to be stored in containers like std::vector.
 */
class FileDescriptor
{
  public:
    FileDescriptor()                      = default;
    FileDescriptor(const FileDescriptor&) = delete;
    FileDescriptor& operator=(const FileDescriptor&) = delete;

    /**
     * Constructor.
     *
     * @param[in] fd - File descriptor
     */
    explicit FileDescriptor(int fd) : fd(fd) {}

    /**
     * Move constructor.
     *
     * Transfers ownership of a file descriptor.
     *
     * @param other - FileDescriptor object being moved
     */
    FileDescriptor(FileDescriptor&& other) : fd(other.fd)
    {
        other.fd = -1;
    }

    /**
     * Move assignment operator.
     *
     * Closes the file descriptor owned by this object, if any.  Then transfers
     * ownership of the file descriptor owned by the other object.
     *
     * @param other - FileDescriptor object being moved
     */
    FileDescriptor& operator=(FileDescriptor&& other)
    {
        // Verify not assigning object to itself (a = std::move(a))
        if (this != &other)
        {
            set(other.fd);
            other.fd = -1;
        }
        return *this;
    }

    /**
     * Destructor.
     *
     * Closes the file descriptor if necessary.
     */
    ~FileDescriptor()
    {
        close();
    }

    /**
     * Returns the file descriptor.
     *
     * @return File descriptor.  Returns -1 if this object does not contain an
     *         open file descriptor.
     */
    int operator()() const
    {
        return fd;
    }

    /**
     * Returns whether this object contains an open file descriptor.
     *
     * @return true if object contains an open file descriptor, false otherwise.
     */
    operator bool() const
    {
        return fd != -1;
    }

    /**
     * Closes the file descriptor.
     *
     * Does nothing if the file descriptor was not set or was already closed.
     *
     * @return 0 if descriptor was successfully closed.  Returns -1 if an error
     *         occurred; errno will be set appropriately.
     */
    int close()
    {
        int rc = 0;
        if (fd >= 0)
        {
            rc = ::close(fd);
            fd = -1;
        }
        return rc;
    }

    /**
     * Sets the file descriptor.
     *
     * Closes the previous file descriptor if necessary.
     *
     * @param[in] descriptor - File descriptor
     */
    void set(int descriptor)
    {
        close();
        fd = descriptor;
    }

  private:
    /**
     * File descriptor.
     */
    int fd = -1;
};

} // namespace util