// (C) Copyright 2008 CodeRage, LLC (turkanis at coderage dot com)
// (C) Copyright 2003-2007 Jonathan Turkanis
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)

// See http://www.boost.org/libs/iostreams for documentation.

// Define BOOST_IOSTREAMS_SOURCE so that <boost/iostreams/detail/config.hpp>
// knows that we are building the library (possibly exporting code), rather
// than using it (possibly importing code).
#define BOOST_IOSTREAMS_SOURCE

#include <cassert>
#include <cerrno>
#include <cstdio>                                 // SEEK_SET, etc.
#include <boost/config.hpp>                       // BOOST_JOIN
#include <boost/iostreams/detail/error.hpp>
#include <boost/iostreams/detail/config/dyn_link.hpp>
#include <boost/iostreams/detail/config/rtl.hpp>  // BOOST_IOSTREAMS_FD_XXX
#include <boost/iostreams/detail/config/windows_posix.hpp>
#include <boost/iostreams/detail/system_failure.hpp>
#include <boost/iostreams/detail/ios.hpp>         // openmodes, failure.
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/integer_traits.hpp>
#include <boost/throw_exception.hpp>

    // OS-specific headers for low-level i/o.

#include <fcntl.h>       // file opening flags.
#include <sys/stat.h>    // file access permissions.
#ifdef BOOST_IOSTREAMS_WINDOWS
# include <io.h>         // low-level file i/o.
# define WINDOWS_LEAN_AND_MEAN
# include <windows.h>
# ifndef INVALID_SET_FILE_POINTER
#  define INVALID_SET_FILE_POINTER ((DWORD)-1)
# endif
#else
# include <sys/types.h>  // mode_t.
# include <unistd.h>     // low-level file i/o.
#endif

namespace pdalboost { namespace iostreams {

//------------------Definition of file_descriptor_impl------------------------//

namespace detail {

// Contains the platform dependant implementation
struct file_descriptor_impl {
    // Note: These need to match file_desciptor_flags
    enum flags {
        never_close = 0,
        close_on_exit = 1,
        close_on_close = 2,
        close_always = 3
    };

