/* This file was contributed by Suzanne Skinner and is copyrighted
   under the GNU General Public License. (C) 2002 Suzanne Skinner.

   The code contained in this file 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, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/

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

#include "dynstr.h"

#define DS_BUFSIZE_INC 128

static int ds_init(dynstr *str);   /* private initializer */
static int ds_grow(dynstr s, size_t size); /* grow to size; private */

int ds_set(dynstr *str, const char *text)
{
    dynstr s;
    int    r;

    r = ds_init(str);
    if (r == -1) {
        return -1;
    }
    s = *str;
    r = ds_grow(s, strlen(text)+1);
    if (r == -1) {
        return -1;
    }
    strcpy(s->buf, text);
    return 0;
}

char *ds_get(dynstr *str)
{
    int r;

    r = ds_init(str);
    if (r == -1) {
        return NULL;
    }
    return (*str)->buf;
}

size_t ds_len(dynstr *str)
{
    int r;

    r = ds_init(str);
    if (r == -1) {
        return -1;
    }
    return strlen((*str)->buf);
}

void ds_free(dynstr *str)
{
    if (!(*str))
        return;
    free((*str)->buf);
    free(*str);
    *str = NULL;
}

int ds_append(dynstr *str, const char *text)
{
    return ds_insert(str, strlen((*str)->buf), text);
}

int ds_append_char(dynstr *str, char ch)
{
    return ds_insert_char(str, strlen((*str)->buf), ch);
}

int ds_insert(dynstr *str, int pos, const char *text)
{
    dynstr  s;
    int     text_len;
    int     r;

    r = ds_init(str);
    if (r == -1) {
        return -1;
    }
    s = *str;
    text_len = strlen(text);
    r = ds_grow(s, strlen(s->buf) + text_len + 1);
    if (r == -1) {
        return -1;
    }
    memmove(s->buf + pos + text_len, s->buf + pos, strlen(s->buf + pos) + 1);
    memcpy(s->buf + pos, text, text_len);
    return 0;
}

int ds_insert_char(dynstr *str, int pos, char ch)
{
    dynstr  s;
    int     len;
    int     r;
    
    r = ds_init(str);
    if (r == -1) {
        return -1;
    }
    s = *str;
    len = strlen(s->buf);
    r = ds_grow(s, len+2);
    if (r == -1) {
        return -1;
    }
    memmove(s->buf + pos + 1, s->buf + pos, strlen(s->buf + pos) + 1);
    s->buf[pos] = ch;
    return 0;
}

int ds_delete_substr(dynstr *str, int pos, int len)
{
    dynstr s;
    int    r;

    r = ds_init(str);
    if (r == -1) {
        return -1;
    }
    s = *str;
    memmove(s->buf + pos, s->buf + pos + len, strlen(s->buf + pos + len) + 1);
    return 0;
}

int ds_delete_char(dynstr *str, int pos)
{
    return ds_delete_substr(str, pos, 1);
}

int ds_truncate(dynstr *str, int new_len)
{
    dynstr s;
    int    r;

    r = ds_init(str);
    if (r == -1) {
        return -1;
    }
    s = *str;
    if (new_len < strlen(s->buf))
        s->buf[new_len] = '\0';
    return 0;
}

int ds_sprintf(dynstr *str, const char *format, ...)
{
    va_list  args;
    int      chars_printed;

    va_start(args, format);
    chars_printed = ds_vsprintf(str, format, args);
    va_end(args);
    return chars_printed;
}

/* sprintf starting at a particular point in the original string. This is
 * the dynstr equivalent of sprintf(str+pos, "<format>", <args>).
 */
int ds_sprintf_at(dynstr *str, int pos, const char *format, ...)
{
    va_list  args;
    int      chars_printed;

    va_start(args, format);
    chars_printed = ds_vsprintf_at(str, pos, format, args);
    va_end(args);
    return chars_printed;
}

int ds_vsprintf(dynstr *str, const char *format, va_list args)
{
    return ds_vsprintf_at(str, 0, format, args);
}

int ds_vsprintf_at(dynstr *str, int pos, const char *format, va_list args)
{
    dynstr  s;
    int     chars_printed;
    int     space;
    int     newsize;
    int     r;

    r = ds_init(str);
    if (r == -1) {
        return -1;
    }
    s = *str;
    while (1) {
        space = s->bufsize - pos;
        chars_printed = vsnprintf(s->buf + pos, space, format, args);
	if (chars_printed > -1 && chars_printed < space) {
  	    break;
	} 
	if (chars_printed == -1) {      /* glibc <= 2.0.6 */
	    newsize = s->bufsize + 1;
	} else {                        /* glibc >= 2.1 */
 	    /* chars_printed == num. chars that _would_ have been printed */
 	    newsize = pos + chars_printed + 1;
	}
	r = ds_grow(s, newsize);
	if (r == -1) {
	    return -1;
	}
    }
    return chars_printed;
}

/*** Private ***/

static int ds_init(dynstr *str)
{
    dynstr s;

    if (*str)
        return 0;
    *str = malloc(sizeof(struct dynstr_struct));
    s = *str;
    if (s == NULL) {
        return -1;
    }
    s->bufsize = DS_BUFSIZE_INC;
    s->buf = malloc(s->bufsize);
    if (s->buf == NULL) {
        free(s);
        return -1;
    }
    s->buf[0] = '\0';
    return 0;
}

/* change allocated size of str to at least size. Return 0 on success,
   or -1 on failure. Assume str has been initialized. If -1 is
   returned, str is unchanged. */
static int ds_grow(dynstr s, size_t size) 
{
    char *p;

    if (s->bufsize >= size) {
        return 0;
    }
    p = realloc(s->buf, size + DS_BUFSIZE_INC);
    if (p == NULL) {
        return -1;
    }
    s->buf = p;
    s->bufsize = size + DS_BUFSIZE_INC;
    return 0;
}
