/*
 * Copyright (c) 1999
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1999
 * Boris Fomitchev
 *
 * This material is provided "as is", with absolutely no warranty expressed
 * or implied. Any use is at your own risk.
 *
 * Permission to use or copy this software for any purpose is hereby granted
 * without fee, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 *
 */

#if HAVE_CONFIG_H
#include <ldastoolsal_config.h>
#endif /* HAVE_CONFIG_H */

#include <iostream>
#include "ldastoolsal/fstream.hh"

using std::ios_base;
using std::streambuf;
using std::streamoff;

// open/close/read/write
#include <sys/stat.h> // For stat
#include <sys/mman.h> // For mmap

#include <unistd.h>
#include <fcntl.h>

#ifndef O_ACCMODE
#define O_ACCMODE ( O_RDONLY | O_WRONLY | O_RDWR )
#endif

#define ULL( x ) ( (unsigned long long)x )

/* :TODO: Need to write the doxygen documentation for fstream.cc */

namespace
{
    ios_base::openmode
    flag_to_openmode( int mode )
    {
        ios_base::openmode ret = std::ios_base::in | std::ios_base::out;

        switch ( mode & O_ACCMODE )
        {
        case O_RDONLY:
            ret = ios_base::in;
            break;
        case O_WRONLY:
            ret = ios_base::out;
            break;
        case O_RDWR:
            ret = ios_base::in | ios_base::out;
            break;
        }

        if ( mode & O_APPEND )
        {
            ret |= ios_base::app;
        }
        return ret;
    }

    // Helper functions for _Filebuf_base.
    bool
    __is_regular_file( _STLP_fd fd )
    {
        struct stat buf;
        return fstat( fd, &buf ) == 0 && S_ISREG( buf.st_mode );
    }

    // Number of characters in the file.
    streamoff
    __file_size( _STLP_fd fd )
    {
        streamoff ret = 0;

        struct stat buf;
        if ( fstat( fd, &buf ) == 0 && S_ISREG( buf.st_mode ) )
            ret = buf.st_size > 0 ? buf.st_size : 0;
        return ret;
    }
} // namespace

// Visual C++ and Intel use this, but not Metrowerks
// Also MinGW, msvcrt.dll (but not crtdll.dll) dependent version
#if ( !defined( __MSL__ ) && !defined( _STLP_WINCE ) && defined( _MSC_VER ) && \
      defined( _WIN32 ) ) ||                                                   \
    ( defined( __MINGW32__ ) && defined( __MSVCRT__ ) )

// fcntl(fileno, F_GETFL) for Microsoft library
// 'semi-documented' defines:
#define IOINFO_L2E 5
#define IOINFO_ARRAY_ELTS ( 1 << IOINFO_L2E )
#define _pioinfo( i )                                                          \
    ( __pioinfo[ ( i ) >> IOINFO_L2E ] + ( ( i ) & ( IOINFO_ARRAY_ELTS - 1 ) ) )
#define FAPPEND 0x20 // O_APPEND flag
#define FTEXT 0x80 // O_TEXT flag
// end of 'semi-documented' defines

// fbp : empirical
#define F_WRITABLE 0x04

// 'semi-documented' internal structure
extern "C" {
struct ioinfo
{
    long osfhnd; // the real os HANDLE
    char osfile; // file handle flags
    char pipech; // pipe buffer
#ifdef _MT
    // multi-threaded locking
    int              lockinitflag;
    CRITICAL_SECTION lock;
#endif /* _MT */
};
#ifdef __MINGW32__
__MINGW_IMPORT ioinfo* __pioinfo[];
#else
extern _CRTIMP ioinfo* __pioinfo[];
#endif

} // extern "C"
// end of 'semi-documented' declarations

