/*
 * NAME
 *   VarBuf.c
 * DESCRIPTION
 *   VarBuf is An Abstract Data Type which provides variable size memory 
 *   buffers. A VarBuf grows and shrinks in size automatically. The caller does
 *   not have to take care of memory allocation. Operations and queries are 
 *   available to use a VarBuf to store variable size character string.
 * COPYRIGHT
 *   VarBuf - Variable size buffer ADT.
 *   Copyright (C) 1995  Rene W.J. Pijlman
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * VERSION
 *   /home/rene/sys/CVS_MasterSourceRepository/skim/VarBuf.c,v 1.9 1995/08/14 23:37:57 rpijlman Exp
 *   Distributed with Skim version 0.6.
 */

#include "Types.h"
#include "VarBuf.h"

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>

#include "MemAlloc.h"

/* The private attributes of a VarBuf. */
struct _VarBuf {
    size_t Size;
    void * Address;
};


#define VB_EXPAND_SIZE_FOR_LINE   80


VarBuf VBCreate( void )
{
    VarBuf Result;

    Result = MemAlloc( sizeof( struct _VarBuf ) );

    Result->Address = NULL;
    Result->Size = 0;

    return Result;
}


void VBAppendString( VarBuf This, const char * String )
{
    size_t StringLength;
    size_t NewSize;

    assert( This != NULL && String != NULL );

    StringLength = strlen( String );
    NewSize = This->Size + StringLength;

    if ( NewSize > 0 )
    {
        This->Address = MemRealloc( This->Address, NewSize );
	memcpy( This->Address + This->Size, String, StringLength );
    }

    This->Size = NewSize;
}


void VBAppendCharacter( VarBuf This, char c )
{
    char String[2];

    assert( This != NULL );

    String[0] = c;
    String[1] = '\0';

    VBAppendString( This, String );
}


void VBAppendVB( VarBuf This, VarBuf Source )
{
    size_t NewSize;

    assert( This != NULL && Source != NULL );

    NewSize = This->Size + Source->Size;

    if ( NewSize > 0 )
    {
	This->Address = MemRealloc( This->Address, NewSize );
        memcpy( This->Address + This->Size, Source->Address, Source->Size );
    }

    This->Size = NewSize;
}


void VBDestroy( VarBuf This )
{
    if ( This != NULL )
    {
	MemFree( This->Address );
    }

    MemFree( This );
}


/* Append one line from File and append to the VarBuf. */
Boolean VBReadLine( VarBuf This, FILE * File, Boolean IncludeLinefeed )
{
    Boolean ReadSomeData = False;
    Boolean MoreData = True;
    size_t OldSize;

    assert( This != NULL && File != NULL );

    OldSize = This->Size;

    while ( MoreData )
    {
        This->Address = MemRealloc( This->Address,
                                    This->Size + VB_EXPAND_SIZE_FOR_LINE );

	if ( fgets( (char *)This->Address + This->Size,
	            VB_EXPAND_SIZE_FOR_LINE, File ) != NULL )
	{
	    ReadSomeData = True;
	    This->Size += strlen( (char *)(This->Address + This->Size) );
	    MoreData = ((char *)This->Address)[ This->Size - 1] != '\n' &&
	               !feof( File );
	}
	else
	{
	    if ( feof( File ) )
	    {
	        MoreData = False;
	    }
	    else
	    {
	        perror( "fgets" );
	        exit( EXIT_FAILURE );
	    }
	}
    }

    if( ReadSomeData )
    {
	if ( !IncludeLinefeed && 
	     ((char *)This->Address)[ This->Size - 1] == '\n' )
	{
	    This->Address = MemRealloc( This->Address, This->Size - 1 );
	    This->Size--;
	}
    }
    else
    {
        /* Back to original size. */
        This->Address = MemRealloc( This->Address, OldSize );
    }

    return ReadSomeData;
}


void * VBAddress( VarBuf This )
{
    assert( This );

    return This->Address;
}

size_t VBSize( VarBuf This )
{
    assert( This != NULL );

    return This->Size;
}


void VBReset( VarBuf This )
{
    This->Address = MemRealloc( This->Address, 0 );
    This->Size = 0;
}


/*
 * The following subset of printf() conversion specifications is supported:
 *
 *     %s    :  char *; print the null terminated string. Precision is not 
 *              supported.
 *     %%    :  Append one '%'.
 * 
 * And the following extensions to the printf() conversion specifications are
 * supported:
 *     %V    :  VarBuf. Print the contents of the VarBuf.
 */

void VBPrintf( VarBuf This, const char * FormatString, ...)
{
    const char * p;
    va_list ArgumentPointer;

    va_start( ArgumentPointer, FormatString );

    for ( p = FormatString; *p != '\0'; p++ )
    {
        if ( *p == '%' )
        {
            switch( *++p )
            {
                case 's':
                    VBAppendString( This, va_arg( ArgumentPointer, char * ) );
                    break;

		case 'V':
                    VBAppendVB( This, va_arg( ArgumentPointer, VarBuf ) );
                    break;

		case '%':
                    VBAppendCharacter( This, '%' );
                    break;

		default:
                    fprintf( stderr, "Unknown conversion specification: %%%c\n",
                             *p );
		    exit( EXIT_FAILURE );
		    break;
            }
        }
        else
        {
            VBAppendCharacter( This, *p );
        }
    }

    va_end( ArgumenPointer );
}


char * VBAsString( VarBuf This )
{
    /* Make null-terminated, but the '\0' is not part of the VarBuf. */
    This->Address = MemRealloc( This->Address, This->Size + 1 );
    ((char *)This->Address)[This->Size] = '\0';

    return This->Address;
}


void VBShiftLeft( VarBuf This, size_t Distance )
{
    assert( This->Size >= Distance );

    memmove( This->Address, This->Address + Distance, This->Size - Distance );
    This->Address = MemRealloc( This->Address, This->Size - Distance );
    This->Size = This->Size - Distance;
}


void VBTruncate( VarBuf This, size_t NewSize )
{
    assert( NewSize <= This->Size );

    This->Address = MemRealloc( This->Address, NewSize );
    This->Size = NewSize;
}
