/***************************************************************/
/*                                                             */
/*  DYNBUF.C                                                   */
/*                                                             */
/*  Implementation of functions for manipulating dynamic       */
/*  buffers.                                                   */
/*                                                             */
/*  This file is part of REMIND.                               */
/*  Copyright (C) 1992-2026 by Dianne Skoll                    */
/*  SPDX-License-Identifier: GPL-2.0-only                      */
/*                                                             */
/***************************************************************/

#include "config.h"
#include "dynbuf.h"
#include "err.h"
#include <string.h>
#include <stdlib.h>

static size_t NumMallocs = 0;
static size_t BytesMalloced = 0;

void DBufGetMallocStats(size_t *num_mallocs, size_t *bytes_malloced)
{
    *num_mallocs = NumMallocs;
    *bytes_malloced = BytesMalloced;
}

/**********************************************************************
%FUNCTION: DBufMakeRoom
%ARGUMENTS:
 dbuf -- pointer to a dynamic buffer
 n -- size to expand to 
%RETURNS:
 OK if all went well; E_NO_MEM if out of memory
%DESCRIPTION:
 Doubles the size of dynamic buffer until it has room for at least
 'n' characters, not including trailing '\0'
**********************************************************************/
static int DBufMakeRoom(DynamicBuffer *dbuf, size_t n)
{
    /* Double size until it's greater than n (strictly > to leave room
       for trailing '\0' */
    size_t size = dbuf->allocatedLen;
    char *buf;

    if (size > n) return OK;

    while (size <= n) {
        size *= 2;
    }

    /* Allocate memory */
    buf = malloc(size);
    if (!buf) return E_NO_MEM;

    NumMallocs++;
    BytesMalloced += size;

    /* Copy contents */
    strcpy(buf, dbuf->buffer);

    /* Free contents if necessary */
    if (dbuf->buffer != dbuf->staticBuf) free(dbuf->buffer);
    dbuf->buffer = buf;
    dbuf->allocatedLen = size;
    return OK;
}

/**********************************************************************
%FUNCTION: DBufInit
%ARGUMENTS:
 dbuf -- pointer to a dynamic buffer
%RETURNS:
 Nothing
%DESCRIPTION:
 Initializes a dynamic buffer
**********************************************************************/
void DBufInit(DynamicBuffer *dbuf)
{
    dbuf->buffer = dbuf->staticBuf;
    dbuf->len = 0;
    dbuf->allocatedLen = DBUF_STATIC_SIZE;
    dbuf->buffer[0] = 0;
}

/**********************************************************************
%FUNCTION: DBufPutcFN
%ARGUMENTS:
 dbuf -- pointer to a dynamic buffer
 c -- character to append to buffer
%RETURNS:
 OK if all went well; E_NO_MEM if out of memory
%DESCRIPTION:
 Appends a character to the buffer.
**********************************************************************/
int DBufPutcFN(DynamicBuffer *dbuf, char c)
{
    if (dbuf->allocatedLen == dbuf->len+1) {
        if (DBufMakeRoom(dbuf, dbuf->len+1) != OK) return E_NO_MEM;
    }
    dbuf->buffer[dbuf->len++] = c;
    dbuf->buffer[dbuf->len] = 0;
    return OK;
}

/**********************************************************************
%FUNCTION: DBufPuts
%ARGUMENTS:
 dbuf -- pointer to a dynamic buffer
 str -- string to append to buffer
%RETURNS:
 OK if all went well; E_NO_MEM if out of memory
%DESCRIPTION:
 Appends a string to the buffer.
**********************************************************************/
int DBufPuts(DynamicBuffer *dbuf, char const *str)
{
    int l = strlen(str);
    if (!l) return OK;

    if (DBufMakeRoom(dbuf, dbuf->len+l) != OK) return E_NO_MEM;
    strcpy((dbuf->buffer+dbuf->len), str);
    dbuf->len += l;
    return OK;
}

/**********************************************************************
%FUNCTION: DBufFree
%ARGUMENTS:
 dbuf -- pointer to a dynamic buffer
%RETURNS:
 Nothing
%DESCRIPTION:
 Frees and reinitializes a dynamic buffer
**********************************************************************/
void DBufFree(DynamicBuffer *dbuf)
{
    if (dbuf->buffer != NULL && dbuf->buffer != dbuf->staticBuf) {
        free(dbuf->buffer);
    }
    DBufInit(dbuf);
}

/**********************************************************************
%FUNCTION: DBufGets
%ARGUMENTS:
 dbuf -- pointer to a dynamic buffer
 fp -- file to read from
 maxlen - Maximam lenght of resulting buffer. 0 = no limit
%RETURNS:
 OK or E_NO_MEM
%DESCRIPTION:
 Reads an entire line from a file and appends to dbuf.  Does not include
 trailing newline.
**********************************************************************/
int DBufGets(DynamicBuffer *dbuf, FILE *fp, int maxlen)
{
    char tmp[256]; /* Safe to hard-code */
    int busy = 1;
    int l;

    DBufFree(dbuf);

    /* Try reading the first few bytes right into the buffer --
       we can usually save some unnecessary copying */

    if (fgets(dbuf->buffer, dbuf->allocatedLen, fp) == NULL) {
        return OK;
    }
    if (!*(dbuf->buffer)) return OK;
    dbuf->len = strlen(dbuf->buffer);
    l = dbuf->len - 1;
    if (dbuf->buffer[l] == '\n') {
        dbuf->buffer[l] = 0;
        dbuf->len = l;
        if (maxlen > 0 && dbuf->len > (size_t) maxlen) {
            return E_LINE_TOO_LONG;
        }
        return OK;
    }

    while(busy) {
        *tmp = 0;
        if (fgets(tmp, 256, fp) == NULL) return OK;
        if (!*tmp) return OK;
        l = strlen(tmp) - 1;
        if (tmp[l] == '\n') {
            tmp[l] = 0;
            busy = 0;
        }
        if (DBufPuts(dbuf, tmp) != OK) return E_NO_MEM;
        if (maxlen > 0 && dbuf->len > (size_t) maxlen) {
            return E_LINE_TOO_LONG;
        }
    }
    return OK;
}