ios_base::openmode
_get_osfflags( int fd, HANDLE oshandle )
{
    char dosflags = 0;

    dosflags = _pioinfo( fd )->osfile;
    // end of 'semi-documented' stuff
    int mode = 0;

    if ( dosflags & FAPPEND )
        mode |= O_APPEND;
    if ( dosflags & FTEXT )
        mode |= O_TEXT;
    else
        mode |= O_BINARY;

        // we have to guess
#ifdef __macintosh
    DWORD dummy, dummy2;
    if ( WriteFile( oshandle, &dummy2, 0, &dummy, 0 ) )
        mode |= O_RDWR;
    else
        mode |= O_RDONLY;
#else
    // on Windows, this seems to work...
    if ( dosflags & F_WRITABLE )
        mode |= O_RDWR;
    else
        mode |= O_RDONLY;
#endif

    return flag_to_openmode( mode );
}

#elif defined( __DMC__ )

#define FHND_APPEND 0x04
#define FHND_DEVICE 0x08
#define FHND_TEXT 0x10

extern "C" unsigned char __fhnd_info[ _NFILE ];

ios_base::openmode
_get_osfflags( int fd, HANDLE oshandle )
{
    int mode = 0;

    if ( __fhnd_info[ fd ] & FHND_APPEND )
        mode |= O_APPEND;

    if ( __fhnd_info[ fd ] & FHND_TEXT == 0 )
        mode |= O_BINARY;

    for ( FILE* fp = &_iob[ 0 ]; fp < &_iob[ _NFILE ]; fp++ )
    {
        if ( ( fileno( fp ) == fd ) &&
             ( fp->_flag & ( _IOREAD | _IOWRT | _IORW ) ) )
        {
            const int osflags = fp->_flag;

            if ( ( osflags & _IOREAD ) && !( osflags & _IOWRT ) &&
                 !( osflags & _IORW ) )
                mode |= O_RDONLY;
            else if ( ( osflags & _IOWRT ) && !( osflags & _IOREAD ) &&
                      !( osflags & _IORW ) )
                mode |= O_WRONLY;
            else
                mode |= O_RDWR;

            break;
        }
    }

    return flag_to_openmode( mode );
}
#endif // _MSC_VER

#ifndef MAP_FAILED /* MMAP failure return code */
#define MAP_FAILED -1
#endif

namespace LDASTools
{

    namespace AL
    {
        size_t _Filebuf_base::_M_page_size = 4096;

        const std::ios_base::openmode _Filebuf_base::m_default_openmode =
            std::ios_base::in | std::ios_base::out;

        _Filebuf_base::_Filebuf_base( )
            : _M_file_id( (_STLP_fd)-1 ), _M_openmode( m_default_openmode ),
              _M_is_open( false ), _M_should_close( false )
        {
            if ( !_M_page_size )
            {
                _M_page_size = sysconf( _SC_PAGESIZE );
            }

            if ( _M_page_size <= 0 )
            {
                _M_page_size = 4096;
            }
        }

        // Return the size of the file.  This is a wrapper for stat.
        // Returns zero if the size cannot be determined or is ill-defined.
        streamoff
        _Filebuf_base::_M_file_size( )
        {
            return __file_size( _M_file_id );
        }

        bool
        _Filebuf_base::_M_open( const char*        name,
                                ios_base::openmode openmode,
                                long               permission )
        {
            _STLP_fd file_no;

            if ( _M_is_open )
                return false;

            int flags = 0;

            // Unix makes no distinction between text and binary files.
            ios_base::openmode mode =
                ( openmode & ( ~ios_base::ate & ~ios_base::binary ) );
            if ( ( mode == ios_base::out ) ||
                 ( mode == ( ios_base::out | ios_base::trunc ) ) )
            {
                flags = O_WRONLY | O_CREAT | O_TRUNC;
            }
            else if ( mode == ( ios_base::out | ios_base::app ) )
            {
                flags = O_WRONLY | O_CREAT | O_APPEND;
            }
            else if ( mode == ios_base::in )
            {
                flags = O_RDONLY;
                permission = 0; // Irrelevant unless we're writing.
            }
            else if ( mode == ( ios_base::in | ios_base::out ) )
            {
                flags = O_RDWR;
            }
            else if ( mode ==
                      ( ios_base::in | ios_base::out | ios_base::trunc ) )
            {
                flags = O_RDWR | O_CREAT | O_TRUNC;
            }
            else
            {
                // The above are the only combinations of flags allowed
                // by the C++ standard.
                return false;
            }

            file_no = open( name, flags, permission );

            if ( file_no < 0 )
                return false;

            _M_is_open = true;

            if ( openmode & ios_base::ate )
            {
                if ( lseek( file_no, 0, SEEK_END ) == -1 )
                {
                    _M_is_open = false;
                }
            }

            _M_file_id = file_no;
            _M_should_close = _M_is_open;
            _M_openmode = openmode;

            if ( _M_is_open )
                _M_regular_file = __is_regular_file( _M_file_id );

            return _M_is_open;
        }

