/** \file
 *
 *  Contains the MappedIOStream class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#include "MappedIOStream.h"

#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

/// Constructor
MappedIOStream::MappedIOStream( const Str& filename, bool clobber, size_t chunkSize = 4096 )
    : file_( -1 ),
      filename_( filename ),
      chunkSize_( chunkSize ),
      size_( 0 ),
      ptr_( NULL ),
      pos_( 0 )
{
    open( clobber );
}

MappedIOStream::~MappedIOStream()
{
    close();
}

/// reads num bytes from the stream to the buffer
IOStream& MappedIOStream::read( char* buffer, size_t num )
{
    if( checkSize( pos_ + num ) )
    {
        memcpy( buffer, ptr_ + pos_, num );
        setBytesRead( num );
        pos_ += num;
    }
    return *this;
}

bool MappedIOStream::checkSize( size_t pos )
{
    if( NULL != ptr_ && pos > size_ )
    {
        size_t oldSize = size_;
        size_ += chunkSize_;
        lseek( file_, size_ - 1, SEEK_SET );
        ::write( file_, "", 1 );
        ptr_ = ( unsigned char* )mremap( ptr_, oldSize, size_, MREMAP_MAYMOVE );
        if( ptr_ + 1 == NULL )
        {
            ptr_ = NULL;
        }
    }
    return NULL != ptr_;
}

bool MappedIOStream::seek( unsigned int location )
{
    if( checkSize( location ) )
    {
        pos_ = location;
    }
    return false;
}

bool MappedIOStream::eof()
{
    if( isReadable() )
    {
        return pos_ == size_;
    }
    return true;
}

const struct stat MappedIOStream::getStat()
{
    struct stat statValue = {0};
    stat( filename_.cStr(), &statValue );
    return statValue;
}

/// writes num bytes from buffer to the output buffer/stream
IOStream& MappedIOStream::write( const char* buffer, size_t num )
{
    if( checkSize( pos_ + num ) )
    {
        memcpy( ptr_ + pos_, buffer, num );
        lastWritten_ = num;
        pos_ += num;
    }
    return *this;
}

int MappedIOStream::tell()
{
    if( isReadable() )
    {
        return pos_;
    }
    return 0;
}

void MappedIOStream::close()
{
    if( NULL != ptr_ )
    {
        if( munmap( ptr_, size_ ) )
        {
            fprintf( stderr, "Error unmapping memory for %s\n", filename_.cStr() );
        }
        ptr_ = NULL;
    }
    if( -1 != file_ )
    {
        if( ::close( file_ ) )
        {
            fprintf( stderr, "Error closing file %s due to error: %s\n", filename_.cStr(), strerror( errno ) );
        }
        file_ = -1;
    }
}

void MappedIOStream::flush()
{
    if( isWritable() )
    {
        msync( ptr_, size_, MS_SYNC );
    }
}

bool MappedIOStream::open( bool clobber )
{
    close();
    if( clobber )
    {
        file_ = ::open( filename_.cStr(), O_RDWR | O_CREAT | O_TRUNC, 0664 );
    }
    else
    {
        file_ = ::open( filename_.cStr(), O_RDWR | O_CREAT, 0664 );
    }
    if( -1 != file_ )
    {
        struct stat statBuffer;
        size_ = 0;
        if( fstat( file_, &statBuffer ) == 0 )
        {
            size_ = statBuffer.st_size;
        }
        if( 0 == size_ )
        {
            size_ = chunkSize_;
            lseek( file_, size_ - 1, SEEK_SET );
            ::write( file_, "", 1 );
        }
        ptr_ = ( unsigned char* )mmap( NULL, size_, PROT_READ | PROT_WRITE,
                                       MAP_SHARED, file_, 0 );
        if( ptr_ + 1 == NULL )
        {
            ptr_ = NULL;
        }
        if( NULL == ptr_ )
        {
            fprintf( stderr, "Error mapping file %s\n", filename_.cStr() );
        }
        pos_ = 0;
        return NULL != ptr_;
    }
    else
    {
        fprintf( stderr, "Error opening file %s due to error: %s\n", filename_.cStr(), strerror( errno ) );
        return false;
    }
}
