/* strbuf.c - The string buffer data-structure.
 *
 * Copyright (C) 2004 Oskar Liljeblad
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

/* Depends on
 * gl_MODULES([strnlen vasprintf xalloc minmax])
 * Optionally
 * gl_MODULES([stdint])
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdint.h>		/* Gnulib/C99 */
#include <stdarg.h>		/* Gnulib/C89 */
#include <stdlib.h>		/* Gnulib/C89 */
#include <string.h>		/* Gnulib/C89 */
#include <stdio.h>		/* Gnulib/C89 */
#include "xalloc.h"             /* Gnulib */
#include "minmax.h"             /* Gnulib */
#include "strbuf.h"		/* common */

#define DEFAULT_STRBUF_CAPACITY 16

#define SWAP_SSIZE_T(a,b) { ssize_t _t = (a); (a) = (b); (b) = _t; }

static ssize_t
normalize_strbuf_pos(StrBuf *sb, ssize_t pos)
{
    if (pos >= (ssize_t) sb->len)
        return sb->len;
    if (pos >= 0)
        return pos;
    pos += 1 + sb->len;
    if (pos >= 0)
        return pos;
    return 0;
}

static ssize_t
normalize_str_pos(const char *str, ssize_t pos)
{
    if (str == NULL)
        return 0;
    if (pos >= 0)
        return strnlen(str, pos);
    pos += 1 + strlen(str);
    if (pos >= 0)
        return pos;
    return 0;
}

char *
strbuf_buffer(StrBuf *sb)
{
    return sb->buf;
}

size_t
strbuf_length(StrBuf *sb)
{
    return sb->len;
}

size_t
strbuf_capacity(StrBuf *sb)
{
    return sb->capacity;
}

StrBuf *
strbuf_new(void)
{
    return strbuf_new_with_capacity(DEFAULT_STRBUF_CAPACITY);
}

StrBuf *
strbuf_new_with_capacity(size_t capacity)
{
    StrBuf *sb;
    sb = xmalloc(sizeof(StrBuf));
    sb->len = 0;
    sb->capacity = capacity;
    sb->buf = xmalloc(sb->capacity * sizeof(char));
    if (sb->capacity > 0)
    	sb->buf[0] = '\0';
    return sb;
}

StrBuf *
strbuf_new_from_char_n(size_t times, char ch)
{
    return strbuf_new_from_substring_n(times, &ch, 0, 1);
}

StrBuf *
strbuf_new_from_substring_n(size_t times, const char *substr, ssize_t subsp, ssize_t subep)
{
    subsp = normalize_str_pos(substr, subsp);
    subep = normalize_str_pos(substr, subep);
    if (subsp > subep)
    	SWAP_SSIZE_T(subsp, subep);

    return strbuf_new_from_data_n(times, substr+subsp, subep-subsp);
}

StrBuf *
strbuf_new_from_data_n(size_t times, const void *mem, size_t len)
{
    StrBuf *sb;

    sb = strbuf_new_with_capacity(len * times + 1);
    sb->len = len * times;
    for (; times > 0; times--)
	memcpy(sb->buf + len*(times-1), mem, len);
    sb->buf[sb->len] = '\0';

    return sb;
}

StrBuf *
strbuf_newf_n(size_t times, const char *fmt, ...)
{
    va_list ap;
    StrBuf *buf;

    va_start(ap, fmt);
    buf = strbuf_vnewf_n(times, fmt, ap);
    va_end(ap);

    return buf;
}

StrBuf *
strbuf_vnewf_n(size_t times, const char *fmt, va_list ap)
{
    char *str;
    int len;
    StrBuf *buf;

    len = vasprintf(&str, fmt, ap);
    if (len < 0)
        xalloc_die();

    buf = strbuf_new_from_substring_n(times, str, 0, len);
    free(str);
    return buf;
}

void
strbuf_free(StrBuf *sb)
{
    if (sb != NULL) {
    	free(sb->buf);
	free(sb);
    }
}

char *
strbuf_free_to_substring(StrBuf *sb, ssize_t sp, ssize_t ep)
{
    char *buf;

    sp = normalize_strbuf_pos(sb, sp);
    ep = normalize_strbuf_pos(sb, ep);
    if (sp != 0)
	memmove(sb->buf, sb->buf+sp, ep-sp);
    sb->buf[ep-sp] = '\0';

    /* Call realloc so that unused memory can be used for other purpose. */
    if (sp == 0 && ep == (ssize_t) sb->len)
	buf = sb->buf;
    else
	buf = xrealloc(sb->buf, ep-sp+1);
    free(sb);
    return buf;
}

void
strbuf_replace_char_n(StrBuf *sb, ssize_t sp, ssize_t ep, size_t times, char ch)
{
    strbuf_replace_substring_n(sb, sp, ep, times, &ch, 0, 1);
}

