/**********************************************************************
 * $Id: cpl_mfile.cpp,v 1.1 2003/08/19 21:32:24 jan Exp $
 *
 * Project:  CPL - Common Portability Library
 * Purpose:  Implement MFILE interface for memory based files.
 * Author:   Jonathan Coles, jonathan@intevation.de
 *
 **********************************************************************
 * Copyright (c) 2003, Intevation GmbH
 **********************************************************************
 *
 */

#include "cpl_vsi.h"
#include "cpl_mfile.h"

CPL_CVSID("$Id: cpl_mfile.cpp,v 1.1 2003/08/19 21:32:24 jan Exp $");

static void MFILEInit( MFILE * mfp );
static int MFILE_NewChunk( MFILE * mfp );

/************************************************************************
 *                                                                      *
 * The MFILE routines hook into the VSI file I/O functions and provide  *
 * an interface to a memory based file. A linked list to hold the file  *
 * data. Embedded in the filename, created with the MFILENAME macro,    *
 * is the address of a MFILEReceiver which will be filled in when the   *
 * file is closed, and will contain the address of a contiguous region  *
 * of memory that has the file data.                                    *
 *                                                                      *
 ************************************************************************/


/************************************************************************/
/*                              MFILEInit()                             */
/************************************************************************/

static void MFILEInit( MFILE * mfp )
{
    mfp->sig[ 0 ]       = 'M';
    mfp->sig[ 1 ]       = 'F';
    mfp->sig[ 2 ]       = 'L';
    mfp->head           = NULL;
    mfp->cur            = NULL;
    mfp->t_offset       = 0;
    mfp->length         = 0;
    mfp->eof            = 0;
    mfp->ungetc         = -1;
    mfp->receiver       = NULL;
}

/************************************************************************/
/*                              MFILEOpen()                             */
/************************************************************************/

MFILE * MFILEOpen( const char * pszFilename )
{
    if ( strncmp( pszFilename, "\3\1\4MFILE", 8 ) != 0 
            || strlen(pszFilename) != ( 8 + 2*sizeof( void * ) ) )
    {
        errno = ENOENT;
        return NULL;
    }

    MFILE *mfile = ( MFILE * )VSIMalloc( sizeof( MFILE ) );
    if ( mfile == NULL )
    {
        return NULL;
    }
    else
    {
        MFILEInit(mfile);
        sscanf(pszFilename, "\3\1\4MFILE%x", &mfile->receiver);
        return mfile;
    }
}

/************************************************************************/
/*                              MFILETell()                             */
/************************************************************************/

vsi_l_offset MFILETell( MFILE * mfp )

{
    return mfp->t_offset;
}

/************************************************************************/
/*                             MFILERewind()                            */
/************************************************************************/

void MFILERewind( MFILE * mfp )

{
    mfp->cur = mfp->head;
    mfp->t_offset = 0;
    mfp->eof = 0;
    mfp->ungetc = -1;
}

/************************************************************************/
/*                              MFILEFlush()                            */
/************************************************************************/

void MFILEFlush( MFILE * mfp )
{
}

/************************************************************************/
/*                              MFILEGets()                             */
/************************************************************************/

char *MFILEGets( char *pszBuffer, int nBufferSize, MFILE * mfp )
{
    int count = 0;

    if ( nBufferSize > 0 )
    {
        while ( count < nBufferSize - 1 )
        {
            int c = MFILEGetc( mfp );
    
            if ( c != EOF )
            {
                pszBuffer[ count ] = ( unsigned char )c;
    
                if ( c == '\n' )
                    break;
            }
            else
            {
                if ( count == 0 )
                    return NULL;

                break;
            }
    
            count++;
        }

        pszBuffer[ count ] = '\0';

        return pszBuffer;
    }

    return NULL;
}

/************************************************************************/
/*                              MFILEGetc()                            */
/************************************************************************/

int MFILEGetc( MFILE * mfp )
{
    char v;
    MFILERead( &v, sizeof( v ), 1, mfp );

    return ( int )v;
}

/************************************************************************/
/*                              MFILEUngetc()                           */
/************************************************************************/