        bool
        _Filebuf_base::_M_open( const char* name, ios_base::openmode openmode )
        {
            // This doesn't really grant everyone in the world read/write
            // access.  On Unix, file-creation system calls always clear
            // bits that are set in the umask from the permissions flag.
            return this->_M_open( name,
                                  openmode,
                                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
                                      S_IROTH | S_IWOTH );
        }

        // Associated the filebuf with a file descriptor pointing to an already-
        // open file.  Mode is set to be consistent with the way that the file
        // was opened.
        bool
        _Filebuf_base::_M_open( int file_no, ios_base::openmode init_mode )
        {

            if ( _M_is_open || file_no < 0 )
                return false;

            (void)init_mode; // dwa 4/27/00 - suppress unused parameter warning
            int mode;
            mode = fcntl( file_no, F_GETFL );

            if ( mode == -1 )
                return false;

            _M_openmode = flag_to_openmode( mode );

            _M_is_open = true;
            _M_file_id = (_STLP_fd)file_no;
            _M_should_close = false;
            _M_regular_file = __is_regular_file( _M_file_id );

            return true;
        }

        bool
        _Filebuf_base::_M_close( )
        {
            if ( !_M_is_open )
                return false;

            bool ok;

            if ( !_M_should_close )
                ok = true;
            else
            {

                ok = ( close( _M_file_id ) == 0 );
            }

            _M_is_open = _M_should_close = false;
            _M_openmode = m_default_openmode;
            return ok;
        }

#define _STLP_LF 10
#define _STLP_CR 13
#define _STLP_CTRLZ 26

        // Read up to n characters into a buffer.  Return value is number of
        // characters read.
        std::ptrdiff_t
        _Filebuf_base::_M_read( char* buf, std::ptrdiff_t n )
        {
            return read( _M_file_id, buf, n );
        }

        // Write n characters from a buffer.  Return value: true if we managed
        // to write the entire buffer, false if we didn't.
        bool
        _Filebuf_base::_M_write( char* buf, std::ptrdiff_t n )
        {

            while ( true )
            {
                std::ptrdiff_t written;

                written = write( _M_file_id, buf, n );

                if ( n == written )
                    return true;
                else if ( written > 0 && written < n )
                {
                    n -= written;
                    buf += written;
                }
                else
                    return false;
            }
        }

        // Wrapper for lseek or the like.
        streamoff
        _Filebuf_base::_M_seek( streamoff offset, ios_base::seekdir dir )
        {
            streamoff result = -1;

            int whence;

            switch ( dir )
            {
            case ios_base::beg:
                if ( offset < 0 /* || offset > _M_file_size() */ )
                    return streamoff( -1 );
                whence = SEEK_SET;
                break;
            case ios_base::cur:
                whence = SEEK_CUR;
                break;
            case ios_base::end:
                if ( /* offset > 0 || */ -offset > _M_file_size( ) )
                    return streamoff( -1 );
                whence = SEEK_END;
                break;
            default:
                return streamoff( -1 );
            }

            result = lseek( _M_file_id, offset, whence );

            return result;
        }

