/* pggbuffer.c - Dynamic memory buffers
 *      Copyright (C) 1999 Michael Roth <mroth@gnupg.org>
 *
 * This file is part of PGG (Privacy Guard Glue).
 *
 * PGG 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.
 *
 * PGG 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */


#include <includes.h>
#include <pgg.h>
#include <pggdebug.h>
#include <pggbuffer.h>


/*
 * Type casts
 */
#define buf		((PggBufferPtr)(_buf))
#define old_buf		((PggBufferPtr)(_old_buf))



#define INITIAL_CAPACITY	256


PggBuffer pgg_buffer_new(PggErrenv errenv)
{
    PggBufferPtr 	new_buf;
    
    PGG_CHECK_SKIP_ARG(NULL);
    
    if (!( new_buf = _malloc(PggBuffer) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    memset(new_buf, 0, _size(PggBuffer));
    
    new_buf->magic      = PggBufferMAGIC;
    new_buf->refcounter = 1;
    
    return _hd(PggBuffer, new_buf);
}


PggBuffer pgg_buffer_clone(PggBuffer _old_buf, PggErrenv errenv)
{
    PggBufferPtr		new_buf;
    
    PGG_STD_ASSERT_ARG(PggBuffer, old_buf, NULL);
    
    if (!( new_buf = _malloc(PggBuffer) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    new_buf->magic      = PggBufferMAGIC;
    new_buf->refcounter = 1;
    new_buf->wipeout    = old_buf->wipeout;
    new_buf->size       = old_buf->size;
    new_buf->maxsize    = old_buf->maxsize;
    new_buf->capacity   = old_buf->size;	/* Be conservative */
    
    if (old_buf->data) {
        if (!( new_buf->data = (char *)malloc(new_buf->capacity) )) {
            free(new_buf);
            PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
        }
        
        memcpy(new_buf->data, old_buf->data, new_buf->size);
    }
    else
        new_buf->data = NULL;
    
    return _hd(PggBuffer, new_buf);
}


void pgg_buffer_addref(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    buf->refcounter++;
}


void pgg_buffer_release(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    
    if (!(--buf->refcounter)) {
        if (buf->data) {
            if (buf->wipeout)
                memset(buf->data, 0, buf->size);
            
            free(buf->data);
        }
        
        free(buf);
    }
}


void pgg_buffer_reclaim(PggBuffer _buf, PggErrenv errenv)
{
    char *		data;
    
    PGG_STD_ASSERT(PggBuffer, buf);
    
    if (buf->size == 0) {
        free(buf->data);
        buf->data     = NULL;
        buf->capacity = 0;
    }
    
    if (buf->capacity > buf->size) {
        if (buf->wipeout) {
            if (!(data = (char *)malloc(buf->size) ))
                PGG_RETURN_ERR(RESOURCE, MEMORY);
            
            memcpy(data, buf->data, buf->size);
            memset(buf->data, 0, buf->size);
            
            free(buf->data);
        }
        else if (!( data = realloc(buf->data, buf->size) ))
            PGG_RETURN_ERR(RESOURCE, MEMORY);
        
        buf->capacity = buf->size;
        buf->data     = data;
    }
}


static int ensure_capacity(PggBufferPtr bufptr, long capacity)
{
    long		new_capacity;
    char *		new_data;
    
    if (bufptr->maxsize && bufptr->maxsize < capacity)
        return -1;
    
    if (bufptr->capacity < capacity) {
        new_capacity = INITIAL_CAPACITY;
        while (new_capacity < capacity)
            new_capacity *= 2;
        
        if (bufptr->maxsize && new_capacity > bufptr->maxsize)
            new_capacity = bufptr->maxsize;
        
        if (bufptr->wipeout) {
            if (!( new_data = (char *)malloc(new_capacity) )) {
                if (!( new_data = (char *)malloc(capacity) ))	/* Try to handle short memory situations */
                    return -1;
                else
                    new_capacity = capacity;
            }
            
            memcpy(new_data, bufptr->data, bufptr->size);
            memset(bufptr->data, 0, bufptr->size);
            
            free(bufptr->data);
        }
        else if (!( new_data = (char *)realloc(bufptr->data, new_capacity) ))
            return -1;
        
        bufptr->capacity = new_capacity;
        bufptr->data     = new_data;
    }
    
    return 0;
}


void pgg_buffer_append(PggBuffer _buf, const void * data, long amount, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    PGG_ASSERT(amount >= 0, ARGUMENT, VALUE);
    
    if (amount > 0) {
        PGG_ASSERT(data, ARGUMENT, NULLPTR);
        
        if (ensure_capacity(buf, buf->size + amount))
            PGG_RETURN_ERR(RESOURCE, MEMORY);
        
        memcpy(buf->data + buf->size, data, amount);
        buf->size += amount;
    }
}


void pgg_buffer_insert(PggBuffer _buf, long offset, const void * data, long amount, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    
    PGG_ASSERT(offset >= 0 && offset <= buf->size, ARGUMENT, RANGE);
    PGG_ASSERT(amount >= 0, ARGUMENT, VALUE);
    
    if (amount > 0) {
        PGG_ASSERT(data, ARGUMENT, NULLPTR);
        
        if (ensure_capacity(buf, buf->size + amount))
            PGG_RETURN_ERR(RESOURCE, MEMORY);
        
        if (buf->size > 0 && offset < buf->size - 1)
            memmove(buf->data + offset + amount, buf->data + offset, buf->size - offset);
        
        memcpy(buf->data + offset, data, amount);
        buf->size += amount;
    }
}


void pgg_buffer_delete(PggBuffer _buf, long offset, long amount, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    PGG_ASSERT(amount >= 0, ARGUMENT, VALUE);
    PGG_ASSERT(offset >= 0 && offset < buf->size, ARGUMENT, RANGE);
    PGG_ASSERT(offset + amount < buf->size, ARGUMENT, RANGE);
    
    if (amount > 0) {
        if (offset < buf->size-1 && offset + amount < buf->size-1)
            memmove(buf->data + offset, buf->data + offset + amount, buf->size - offset - amount);
        
        if (buf->wipeout)
            memset(buf->data + buf->size - amount, 0, amount);
        
        buf->size -= amount;
    }
}


void pgg_buffer_clear(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    
    if (buf->wipeout)
        memset(buf->data, 0, buf->size);
    
    buf->size = 0; /* Note: We don't free unused memory. The user could call pgg_buffer_reclaim() if he wish */
}


void pgg_buffer_set_wipeout(PggBuffer _buf, int yesno, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    buf->wipeout = yesno ? 1 : 0;
}


void pgg_buffer_set_maxsize(PggBuffer _buf, long maxsize, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    PGG_ASSERT(maxsize >= 0, ARGUMENT, VALUE);
    
    buf->maxsize = maxsize;
    
    if (buf->size > buf->maxsize) {	/* Truncate, if necessary */
        if (buf->wipeout)
            memset(buf->data + buf->maxsize, 0, buf->size - buf->maxsize);
        
        buf->size = buf->maxsize;
    }
}


void pgg_buffer_set_size(PggBuffer _buf, long size, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    PGG_ASSERT(size >= 0, ARGUMENT, VALUE);
    
    if (buf->maxsize && size > buf->maxsize)		/* FIXME: If we should enlarge maxsize? IMHO no */
        PGG_RETURN_ERR(ARGUMENT, RANGE);
    
    if (size < buf->size) {
        if (buf->wipeout)
            memset(buf->data + size, 0, buf->size - size);
        
        buf->size = size;
    }
    else if (size > buf->size) {
        if (ensure_capacity(buf, size))
            PGG_RETURN_ERR(RESOURCE, MEMORY);
        
        buf->size = size;
    }
}

/*
 * This is different from ensure_capacity() in the case that if more
 * memory is needed then present, exatly the given amount of memory
 * is allocated. Furthermore, this function doesn't contain an 
 * integrated maxsize check.
 */
void pgg_buffer_ensure_capacity(PggBuffer _buf, long capacity, PggErrenv errenv)
{
    char *		data;
    
    PGG_STD_ASSERT(PggBuffer, buf);
    PGG_ASSERT(capacity >= 0, ARGUMENT, VALUE);
    
    if (capacity > buf->capacity) {
        if (buf->wipeout) {
            if (!( data = (char *)malloc(capacity) ))
                PGG_RETURN_ERR(RESOURCE, MEMORY);
            
            memcpy(data, buf->data, buf->size);
            memset(buf->data, 0, buf->size);
            
            free(buf->data);
        }
        else if (!( data = (char *)realloc(buf->data, capacity) ))
            PGG_RETURN_ERR(RESOURCE, MEMORY);
        
        buf->capacity = capacity;
        buf->data     = data;
    }
}


long pgg_buffer_get_size(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggBuffer, buf, -1);
    return buf->size;
}


long pgg_buffer_get_maxsize(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggBuffer, buf, -1);
    return buf->maxsize;
}


long pgg_buffer_get_capacity(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggBuffer, buf, -1);
    return buf->capacity;
}


void * pgg_buffer_get_data(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggBuffer, buf, NULL);
    return buf->data;
}


void pgg_buffer_detach_data(PggBuffer _buf, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    
    buf->data     = NULL;
    buf->size     = 0;
    buf->capacity = 0;
}


void pgg_buffer_atach_data(PggBuffer _buf, void * data, long size, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggBuffer, buf);
    
    PGG_ASSERT(data, ARGUMENT, NULLPTR);
    PGG_ASSERT(size > 0, ARGUMENT, VALUE);
    
    if (buf->data) {
        if (buf->wipeout)
            memset(buf->data, 0, buf->size);
        
        free(buf->data);
    }
    
    buf->data     = data;
    buf->size     = size;
    buf->capacity = size;
    
    if (buf->maxsize && size > buf->maxsize)	/* FIXME: If it is correct, to adjust maxsize? */
        buf->maxsize = size;
}