int MFILEUngetc( int c, MFILE * mfp )
{
    if ( mfp->ungetc >= 0 || c < 0 )
    {
        return EOF;
    }

    mfp->ungetc = ( unsigned char )c;

    return mfp->ungetc;
}

/************************************************************************/
/*                              MFILEEof()                            */
/************************************************************************/

int MFILEEof( MFILE * mfp )

{
    return mfp->eof;
}

/************************************************************************/
/*                              MFILERead()                             */
/************************************************************************/

size_t MFILERead( void * pBuffer, size_t nSize, size_t nCount, MFILE * mfp )

{
    char *dst = ( char * )pBuffer;
    size_t count = nCount;
    size_t size = nSize;

    if ( mfp->cur == NULL )
    {
        mfp->eof = 1;
        goto err;
    }

    if ( mfp->ungetc >= 0 )
    {
        dst += 1;
        mfp->t_offset++;
        mfp->ungetc = -1;
        size--;
    }

    while ( count > 0 )
    {
        while ( size > 0 )
        {
            void * src;
            size_t n;
            
            vsi_l_offset x, y, chunk_offset;

            chunk_offset = mfp->t_offset - mfp->cur->offset;

            x = CHUNK_SIZE - chunk_offset;
            y = mfp->length - mfp->t_offset;

            n = MIN(size, MIN(x, y));

            if ( n <= 0 ) /* could be < 0 if there was an ungetc char */
            {
                /* if there's nothing to read, we may be at the end */
                if ( mfp->cur->next == NULL )
                {
                    mfp->eof = 1;
                    goto err;
                }
                else
                {
                    mfp->cur = mfp->cur->next;
                    continue;
                }
            }

            src = mfp->cur->data + chunk_offset;
            memcpy(dst, src, n);
            dst += n;
            size -= n;

            mfp->t_offset += n;
        }

        count--;

        size = nSize;
    }

err:
    return nCount - count;
}

/************************************************************************/
/*                           MFILE_NewChunk()                           */
/************************************************************************/

static int MFILE_NewChunk( MFILE * mfp )
{
    /*
     * We may not allocate any new chunks. This function tries to make
     * sure that the cur pointer is valid for a successive write. If
     * the pointer is NULL, more memory is allocated, if cur->next is
     * valid, it becomes the new cur.
     */

    if ( mfp->head == NULL )
    {
        mfp->head = ( struct s_chunk * )VSIMalloc( sizeof( struct s_chunk ) );
        mfp->cur = mfp->head;

        if ( mfp->cur != NULL )
        {
            mfp->cur->next = NULL;
            mfp->cur->offset = 0;

            mfp->length = 0;
            mfp->t_offset = 0;

            memset( mfp->cur->data, 0, CHUNK_SIZE );
        }

    }
    else if ( mfp->cur->next == NULL )
    {
        struct s_chunk *prev = mfp->cur;

        prev->next = ( struct s_chunk * )VSIMalloc( sizeof( struct s_chunk ) );
        mfp->cur = prev->next;

        if ( mfp->cur != NULL )
        {
            mfp->cur->next = NULL;
            mfp->cur->offset = prev->offset + CHUNK_SIZE;

            memset( mfp->cur->data, 0, CHUNK_SIZE );
        }
    }
    else if ( mfp->cur->next != NULL )
    {
        mfp->cur = mfp->cur->next;
        mfp->t_offset = mfp->cur->offset;
    }


    return mfp->cur == NULL;
}

/************************************************************************/
/*                             MFILEWrite()                             */
/************************************************************************/

size_t MFILEWrite( void * pBuffer, size_t nSize, size_t nCount, MFILE * mfp )