        // Attempts to memory-map len bytes of the current file, starting
        // at position offset.  Precondition: offset is a multiple of the
        // page size.  Postcondition: return value is a null pointer if the
        // memory mapping failed.  Otherwise the return value is a pointer to
        // the memory-mapped file and the file position is set to offset.
        void*
        _Filebuf_base::_M_mmap( streamoff offset, streamoff len )
        {
            void* base;
            base = mmap( 0, len, PROT_READ, MAP_PRIVATE, _M_file_id, offset );
            if ( base != (void*)MAP_FAILED )
            {
                if ( lseek( _M_file_id, offset + len, SEEK_SET ) < 0 )
                {
                    this->_M_unmap( base, len );
                    base = 0;
                }
            }
            else
                base = 0;

            return base;
        }

        void
        _Filebuf_base::_M_unmap( void* base, streamoff len )
        {
            // precondition : there is a valid mapping at the moment
            munmap( (char*)base, len );
        }

        // fbp : let us map 1 MB maximum, just be sure not to trash VM
        const std::streamoff MMAP_CHUNK = std::streamoff( 0x100000UL );

        int
        _Underflow< char, std::char_traits< char > >::_M_doit(
            basic_filebuf< char, std::char_traits< char > >* __this )
        {
            if ( !__this->_M_in_input_mode )
            {
                if ( !__this->_M_switch_to_input_mode( ) )
                    return traits_type::eof( );
            }

            else if ( __this->_M_in_putback_mode )
            {
                __this->_M_exit_putback_mode( );
                if ( __this->gptr( ) != __this->egptr( ) )
                {
                    int_type __c = traits_type::to_int_type( *__this->gptr( ) );
                    return __c;
                }
            }

            // If it's a disk file, and if the internal and external character
            // sequences are guaranteed to be identical, and the user has not
            // explicately disabled the use of memory mapped I/O, then try to
            // use memory mapped I/O.  Otherwise, revert to ordinary read.
            if ( __this->_M_base.__regular_file( ) &&
                 __this->_M_always_noconv &&
                 __this->_M_base._M_in_binary_mode( ) &&
                 __this->_M_use_memory_mapped_io )
            {
                // If we've mmapped part of the file already, then unmap it.
                if ( __this->_M_mmap_base )
                    __this->_M_base._M_unmap( __this->_M_mmap_base,
                                              __this->_M_mmap_len );
                __this->_M_mmap_base = 0;
                __this->_M_mmap_len = 0;

                // Determine the position where we start mapping.  It has to be
                // a multiple of the page size.
                streamoff __cur = __this->_M_base._M_seek( 0, ios_base::cur );
                streamoff __size = __this->_M_base._M_file_size( );
                if ( __size > 0 && __cur >= 0 && __cur < __size )
                {
                    streamoff __offset =
                        ( __cur / __this->_M_base.__page_size( ) ) *
                        __this->_M_base.__page_size( );
                    streamoff __remainder = __cur - __offset;

                    __this->_M_mmap_len = __size - __offset;

                    if ( __this->_M_mmap_len > MMAP_CHUNK )
                        __this->_M_mmap_len = MMAP_CHUNK;

                    if ( ( __this->_M_mmap_base = __this->_M_base._M_mmap(
                               __offset, __this->_M_mmap_len ) ) != 0 )
                    {
                        __this->setg( (char*)__this->_M_mmap_base,
                                      (char*)__this->_M_mmap_base + __remainder,
                                      (char*)__this->_M_mmap_base +
                                          __this->_M_mmap_len );
                        return traits_type::to_int_type( *__this->gptr( ) );
                    }
                }
                else /* size > 0 ... */
                {
                    // There is nothing to map. We unmapped the file above, now
                    // zap pointers.
                    __this->_M_mmap_base = 0;
                    __this->_M_mmap_len = 0;
                }
            }

            return __this->_M_underflow_aux( );
        }

        //----------------------------------------------------------------------
        // Force instantiation of filebuf and fstream classes.
        template class basic_filebuf< char >;
        template class basic_ifstream< char >;
        template class basic_ofstream< char >;
        template class basic_fstream< char >;

        template class _Underflow< wchar_t >;
        template class basic_filebuf< wchar_t >;
        template class basic_ifstream< wchar_t >;
        template class basic_ofstream< wchar_t >;
        template class basic_fstream< wchar_t >;
    } // namespace AL
} // namespace LDASTools