    file_descriptor_impl();
    ~file_descriptor_impl();
    void open(file_handle fd, flags);
#ifdef BOOST_IOSTREAMS_WINDOWS
    void open(int fd, flags);
#endif
    void open(const detail::path&, BOOST_IOS::openmode);
    bool is_open() const;
    void close();
    void close_impl(bool close_flag, bool throw_);
    std::streamsize read(char* s, std::streamsize n);
    std::streamsize write(const char* s, std::streamsize n);
    std::streampos seek(stream_offset off, BOOST_IOS::seekdir way);
    static file_handle invalid_handle();
    file_handle  handle_;
    int          flags_;
};

//------------------Implementation of file_descriptor_impl--------------------//

file_descriptor_impl::file_descriptor_impl() 
    : handle_(invalid_handle()), flags_(0) 
    { }

file_descriptor_impl::~file_descriptor_impl() 
{ 
    close_impl(flags_ & close_on_exit, false);
}

void file_descriptor_impl::open(file_handle fd, flags f)
{
    // Using 'close' to close the existing handle so that it will throw an
    // exception if it fails.
    //
    // Only closing after assigning the new handle, so that the class will
    // take ownership of the handle regardless of whether close throws.

    file_descriptor_impl tmp;
    tmp.handle_ = handle_;
    tmp.flags_ = flags_ & close_on_exit ? close_on_close : never_close;

    handle_ = fd;
    flags_ = f;
    
    tmp.close();
}

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//

void file_descriptor_impl::open(int fd, flags f)
{ open(reinterpret_cast<file_handle>(_get_osfhandle(fd)), f); }

#endif // #ifdef BOOST_IOSTREAMS_WINDOWS //-----------------------------------//

void file_descriptor_impl::open(const detail::path& p, BOOST_IOS::openmode mode)
{
    close_impl(flags_ & close_on_exit, true);

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//
    DWORD dwDesiredAccess;
    DWORD dwCreationDisposition;
    if ( (mode & (BOOST_IOS::in | BOOST_IOS::out))
             ==
         (BOOST_IOS::in | BOOST_IOS::out) )
    {
        if (mode & BOOST_IOS::app)
            pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
        dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
        dwCreationDisposition =
            (mode & BOOST_IOS::trunc) ?
                CREATE_ALWAYS :
                OPEN_EXISTING;
    } else if (mode & BOOST_IOS::in) {
        if (mode & (BOOST_IOS::app | BOOST_IOS::trunc))
            pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
        dwDesiredAccess = GENERIC_READ;
        dwCreationDisposition = OPEN_EXISTING;
    } else if (mode & BOOST_IOS::out) {
        if ( (mode & (BOOST_IOS::app | BOOST_IOS::trunc))
                 ==
              (BOOST_IOS::app | BOOST_IOS::trunc) )
            pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
        if (mode & BOOST_IOS::app) {
            dwCreationDisposition = OPEN_ALWAYS;
            dwDesiredAccess = 
                FILE_APPEND_DATA |
                FILE_WRITE_ATTRIBUTES |
                FILE_WRITE_EA |
                STANDARD_RIGHTS_WRITE |
                SYNCHRONIZE;
        } else {
            dwDesiredAccess = GENERIC_WRITE;
            dwCreationDisposition = CREATE_ALWAYS;
        }
    } else {
        pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
    }

    HANDLE handle = p.is_wide() ?
        ::CreateFileW( p.c_wstr(),
                       dwDesiredAccess,
                       FILE_SHARE_READ | FILE_SHARE_WRITE,
                       NULL,                   // lpSecurityAttributes
                       dwCreationDisposition,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL ) :                // hTemplateFile
        ::CreateFileA( p.c_str(),
                       dwDesiredAccess,
                       FILE_SHARE_READ | FILE_SHARE_WRITE,
                       NULL,                   // lpSecurityAttributes
                       dwCreationDisposition,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL );                 // hTemplateFile
    if (handle != INVALID_HANDLE_VALUE) {
        handle_ = handle;
        flags_ = close_always;
    } else {
        flags_ = 0;
        throw_system_failure("failed opening file");
    }
#else // #ifdef BOOST_IOSTREAMS_WINDOWS //------------------------------------//

        // Calculate oflag argument to open.

    int oflag = 0;
    if ( (mode & (BOOST_IOS::in | BOOST_IOS::out))
             ==
         (BOOST_IOS::in | BOOST_IOS::out) )
    {
        if( mode & BOOST_IOS::app )
            pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
        oflag |= O_RDWR;
        if( mode & BOOST_IOS::trunc ) {
            oflag |= O_TRUNC;
            oflag |= O_CREAT;
        }
    } else if (mode & BOOST_IOS::in) {
        if( mode & (BOOST_IOS::app | BOOST_IOS::trunc) )
            pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
        oflag |= O_RDONLY;
    } else if (mode & BOOST_IOS::out) {
        if( (mode & (BOOST_IOS::app | BOOST_IOS::trunc))
               ==
            (BOOST_IOS::app | BOOST_IOS::trunc) )
            pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
        oflag |= O_WRONLY;
        if (mode & BOOST_IOS::app)
            oflag |= O_APPEND;
        else {
            oflag |= O_CREAT;
            oflag |= O_TRUNC; 
        }
    } else {
        pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad open mode"));
    }
    #ifdef _LARGEFILE64_SOURCE
        oflag |= O_LARGEFILE;
    #endif

        // Calculate pmode argument to open.

    mode_t pmode = S_IRUSR | S_IWUSR |
                   S_IRGRP | S_IWGRP |
                   S_IROTH | S_IWOTH;

        // Open file.

    int fd = BOOST_IOSTREAMS_FD_OPEN(p.c_str(), oflag, pmode);
    if (fd == -1) {
        pdalboost::throw_exception(system_failure("failed opening file"));
    } else {
        handle_ = fd;
        flags_ = close_always;
    }
#endif // #ifndef BOOST_IOSTREAMS_WINDOWS //----------------------------------//
}

bool file_descriptor_impl::is_open() const
{ return handle_ != invalid_handle(); }

void file_descriptor_impl::close()
{
    close_impl(flags_ & close_on_close, true);
}

void file_descriptor_impl::close_impl(bool close_flag, bool throw_) {
    if (handle_ != invalid_handle()) {
        if (close_flag) {
            bool success = 
                #ifdef BOOST_IOSTREAMS_WINDOWS
                    ::CloseHandle(handle_) == 1;
                #else
                    BOOST_IOSTREAMS_FD_CLOSE(handle_) != -1;
                #endif
            if (!success && throw_)
                throw_system_failure("failed closing file");
        }
        handle_ = invalid_handle();
        flags_ = 0;
    }
}

std::streamsize file_descriptor_impl::read(char* s, std::streamsize n)
{
#ifdef BOOST_IOSTREAMS_WINDOWS
    DWORD result;
    if (!::ReadFile(handle_, s, n, &result, NULL))
    {
        // report EOF if the write-side of a pipe has been closed
        if (GetLastError() == ERROR_BROKEN_PIPE)
        {
            result = 0;
        }
        else
            throw_system_failure("failed reading");
    }
    return result == 0 ? -1 : static_cast<std::streamsize>(result);
#else // #ifdef BOOST_IOSTREAMS_WINDOWS
    errno = 0;
    std::streamsize result = BOOST_IOSTREAMS_FD_READ(handle_, s, n);
    if (errno != 0)
        throw_system_failure("failed reading");
    return result == 0 ? -1 : result;
#endif // #ifdef BOOST_IOSTREAMS_WINDOWS
}

std::streamsize file_descriptor_impl::write(const char* s, std::streamsize n)
{
#ifdef BOOST_IOSTREAMS_WINDOWS
    DWORD ignore;
    if (!::WriteFile(handle_, s, n, &ignore, NULL))
        throw_system_failure("failed writing");
    return n;
#else // #ifdef BOOST_IOSTREAMS_WINDOWS
    int amt = BOOST_IOSTREAMS_FD_WRITE(handle_, s, n);
    if (amt < n) // Handles blocking fd's only.
        throw_system_failure("failed writing");
    return n;
#endif // #ifdef BOOST_IOSTREAMS_WINDOWS
}

std::streampos file_descriptor_impl::seek
    (stream_offset off, BOOST_IOS::seekdir way)
{
#ifdef BOOST_IOSTREAMS_WINDOWS
    LONG lDistanceToMove = static_cast<LONG>(off & 0xffffffff);
    LONG lDistanceToMoveHigh = static_cast<LONG>(off >> 32);
    DWORD dwResultLow =
        ::SetFilePointer( handle_,
                          lDistanceToMove,
                          &lDistanceToMoveHigh,
                          way == BOOST_IOS::beg ?
                              FILE_BEGIN :
                              way == BOOST_IOS::cur ?
                                FILE_CURRENT :
                                FILE_END );
    if ( dwResultLow == INVALID_SET_FILE_POINTER &&
         ::GetLastError() != NO_ERROR )
    {
        pdalboost::throw_exception(system_failure("failed seeking"));
    } else {
       return offset_to_position(
                  (stream_offset(lDistanceToMoveHigh) << 32) + dwResultLow
              );
    }
#else // #ifdef BOOST_IOSTREAMS_WINDOWS
    if ( off > integer_traits<BOOST_IOSTREAMS_FD_OFFSET>::const_max ||
         off < integer_traits<BOOST_IOSTREAMS_FD_OFFSET>::const_min )
    {
        pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("bad offset"));
    }
    stream_offset result =
        BOOST_IOSTREAMS_FD_SEEK(
            handle_,
            static_cast<BOOST_IOSTREAMS_FD_OFFSET>(off),
            ( way == BOOST_IOS::beg ?
                  SEEK_SET :
                  way == BOOST_IOS::cur ?
                      SEEK_CUR :
                      SEEK_END ) 
        );
    if (result == -1)
        pdalboost::throw_exception(system_failure("failed seeking"));
    return offset_to_position(result);
#endif // #ifdef BOOST_IOSTREAMS_WINDOWS
}

// Returns the value stored in a file_handle variable when no file is open
file_handle file_descriptor_impl::invalid_handle()
{
#ifdef BOOST_IOSTREAMS_WINDOWS
    return INVALID_HANDLE_VALUE;
#else
    return -1;
#endif
}

} // End namespace detail.

//------------------Implementation of file_descriptor-------------------------//

file_descriptor::file_descriptor() : pimpl_(new impl_type) { }

file_descriptor::file_descriptor(handle_type fd, file_descriptor_flags f)
    : pimpl_(new impl_type)
{ open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
file_descriptor::file_descriptor(handle_type fd, bool close_on_exit)
    : pimpl_(new impl_type)
{ open(fd, close_on_exit); }
#endif

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//

file_descriptor::file_descriptor(int fd, file_descriptor_flags f)
    : pimpl_(new impl_type)
{ open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
file_descriptor::file_descriptor(int fd, bool close_on_exit)
    : pimpl_(new impl_type)
{ open(fd, close_on_exit); }
#endif

#endif // #ifdef BOOST_IOSTREAMS_WINDOWS //-----------------------------------//

file_descriptor::file_descriptor( const std::string& path,
                                  BOOST_IOS::openmode mode )
    : pimpl_(new impl_type)
{ open(path, mode); }

file_descriptor::file_descriptor( const char* path,
                                  BOOST_IOS::openmode mode )
    : pimpl_(new impl_type)
{ open(path, mode); }

file_descriptor::file_descriptor(const file_descriptor& other) 
    : pimpl_(other.pimpl_) 
    { }

void file_descriptor::open(handle_type fd, file_descriptor_flags f)
{ pimpl_->open(fd, static_cast<detail::file_descriptor_impl::flags>(f)); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
void file_descriptor::open(handle_type fd, bool close_on_exit)
{ pimpl_->open(fd, close_on_exit ?
    detail::file_descriptor_impl::close_always :
    detail::file_descriptor_impl::close_on_close); }
#endif

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//

void file_descriptor::open(int fd, file_descriptor_flags f)
{ pimpl_->open(fd, static_cast<detail::file_descriptor_impl::flags>(f)); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
void file_descriptor::open(int fd, bool close_on_exit)
{ pimpl_->open(fd, close_on_exit ?
    detail::file_descriptor_impl::close_always :
    detail::file_descriptor_impl::close_on_close); }
#endif

#endif // #ifdef BOOST_IOSTREAMS_WINDOWS //-----------------------------------//

void file_descriptor::open(const std::string& path, BOOST_IOS::openmode mode)
{ open(detail::path(path), mode); }

void file_descriptor::open(const char* path, BOOST_IOS::openmode mode)
{ open(detail::path(path), mode); }

bool file_descriptor::is_open() const { return pimpl_->is_open(); }

void file_descriptor::close() { pimpl_->close(); }

std::streamsize file_descriptor::read(char_type* s, std::streamsize n)
{ return pimpl_->read(s, n); }

std::streamsize file_descriptor::write(const char_type* s, std::streamsize n)
{ return pimpl_->write(s, n); }

std::streampos file_descriptor::seek(stream_offset off, BOOST_IOS::seekdir way)
{ return pimpl_->seek(off, way); }

detail::file_handle file_descriptor::handle() const { return pimpl_->handle_; }

void file_descriptor::init() { pimpl_.reset(new impl_type); }

void file_descriptor::open(
    const detail::path& path, 
    BOOST_IOS::openmode mode, 
    BOOST_IOS::openmode base )
{
    mode |= base;
    pimpl_->open(path, mode);
}
                    
//------------------Implementation of file_descriptor_source------------------//

file_descriptor_source::file_descriptor_source(
    handle_type fd, file_descriptor_flags f)
{ open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
file_descriptor_source::file_descriptor_source(
    handle_type fd, bool close_on_exit)
{ open(fd, close_on_exit); }
#endif

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//

file_descriptor_source::file_descriptor_source(int fd, file_descriptor_flags f)
{ open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
file_descriptor_source::file_descriptor_source(int fd, bool close_on_exit)
{ open(fd, close_on_exit); }
#endif

#endif // #ifdef BOOST_IOSTREAMS_WINDOWS //-----------------------------------//

file_descriptor_source::file_descriptor_source(
    const std::string& path, BOOST_IOS::openmode mode)
{ open(path, mode); }

file_descriptor_source::file_descriptor_source(
    const char* path, BOOST_IOS::openmode mode)
{ open(path, mode); }

file_descriptor_source::file_descriptor_source(
    const file_descriptor_source& other) 
        : file_descriptor(static_cast<const file_descriptor&>(other)) 
    { }

void file_descriptor_source::open(handle_type fd, file_descriptor_flags f)
{ file_descriptor::open(fd, f);  }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
void file_descriptor_source::open(handle_type fd, bool close_on_exit)
{ file_descriptor::open(fd, close_on_exit); }
#endif

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//

void file_descriptor_source::open(int fd, file_descriptor_flags f)
{ file_descriptor::open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
void file_descriptor_source::open(int fd, bool close_on_exit)
{ file_descriptor::open(fd, close_on_exit); }
#endif

#endif // #ifdef BOOST_IOSTREAMS_WINDOWS //-----------------------------------//

void file_descriptor_source::open(
    const std::string& path, BOOST_IOS::openmode mode)
{ open(detail::path(path), mode); }

void file_descriptor_source::open(
    const char* path, BOOST_IOS::openmode mode)
{ open(detail::path(path), mode); }

void file_descriptor_source::open(
    const detail::path& path, BOOST_IOS::openmode mode)
{ 
    if (mode & (BOOST_IOS::out | BOOST_IOS::app | BOOST_IOS::trunc))
        pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("invalid mode"));
    file_descriptor::open(path, mode, BOOST_IOS::in); 
}
                    
//------------------Implementation of file_descriptor_sink--------------------//

file_descriptor_sink::file_descriptor_sink(
    handle_type fd, file_descriptor_flags f)
{ open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
file_descriptor_sink::file_descriptor_sink(
    handle_type fd, bool close_on_exit)
{ open(fd, close_on_exit); }
#endif

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//

file_descriptor_sink::file_descriptor_sink(int fd, file_descriptor_flags f)
{ open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
file_descriptor_sink::file_descriptor_sink(int fd, bool close_on_exit)
{ open(fd, close_on_exit); }
#endif

#endif // #ifdef BOOST_IOSTREAMS_WINDOWS //-----------------------------------//

file_descriptor_sink::file_descriptor_sink(
    const std::string& path, BOOST_IOS::openmode mode)
{ open(path, mode); }

file_descriptor_sink::file_descriptor_sink(
    const char* path, BOOST_IOS::openmode mode)
{ open(path, mode); }

file_descriptor_sink::file_descriptor_sink(const file_descriptor_sink& other) 
    : file_descriptor(static_cast<const file_descriptor&>(other)) 
    { }

void file_descriptor_sink::open(handle_type fd, file_descriptor_flags f)
{ file_descriptor::open(fd, f);  }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
void file_descriptor_sink::open(handle_type fd, bool close_on_exit)
{ file_descriptor::open(fd, close_on_exit); }
#endif

#ifdef BOOST_IOSTREAMS_WINDOWS //---------------------------------------------//

void file_descriptor_sink::open(int fd, file_descriptor_flags f)
{ file_descriptor::open(fd, f); }

#if defined(BOOST_IOSTREAMS_USE_DEPRECATED)
void file_descriptor_sink::open(int fd, bool close_on_exit)
{ file_descriptor::open(fd, close_on_exit); }
#endif

#endif // #ifdef BOOST_IOSTREAMS_WINDOWS //-----------------------------------//

void file_descriptor_sink::open(
    const std::string& path, BOOST_IOS::openmode mode)
{ open(detail::path(path), mode); }

void file_descriptor_sink::open(
    const char* path, BOOST_IOS::openmode mode)
{ open(detail::path(path), mode); }

void file_descriptor_sink::open(
    const detail::path& path, BOOST_IOS::openmode mode)
{ 
    if (mode & BOOST_IOS::in)
        pdalboost::throw_exception(BOOST_IOSTREAMS_FAILURE("invalid mode"));
    file_descriptor::open(path, mode, BOOST_IOS::out); 
}

} } // End namespaces iostreams, boost.
