/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2016 Danny Robson <danny@nerdcruft.net>
 */

#include "fd.hpp"

#include "except.hpp"
#include "cast.hpp"

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using cruft::posix::fd;


///////////////////////////////////////////////////////////////////////////////
fd::fd (const std::experimental::filesystem::path &path, int flags):
    fd (path, flags, 0666)
{ ; }


//-----------------------------------------------------------------------------
fd::fd (const std::experimental::filesystem::path &path, int flags, mode_t mode):
    m_fd (error::try_value (::open (path.u8string ().c_str (), flags, mode)))
{
    // You always want binary mode. Always.
    //
    // But we want the user to have considered this platform issue regardless
    // so we won't forcibly set the flag, but we will abort the program when
    // debugging if they've failed to do so.
    CHECK_EQ (flags & O_BINARY, O_BINARY);
}


///////////////////////////////////////////////////////////////////////////////
fd::fd (fd &&rhs) noexcept:
    m_fd (-1)
{
    std::swap (m_fd, rhs.m_fd);
}


//-----------------------------------------------------------------------------
fd&
fd::operator= (fd &&rhs) noexcept
{
    close ();
    std::swap (m_fd, rhs.m_fd);
    return *this;
}


//-----------------------------------------------------------------------------
fd&
fd::operator= (int rhs)
{
    reset (rhs);
    return *this;
}


///////////////////////////////////////////////////////////////////////////////
fd::fd (int _fd):
    m_fd (_fd)
{ ; }


///////////////////////////////////////////////////////////////////////////////
fd
fd::dup (void) const
{
    return dup (m_fd);
}


//-----------------------------------------------------------------------------
fd
fd::dup (int _fd)
{
    return fd {
        error::try_value (::dup (_fd))
    };
}


///////////////////////////////////////////////////////////////////////////////
fd::~fd ()
{
    if (m_fd < 0)
        return;
    close ();
}


///////////////////////////////////////////////////////////////////////////////
struct ::stat
fd::stat (void) const
{
    struct stat buf;
    error::try_value (fstat (m_fd, &buf));
    return buf;
}


///////////////////////////////////////////////////////////////////////////////
void
fd::close (void)
{
    error::try_value (::close (m_fd));
    m_fd = -1;
}


//-----------------------------------------------------------------------------
void
fd::reset (void)
{
    if (m_fd >= 0) {
        close ();
        m_fd = -1;
    }
}


//-----------------------------------------------------------------------------
void
fd::reset (int rhs)
{
    if (m_fd >= 0)
        close ();
    m_fd = rhs;
}


//-----------------------------------------------------------------------------
int
fd::release (void)
{
    int tmp = m_fd;
    m_fd = -1;
    return tmp;
}


///////////////////////////////////////////////////////////////////////////////
ssize_t
fd::read (void *buffer, std::size_t count)
{
    return error::try_value (
        ::read (m_fd, buffer, cruft::cast::narrow<unsigned> (count))
    );
}


///////////////////////////////////////////////////////////////////////////////
ssize_t
fd::write (const void *buffer, size_t count)
{
    return error::try_value (
        ::write (m_fd, buffer, cruft::cast::narrow<unsigned> (count))
    );
}


///////////////////////////////////////////////////////////////////////////////
off_t
fd::lseek (off_t offset, int whence)
{
    return error::try_value (
        ::lseek (m_fd, offset, whence)
    );
}


///////////////////////////////////////////////////////////////////////////////
fd::operator int (void) const
{
    return m_fd;
}