void
strbuf_replace_data_n(StrBuf *sb, ssize_t sp, ssize_t ep, size_t times, const void *mem, size_t len)
{
    size_t addlen;
    size_t dellen;

    sp = normalize_strbuf_pos(sb, sp);
    ep = normalize_strbuf_pos(sb, ep);
    if (sp > ep)
    	SWAP_SSIZE_T(sp, ep);

    addlen = len * times;
    dellen = ep-sp;
    if (addlen != dellen) {
    	strbuf_ensure_capacity(sb, sb->len+1-dellen+addlen);
	memmove(sb->buf+sp+addlen, sb->buf+ep, sb->len+1-ep);
	sb->len += addlen-dellen;
    }
    if (addlen > 0) {
    	for (; times > 0; times--) {
    	    memcpy(sb->buf+sp, mem, len);
	    sp += len;
	}
    }
}

void
strbuf_replace_substring_n(StrBuf *sb, ssize_t sp, ssize_t ep, size_t times, const char *substr, ssize_t subsp, ssize_t subep)
{
    subsp = normalize_str_pos(substr, subsp);
    subep = normalize_str_pos(substr, subep);
    if (subsp > subep)
    	SWAP_SSIZE_T(subsp, subep);

    strbuf_replace_data_n(sb, sp, ep, times, substr+subsp, subep-subsp);
}

int
strbuf_replacef_n(StrBuf *sb, ssize_t sp, ssize_t ep, size_t times, const char *fmt, ...)
{
    va_list ap;
    int len;

    va_start(ap, fmt);
    len = strbuf_vreplacef_n(sb, sp, ep, times, fmt, ap);
    va_end(ap);

    return len;
}

int
strbuf_vreplacef_n(StrBuf *sb, ssize_t sp, ssize_t ep, size_t times, const char *fmt, va_list ap)
{
    char *str;
    int len;

    sp = normalize_strbuf_pos(sb, sp);
    ep = normalize_strbuf_pos(sb, ep);
    if (sp > ep)
    	SWAP_SSIZE_T(sp, ep);

    len = vasprintf(&str, fmt, ap);
    if (len < 0)
        xalloc_die();

    strbuf_replace_substring_n(sb, sp, ep, times, str, 0, len);
    free(str);
    return len;
}

void
strbuf_reverse_substring(StrBuf *sb, ssize_t sp, ssize_t ep)
{
    sp = normalize_strbuf_pos(sb, sp);
    ep = normalize_strbuf_pos(sb, ep);

    while (sp < ep) {
    	SWAP_SSIZE_T(sb->buf[sp], sb->buf[ep]);
    	sp++;
	ep--;
    }
}

void
strbuf_repeat_substring(StrBuf *sb, ssize_t sp, ssize_t ep, size_t times)
{
    ssize_t addlen;

    sp = normalize_strbuf_pos(sb, sp);
    ep = normalize_strbuf_pos(sb, ep);

    addlen = (ep-sp) * (times - 1);
    if (addlen != 0) {
    	size_t p;

    	strbuf_ensure_capacity(sb, sb->len+1+addlen);
	memmove(sb->buf+sp+addlen, sb->buf+ep, sb->len+1-ep);
	sb->len += addlen;

	p = ep;
    	for (; times > 0; times--) {
    	    memmove(sb->buf+p, sb->buf+sp, ep-sp);
	    p += ep-sp;
	}
    }
}

void
strbuf_set_length(StrBuf *sb, size_t new_length)
{
    strbuf_ensure_capacity(sb, new_length+1);
    sb->buf[new_length] = '\0';
    sb->len = new_length;
}

/* Note: The terminating null-byte counts as 1 in min_capacity */
void
strbuf_ensure_capacity(StrBuf *sb, size_t min_capacity)
{
    if (min_capacity > sb->capacity) {
	sb->capacity = MAX(min_capacity, sb->len*2+2); /* MAX -> max */
    	sb->buf = xrealloc(sb->buf, sb->capacity * sizeof(char));
	if (sb->len == 0)
    	    sb->buf[0] = '\0';
    }
}

char *
strbuf_substring(StrBuf *sb, ssize_t sp, ssize_t ep)
{
    char *str;

    sp = normalize_strbuf_pos(sb, sp);
    ep = normalize_strbuf_pos(sb, ep);
    if (sp > ep)
    	SWAP_SSIZE_T(sp, ep);

    str = xmalloc((ep-sp+1) * sizeof(char));
    memcpy(str, sb->buf+sp, (ep-sp+1) * sizeof(char));
    str[ep-sp] = '\0';

    return str;
}

char
strbuf_char_at(StrBuf *sb, ssize_t sp)
{
    return sb->buf[normalize_strbuf_pos(sb, sp)];
}

#if 0
char
strbuf_set_char_at(StrBuf *sb, ssize_t sp, char chr)
{
    char old;

    sp = normalize_strbuf_pos(sb, sp);
    old = str[sp];
    str[sp] = chr;
    if (sp == sb->len) {
        sb->len++;
        strbuf_ensure_capacity(sb, sb->len+1);
        str[sb->len] = '\0'
    }
    return old;
}

void
strbuf_replace_strbuf(StrBuf *sb, ssize_t sp, ssize_t ep, StrBuf *strbuf)
{
    strbuf_replace_data_n(sb,sp,ep,1,strbuf->buf,strbuf->len);
}

char
strbuf_delete_char(StrBuf *sb, ssize_t sp)
{

}

#endif
