#define REF_VERS 0

/******************************************************************************

    MODULE
	DYN.c

    DESCRIPTION
	Simple Dynamic Strings in "C"

    NOTES
      * this file contains 2 implementations:
	one is using bigger base variables (12bytes) and is a little bit faster
	the other is using smaller base variables (4bytes) and is a little bit slower
	(speed is not tested ...)
      * to keep codesize small and for debugging purposes
	(only one function to modify) DynCpy & DynCat are
	using DynIns, so the functions DynCpy and DynCat are
	much slower than necessary.

    BUGS
	none known

    TODO
	tell me

    EXAMPLES

    SEE ALSO

    HISTORY
	15-09-94 b_noll created
	28-09-94 b_noll enhanced !REF_VERS (REF_VERS is going away)

******************************************************************************/


/**************************************
	      Includes
**************************************/

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

#ifdef AMIGA
#include <clib/exec_protos.h>
extern APTR SysBase;
#include <pragmas/exec_pragmas.h>
#endif

//#undef DYNSTR_MACROS
#ifndef   DYN_H
#include "DYN.h"
#endif /* DYN_H */

/**************************************
      Internal Defines & Structures
**************************************/

#ifndef STREAM
#define STREAM void *
#endif

#undef	Prototype
#define Prototype extern

#define STRLEN	    32	   /* each dynamic string is sized ((STRLEN+strlen()-1)/STRLEN)*STRLEN ... */

#ifndef TRUE
#define TRUE  1
#define FALSE 0
#define BOOL  int
#endif

#define BRA_ do{
#define KET_ }while(0)

/**************************************
	     Implementation
**************************************/

#if REF_VERS /* ******************************************* */

struct _DSTR {
    int   Size;
    int   Length;
    char *Str;
}; /* struct _DSTR */
#define DSTR struct _DSTR

DSTR _EmptyDyn = {0,0,NULL};

void DynInit (DSTR *pstr) {
    *pstr = _EmptyDyn;
} /* DynInit */

void DynClear (DSTR *pstr) {
    if (DynValue(pstr))
	free (DynValue(pstr));
    DynInit(pstr);
} /* DynClear */

void DynReset (DSTR *pstr) {
    if (DynValue (pstr))
	pstr->Length = strlen(DynValue(pstr));
    else
	pstr->Length = pstr->Size = 0;
} /* DynReset */

void DynErase (DSTR *pstr) {
    if (DynValue (pstr))
	*DynValue(pstr) = 0;
    DynReset(pstr);
} /* DynErase */

char *DynValue (DSTR *pstr) {
    return pstr->Str;
} /* DynValue */

int DynSize (DSTR *pstr) {
    return pstr->Size;
} /* DynSize */

int DynLen (DSTR *pstr) {
    return pstr->Length;
} /* DynLen */

BOOL  DynCpy	(DSTR *pstr, const char *src) {
    DynClear(pstr);
    DynCat  (pstr, src);
} /* DynCpy */

BOOL  DynCat	(DSTR *pstr, const char *src) {
    return DynIns (pstr, src, pstr->Length);
} /* DynCat */

BOOL  DynDel	(DSTR *pstr, int pos, int chars) {
    /* ---- Empty? or nothing to Delete */
    if (!pstr->Str || (pstr->Length < pos) || !chars)
	return TRUE;

    /* ---- End Truncated? */
    if (pstr->Length < pos + chars) {
	pstr->Length = pos;
	pstr->Str[pos] = 0;
	return TRUE;
    } /* if */

    /* ---- Midth Deleted? */
    memcpy (pstr->Str + pos, pstr->Str + pos + chars, pstr->Length + 1 - (chars + pos));
    pstr->Length -= chars;
    return TRUE;
} /* DynDel */