{
    size_t count = nCount;
    char *src = ( char * )pBuffer;

    while ( count > 0 )
    {
        size_t size = nSize;
        while ( size > 0 )
        {
            void * dst;
            size_t n;

            vsi_l_offset x, chunk_offset;

            if ( mfp->cur == NULL /* only NULL if head is NULL */
                || ( mfp->t_offset - mfp->cur->offset ) >= CHUNK_SIZE )
            {
                if ( MFILE_NewChunk(mfp) != 0 ) 
                {
                    goto err;
                }
            }

            chunk_offset = mfp->t_offset - mfp->cur->offset;

            x = CHUNK_SIZE - chunk_offset;
            n = MIN(size, x);

            dst = &mfp->cur->data[ chunk_offset ];
            memcpy(dst, src, n);
            src += n;
            size -= n;

            /* 
             * only adjust the length if we write data past the end
             * of the current length.
             */
            if ( ( mfp->length - mfp->t_offset ) < n )
            {
                mfp->length += ( mfp->t_offset + n ) - mfp->length;
            }

            mfp->t_offset += n;
        }

        count--;
    }

err:
    return nCount - count;
}

/************************************************************************/
/*                             MFILESeek()                              */
/************************************************************************/

int MFILESeek( MFILE * mfp, vsi_l_offset nOffset, int nWhence )
{
    int ret = -1;

    switch ( nWhence )
    {
        case SEEK_CUR:
        case SEEK_END:

            /*
             * guard against overflow
             */
            if ( nOffset != 0 )
            {
                if ( mfp->t_offset + nOffset <= mfp->t_offset )
                {
                    errno = ERANGE;
                    break;
                }
            }

            nOffset = mfp->t_offset + nOffset;

            /* fall through */

        case SEEK_SET:
            if ( nOffset >= 0 )
            {
                ret = 0;
            }
            break;
        default:
            errno = EINVAL;
            break;
    }

    if ( ret == 0 ) 
    {
        struct s_chunk *cur = NULL;

        if ( nOffset > mfp->length )
        {
            /*
             * An offset past the end of the file pads the file with 0's.
             */ 

            struct s_chunk *prev = NULL;

            int n = ( ( nOffset - mfp->length ) / CHUNK_SIZE ) + 
                    ( ( nOffset - mfp->length ) & ( CHUNK_SIZE - 1 ) != 0);

            for (cur = mfp->cur; cur != NULL; cur = cur->next )
                prev = cur;

            if ( prev != NULL || mfp->head == NULL ) 
            {
                mfp->cur = prev;

                while ( n-- > 0 && !( ret = MFILE_NewChunk( mfp ) ) );

                if ( ret == 0 )
                {
                    mfp->t_offset = nOffset;
                    mfp->length = nOffset;
                }
            }
        }
        else 
        {
            if ( nOffset < mfp->t_offset )
            {
                cur = mfp->head;
            }
            else if ( nOffset > mfp->t_offset )
            {
                cur = mfp->cur;
            }
            /* if nOffset == mfp->t_offset, cur == NULL so nothing is done */

            for ( ; cur != NULL; cur = cur->next )
            {
                if ( nOffset < cur->offset + CHUNK_SIZE )
                {
                    mfp->cur = cur;
                    mfp->t_offset = nOffset;
                    break;
                }
            }
        }

        mfp->eof = 0;
        mfp->ungetc = -1;
    }

    return ( ret ) ? -1 : 0;
}

/************************************************************************/
/*                            MFILEClose()                              */
/************************************************************************/

int MFILEClose( MFILE * mfp )
{
    if ( mfp != NULL )
    {
        struct s_chunk *cur, *tmp;
        char *data = NULL;

        if ( mfp->receiver != NULL ) 
        {
            data = ( char * )VSIMalloc( mfp->length );
        }

        /*
         * Free the linked list and collapse it into a contiguous array 
         */

        char *data_ptr = data;
        cur = mfp->head; 
        while ( cur != NULL )
        {
            if ( data_ptr != NULL )
            {
                int len = MIN(mfp->length - cur->offset, CHUNK_SIZE);
                memcpy( data_ptr, cur->data, len );
                data_ptr += len;
            }

            tmp = cur->next;
            VSIFree( cur );
            cur = tmp;
        }
    
        mfp->receiver->data = ( void * )data;
        if ( data != NULL )
        {
            mfp->receiver->len  = mfp->length;
        }
        else
        {
            mfp->receiver->len  = 0;
        }

        VSIFree( mfp );
        mfp = NULL;
    }

    return mfp == NULL;
}

