/*
    zbinbuf - Binary buffer
    Copyright (C) 2011-2023 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <zbinbuf.h>

#include <glib.h>
#include <stdio.h>
#include <string.h>
#include <zstr.h>

#ifdef Z_MSC
#pragma warning(disable : 4996)
#endif

struct zbinbuf *zbinbuf_init(void){
    struct zbinbuf *zbb;

    zbb = g_new0(struct zbinbuf, 1);
    zbb->size = 256;
    zbb->buf = g_new0(char, zbb->size);
	zbb->buf[0] = '\0';
    zbb->len = 0;
	zbb->increment = 0;
    return zbb;
}


void zbinbuf_free(struct zbinbuf *zbb){
    if (zbb == NULL) return;

    g_free(zbb->buf);
    g_free(zbb);
}

void zbinbuf_append_bin(struct zbinbuf *zbb, const void *data, int len){
    int size;

    if (len <= 0) return;
    size = zbb->len + len + 1;
    if (size > zbb->size){
		size += zbb->increment;
        zbb->buf = (char *)g_realloc(zbb->buf, size);
        zbb->size = size;
    }
    memcpy(zbb->buf + zbb->len, data, len);
    zbb->len += len;
	zbb->buf[zbb->len] = '\0';
}

void zbinbuf_append(struct zbinbuf *zbb, const char *str){
	zbinbuf_append_bin(zbb, str, strlen(str));
}

int zbinbuf_sprintf(struct zbinbuf *zbb, char *fmt, ...)
{
	gchar *c;
	int len;

	va_list l;
	va_start(l, fmt);
	c = g_strdup_vprintf(fmt, l);
	len = strlen(c);
	zbinbuf_truncate(zbb, 0);
	zbinbuf_append_bin(zbb, c, len);
	g_free(c);
	va_end(l);
	return len;
}

int zbinbuf_sprintfa(struct zbinbuf *zbb, char *fmt, ...)
{
	gchar *c;
	int len;

	va_list l;
	va_start(l, fmt);
	c = g_strdup_vprintf(fmt, l);
	len = strlen(c);
	zbinbuf_append_bin(zbb, c, len);
	g_free(c);
	va_end(l);
	return len;
}


void zbinbuf_prepend(struct zbinbuf *zbb, void *data, int len){
    int size;

    if (len <= 0) return;
    size = zbb->len + len + 1;
    if (size > zbb->size){
        zbb->buf = (char *)g_realloc(zbb->buf, size);
        zbb->size = size;
    }
    memmove(zbb->buf + len, zbb->buf, zbb->len + 1);
    memcpy(zbb->buf, data, len);
    zbb->len += len;
	zbb->buf[zbb->len] = '\0';
}

void zbinbuf_erase(struct zbinbuf *zbb, int pos, int len){
    int l;

    if (len <= 0) return;
    if (pos < 0) pos = 0;
    l = zbb->len - pos - len; // 012xx56789 pos=3 len=2
    if (l > 0){
        memmove(zbb->buf + pos, zbb->buf + pos + len, l + 1);
    }
    zbb->len -= len;
	zbb->buf[zbb->len] = '\0';
}

void zbinbuf_truncate(struct zbinbuf *zbb, int len){
	zbb->len = Z_MIN(len, zbb->size - 1);
	zbb->buf[zbb->len] = '\0';
}


void zbinbuf_getstr(struct zbinbuf *zbb, int pos, char *str, int maxlen){
	int len;

	if (pos < 0){
		*str = '\0';
		return;
	}
	len = zbb->len - pos + 1;
	if (len <= 0) return;
	if (maxlen >= 0 && len > maxlen) len = maxlen;

	g_strlcpy(str, zbb->buf + pos, len);
	//memcpy(str, zbb->buf + pos, len); untested
}



void zbinbuf_getline(struct zbinbuf *zbb, int *pos, char *str, int maxlen){
	int i;
	char *src, *dst;

	if (maxlen <= 0) return;
	if (pos < 0){
		*str = '\0';
		return;
	}

	src = zbb->buf + *pos;	// no dereference until boundary check
	dst = str;
	for (i = 0; i < maxlen - 1; i++){
		if (*pos >= zbb->len) break;
		if (*src == '\r' || *src == '\n') break;
		*dst = *src;
		dst++;
		src++;
		(*pos)++;
	}
	*dst = '\0';
}


int zbinbuf_append_file(struct zbinbuf *zbb, const char *filename){
	FILE *f;
	int r, total = 0;
	char buf[65536];

	f = fopen(filename, "rb");
	if (!f) return -1;

	while(1){
		r = fread(buf, 1, sizeof(buf), f);
		if (r < 0) {
			total = -1;
			break;
		}
		if (r == 0) break;
		zbinbuf_append_bin(zbb, buf, r);
		total += r;
	}
	fclose(f);
	return total; 
}

int zbinbuf_write_to_file(struct zbinbuf *zbb, const char *filename, int offset, int len){
    FILE *f;
    int ret;

    f = fopen(filename, "wb");
    if (!f) return -1;

    ret = fwrite(zbb->buf + offset, 1, len, f);
    fclose(f);
    return ret;
}

void zbinbuf_append_le_s16(struct zbinbuf *zbb, int16_t a){
    zbinbuf_append_bin(zbb, &a, sizeof(a));
}

void zbinbuf_append_le_s32(struct zbinbuf *zbb, int32_t a){
    zbinbuf_append_bin(zbb, &a, sizeof(a));
}

char *zbinbuf_readline(struct zbinbuf *zbb){
	char *c = (char *)memchr(zbb->buf, '\n', zbb->len);
	if (c == NULL) return NULL;

	char *ret = g_strndup(zbb->buf, c - zbb->buf + 1);
	ret[c - zbb->buf] = '\0';
	zbinbuf_erase(zbb, 0, c - zbb->buf + 1);

	z_trim_end(ret);
	return ret;
}