BOOL  DynIns	(DSTR *pstr, const char *src, int pos) {
    int   slen, tlen;
    char *inter;
    assert (pstr != NULL);

    /* ---- make sure we have consistent data */
    if (!pstr->Str)
	pstr->Length = pstr->Size = 0;

    /* ---- Empty append? */
    if (!src)
	return TRUE;
    slen = strlen (src);
    if (!slen)
	return TRUE;

    /* ---- Fits into old String? */
    if (slen + pstr->Length < pstr->Size) {
	strins (pstr->Str + pos, src); /* DIFF1_2_Cat */
	pstr->Length += len;
	return TRUE;
    } /* if */

    /* ---- New Mem needed */
    if (!(inter = malloc (tlen = (STRLEN * (1 + pstr->Length + slen + STRLEN - 1)/STRLEN ))))
	return FALSE;

    strcpy (inter, pstr->Str);
    free   (pstr->Str);
    strins (inter + pos, src); /* DIFF2_2_Cat */

    pstr->Length += slen;
    pstr->Size	= tlen;
    pstr->Str  = inter;
    return TRUE;
} /* DynIns */


#endif /* ************************************************** */

/*
#undef DynInit
#undef DynClear
#undef DynDelete
#undef DynReset
#undef DynErase
#undef DynLen
#undef DynSize
#undef DynValue
*/

#define DB(pstr) (*pstr)


/* ---- Return the contents of a DSTR */
#ifndef DynValue
Prototype char *DynValue (DSTR *pstr);
char *DynValue (DSTR *pstr) {
    assert (pstr != NULL);
    if (DB(pstr))
	return DB(pstr)->Str;
    return "";
} /* DynValue */
#define DynValue(pstr)  ((*(pstr))? ((*(pstr))->Str): "")
#endif


/* ---- Return the sizeof() of a DSTR */
#ifndef DynSize
Prototype int DynSize (DSTR *pstr);
int DynSize (DSTR *pstr) {
    assert (pstr != NULL);
    if (DB(pstr))
	return DB(pstr)->Size;
    return 0;
} /* DynSize */
#define DynSize(pstr)   ((*(pstr))? ((*(pstr))->Size): 0)
#endif


/* ---- Return the strlen() of a DSTR */
#ifndef DynLen
Prototype int DynLen (DSTR *pstr);
int DynLen (DSTR *pstr) {
    assert (pstr != NULL);
    if (DB(pstr))
	return DB(pstr)->Length;
    return 0;
} /* DynLen */
#define DynLen(pstr)    ((*(pstr))? ((*(pstr))->Length): 0)
#endif


/* ---- Initialize a DSTR */
#ifndef DynInit
Prototype void DynInit (DSTR *pstr);
void DynInit (DSTR *pstr) {
    assert (pstr != NULL);
    *pstr = EmptyDyn;
} /* DynInit */
#define DynInit(pstr)   *(pstr) = EmptyDyn
#endif


/* ---- Reset a DSR to initialisation-state */
#ifndef DynClear
Prototype void DynClear (DSTR *pstr);
void DynClear (DSTR *pstr) {
    assert (pstr != NULL);
    if (DB(pstr))
	free (DB(pstr));
    DynInit(pstr);
} /* DynClear */
#define DynClear(pstr)  BRA_ DSTR d; if ((d = *(pstr))) free(d); *(pstr) = EmptyDyn; KET_
#endif


/* ---- Delete a DSTR - make in uninitlialized */
#ifndef DynDelete
Prototype void DynDelete (DSTR *pstr);
void DynDelete (DSTR *pstr) {
    assert (pstr != NULL);
    DynClear (pstr);
} /* DynDelete */
#define DynDelete(pstr) DynClear(pstr)
#endif


/* ---- Make sure a DSTR is consistent */
#ifndef DynReset
Prototype void DynReset (DSTR *pstr);
void DynReset (DSTR *pstr) {
    assert (pstr != NULL);
    if (DB(pstr))
	DB(pstr)->Length = strlen (DB(pstr)->Str);
} /* DynReset */
#define DynReset(pstr)  BRA_ DSTR d; if ((d = *(pstr))) d->Length = strlen(d->Str);     KET_
#endif


/* ---- Set the contents of a DSTR to "" and keep the DSTR consistent */
#ifndef DynErase
Prototype void DynErase (DSTR *pstr);
void DynErase (DSTR *pstr) {
    assert (pstr != NULL);
    if (DB(pstr))
	DB(pstr)->Length = DB(pstr)->Str[0] = 0;
} /* DynErase */
#define DynErase(pstr)  BRA_ DSTR d; if ((d = *(pstr))) d->Length = d->Str[0] = 0;      KET_
#endif


/* ---- Delete text inside a DSTR */
Prototype BOOL DynDel	 (DSTR *pstr, int pos, int chars);
BOOL DynDel    (DSTR *pstr, int pos, int chars) {
    struct _DSTR *dstr;

    assert (pstr != NULL);
    dstr = DB(pstr);
    /* ---- Empty? or nothing to Delete */
    if (!(dstr = DB(pstr)) || (dstr->Length < pos) || !chars)
	return TRUE;

    /* ---- End Truncated? */
    if (dstr->Length < pos + chars) {
	dstr->Length = pos;
	dstr->Str[pos] = 0;
	return TRUE;
    } /* if */

    /* ---- Midth Deleted? */
    memcpy (dstr->Str + pos, dstr->Str + pos + chars, dstr->Length + 1 - (chars + pos));
    dstr->Length -= chars;
    return TRUE;
} /* DynDel */


/* ---- Make sure a DSTR is at least big enough to keep a certain amount of memory */
Prototype BOOL	DynExpand (DSTR *pstr, int max);
BOOL  DynExpand (DSTR *pstr, int max) {
    DSTR new;
    assert (pstr != NULL);

    /* ---- no more heap needed? */
    if (DB(pstr) && (DynSize(pstr) > max))
	return TRUE;

    /* ---- make size block aligned */
    max = STRLEN * ((max + STRLEN - 1) / STRLEN);

    /* ---- New Mem needed */
    if (!(new = malloc (sizeof (*new) + max)))
	return FALSE;

    /* ---- init the new str */
    new->Size	 = max;
    new->Length    = 0;
    new->Str[0] = 0;

    /* ---- get the old string's data and release it */
    if (DB(pstr)) {
	new->Length = DB(pstr)->Length;
	strcpy (new->Str, DB(pstr)->Str);
	free   (DB(pstr));
    } /* if */
    *pstr = new;

    return TRUE;
} /* DynExpand */


/* ---- Insert text of a certain length into a DSTR */
static BOOL _DynLIns (DSTR *pstr, const char *src, int pos, int slen) {
    int   use, len;
    struct _DSTR *dstr;
    assert (pstr != NULL);

    /* ---- Empty append? */
    if (!src)
	return TRUE;

    /* ---- make sure we have at least partly consistent data */
    if (!(dstr = DB(pstr)))
	use = len = 0;
    else {
	use = dstr->Length;
	len = dstr->Size;
    } /* if */

    /* ---- what shall happen, if pos>use? */
    /* if (pos > use) return FALSE; */
    if (pos > use) pos = use;

    slen = strlen (src);
    /* ---- isn't it better o make sure *pstr!=NULL - remove the next line! */
    /* if (!slen) return TRUE; */

    /* ---- Fits into old String?      */
    /*	    New Mem needed and got it? */
    if ((slen + use >= len) && !DynExpand (pstr, use + slen))
	return FALSE;
    dstr = DB(pstr);

    /* strins (dstr->Str + pos, src); /* this is the only DIFF_2_Cat */
    memmove(dstr->Str + pos + slen, dstr->Str + pos, dstr->Length + 1 - pos);
    memmove(dstr->Str + pos, src, slen);
    dstr->Length  += slen;

    return TRUE;
} /* _DynLIns */


/* ---- Insert text into a DSTR */
Prototype BOOL	DynIns	  (DSTR *pstr, const char *src, int pos);
BOOL  DynIns	(DSTR *pstr, const char *src, int pos) {
    assert (pstr != NULL);
    return _DynLIns (pstr, src, pos, src? strlen (src): 0);
} /* DynIns */


/* ---- Append text to a DSTR */
Prototype BOOL DynCat	 (DSTR *pstr, const char *src);
BOOL DynCat    (DSTR *pstr, const char *src) {
    // return _DynLIns (pstr, src, DynLen(pstr), src? strlen (src): 0);
    int slen;
    assert (pstr != NULL);
    if (src && DynExpand(pstr, slen = strlen(src) + DynLen(pstr))) {
	strcat (DynValue(pstr) + DynLen(pstr), src);
	DB(pstr)->Length = slen;
	return TRUE;
    } /* if */
    return FALSE;
} /* DynCat */


/* ---- Replace the contents of a DSTR */
Prototype BOOL DynCpy	 (DSTR *pstr, const char *src);
BOOL DynCpy    (DSTR *pstr, const char *src) {
    assert (pstr != NULL);
    DynErase (pstr);
    return DynCat (pstr, src);
} /* DynCpy */


/* The DynD* functions are a little bit faster when copying data between
** 2 DSTRs that the way via DynValue and the normal Dyn* functions */


/* ---- Insert text into a DSTR from a DSTR */
Prototype BOOL	DynDIns    (DSTR *pstr, DSTR *src, int pos);
BOOL  DynDIns	 (DSTR *pstr, DSTR *src, int pos) {
    assert (pstr != NULL);
    return _DynLIns (pstr, DynValue(src), pos, DynLen(src));
} /* DynDIns */


/* ---- Append text to a DSTR from a DSTR */
Prototype BOOL DynDCat	  (DSTR *pstr, DSTR *src);
BOOL DynDCat	(DSTR *pstr, DSTR *src) {
    assert (pstr != NULL);
    return _DynLIns (pstr, DynValue(src), DynLen(pstr), DynLen(src));
} /* DynDCat */


/* ---- Replace the contents of a DSTR from a DSTR */
Prototype BOOL DynDCpy	  (DSTR *pstr, DSTR *src);
BOOL DynDCpy	(DSTR *pstr, DSTR *src) {
    assert (pstr != NULL);
    DynErase (pstr);
    return DynDCat (pstr, src);
} /* DynDCpy */


/* ---- _TRY_ to replace the value of a STR */
/*	the use of this function is recommended, if the  */
/*	old value of a DSTR shall stay valid in the case */
/*	there is not enough heap to hold the new value	 */
/*	the price for this security, is less speed	 */
Prototype BOOL DynSCpy (DSTR *pstr, const char *src);
BOOL DynSCpy (DSTR *pstr, const char *src) {
    int slen;
    assert (pstr != NULL);
    if (DynExpand(pstr, slen = strlen(src))) {
	strcpy (DynValue(pstr), src);
	DB(pstr)->Length = slen;
	return TRUE;
    } /* if */
    return FALSE;
} /* DynSCpy */


Prototype BOOL DynDSCpy (DSTR *pstr, DSTR *src);
BOOL DynDSCpy (DSTR *pstr, DSTR *src) {
    int slen;
    assert (pstr != NULL);
    if (DynExpand(pstr, slen = DynLen(src))) {
	strcpy (DynValue(pstr), DynValue(src));
	DB(pstr)->Length = slen;
	return TRUE;
    } /* if */
    return FALSE;
} /* DynDSCpy */


/* ---- Replace some chars within a DSTR */
Prototype BOOL DynReplace (DSTR *pstr, const char *src, int pos, int chars);
BOOL DynReplace (DSTR *pstr, const char *src, int pos, int chars) {
    int slen;
    slen = src? strlen(src): 0;
    assert (pstr != NULL);

    /* ---- we cannot write behind the end */
    if (pos > DynLen(pstr))
	return FALSE;

    /* ---- adjust the removed chars */
    if (pos + chars > DynLen(pstr))
	chars = DynLen(pstr) - pos;

    /* ---- expand the string */
    if (DynExpand(pstr, DynLen(pstr) + slen - chars)) {

	/* ---- make room for the replace part */
	if (slen != chars);
	    memmove(DynValue(pstr) + pos + slen, DynValue(pstr) + pos + chars, 1 + DynLen(pstr) - (pos + chars));

	/* ---- insert the replace part */
	memmove(DynValue(pstr) + pos, src, slen);
	DB(pstr)->Length += slen - chars;
	return TRUE;
    } /* if */
    return FALSE;
} /* DynReplace */


/* ---- read all incoming data from a stream and insert it into a DSTR */
Prototype BOOL DynGIns (DSTR *pstr, int pos, STREAM instream, char *(read)(STREAM));
BOOL DynGIns (DSTR *pstr, int pos, STREAM instream, char *(read)(STREAM)) {
    char *data;
    assert (pstr != NULL);

    /* ---- I do not think that function needs any explanation */
    while (data = (*read)(instream)) {
	int olen = DynLen(pstr);

	if (!DynIns(pstr, data, pos))
	    return FALSE;

	pos += DynLen(pstr) - olen;
    } /* while */

    return TRUE;
} /* DynGIns */


/* ---- Append several strings at one time ... */
Prototype BOOL vDynAppend (DSTR *pstr, int num, va_list adds);
BOOL vDynAppend (DSTR *pstr, int num, va_list adds) {
    int   il;
    char* istr;
    assert (pstr != NULL);

    /* ---- append all adds to pstr */
    while (num-- > 0) {
	istr = va_arg(adds, char*);

	/* ---- ignore empty strings */
	if (!istr)
	    continue;

	/* ---- make sure we have enough mem */
	il = strlen (istr);

	if (DynExpand(pstr, il + DynLen(pstr)))
	    return FALSE;

	/* ---- append the string */
	strcpy (DynValue(pstr) + DynLen(pstr), istr);
	DB(pstr)->Length += il;
    } /* while */

    return TRUE;
} /* vDynAppend */


/* ---- Append a single char to a DSTR		*/
/*	Not the fastest way, but it should work */
Prototype int DynPutc (DSTR *pstr, int c);
int DynPutc (DSTR *pstr, int c) {
    unsigned char bf[2] = { 0, '\0' };

    bf[0] = c & 255;
    return DynCat(pstr, bf);
} /* DynPutc */


#ifdef AMIGA

#if defined(__SASC)
#define PREFIX __asm
#define __D0(x) register __d0 x
#define __A3(x) register __a3 x
#elif defined(_DCC)
#define PREFIX
#define __D0(x)  __D0 x
#define __A3(x)  __A3 x
#endif

/* ---- Not the fastest way, but it should work */
static PREFIX int _DynPutc (__A3( DSTR *pstr ), __D0( int c )) {
    unsigned char bf[2] = { 0, '\0' };

    bf[0] = c & 255;
    return DynCat(pstr, bf);
} /* DynPutc */

/* ---- That function is _NOT_PORTABLE_! nor it can test, */
/*	if the operation ended successfully.		  */
/*	btw. remember - we have Flexprintf ;-)		  */
Prototype BOOL vDynPrintf (DSTR *pstr, const char *tplt, va_list args);
BOOL vDynPrintf (DSTR *pstr, const char *tplt, va_list args) {
    assert (pstr != NULL);
    RawDoFmt ((void *)tplt, args, (void *)_DynPutc, pstr);
    return TRUE;
} /* vDynPrintf */

#else

/* no amiga? poor guy... */
/* ---- Keep on Dreamin'  8-)  ...  btw. we have Flexprintf ;-) */
/*	Since there is no standardized way to access the format */
/*	function used by the *printf-funcs, and since I don't   */
/*	want to reinvent the wheel, so.else may write that func */
/* BOOL vDynPrintf (DSTR *pstr, const char *tplt, va_list args) {
} /* vDynPrintf */

#endif

/* ---- strip all ending spaces */
Prototype BOOL DynStripES (DSTR *pstr);
BOOL DynStripES (DSTR *pstr) {
    int   i;
    char *str;
    assert (pstr != NULL);

    /* ---- init */
    if (!(i = DynLen(pstr)))
	return FALSE;
    str = DynValue(pstr);
    --i;

    /* ---- remove all ending spaces */
    while ((i >= 0) && (str[i] < 33))
	str[i--] = 0;

    /* ---- restore the consistency */
    if (i < 0) {
	DynClear(pstr);
	return TRUE;
    } else if (DynLen(pstr) > i + 1) {
	DB(pstr)->Length = i + 1;
	return TRUE;
    } else
	return FALSE;
} /* DynStripES */

Prototype BOOL DynGetFile (DSTR *pstr, const char *name);
BOOL DynGetFile (DSTR *pstr, const char *name) {
    FILE *fi;
    if ((fi = fopen (name, "r"))) {
	int size;
	fseek(fi, 0, SEEK_END);
	if (!DynExpand(pstr, 1 + (size = ftell(fi)))) {
	    fclose (fi);
	    return FALSE;
	} /* if */
	fseek(fi, 0, SEEK_SET);

	fread (DynValue(pstr), 1, size, fi);
	DynValue(pstr)[size] = 0;

	fclose(fi);
	return TRUE;
    } /* if */
    return FALSE;
} /* DynGetFile */


/******************************************************************************
*****  END DYN.c
******************************************************************************/

