/*
  SPDX-License-Identifier: GPL-2.0-only

  Copyright (C) 2008 Arnaldo Carvalho de Melo <acme@redhat.com>

  Grow only buffer, add entries but never delete
*/

#include "gobuffer.h"

#include <search.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <zlib.h>
#include <errno.h>

#include "dutil.h"

#define GOBUFFER__BCHUNK (8 * 1024)
#define GOBUFFER__ZCHUNK (8 * 1024)

void gobuffer__init(struct gobuffer *gb)
{
	gb->entries = NULL;
	gb->nr_entries = gb->allocated_size = 0;
	/* 0 == NULL */
	gb->index = 1;
}

struct gobuffer *gobuffer__new(void)
{
	struct gobuffer *gb = malloc(sizeof(*gb));

	if (gb != NULL)
		gobuffer__init(gb);

	return gb;
}

void __gobuffer__delete(struct gobuffer *gb)
{
	if (gb == NULL)
		return;

	zfree(&gb->entries);
}

void gobuffer__delete(struct gobuffer *gb)
{
	__gobuffer__delete(gb);
	free(gb);
}

void *gobuffer__ptr(const struct gobuffer *gb, unsigned int s)
{
	return s ? gb->entries + s : NULL;
}

int gobuffer__allocate(struct gobuffer *gb, unsigned int len)
{
	const unsigned int rc = gb->index;
	const unsigned int index = gb->index + len;

	if (index >= gb->allocated_size) {
		unsigned int allocated_size = (gb->allocated_size +
					       GOBUFFER__BCHUNK);
		if (allocated_size < index)
			allocated_size = index + GOBUFFER__BCHUNK;
		char *entries = realloc(gb->entries, allocated_size);

		if (entries == NULL)
			return -ENOMEM;

		gb->allocated_size = allocated_size;
		gb->entries = entries;
	}

	gb->index = index;
	return rc;
}

int gobuffer__add(struct gobuffer *gb, const void *s, unsigned int len)
{
	const int rc = gobuffer__allocate(gb, len);

	if (rc >= 0) {
		++gb->nr_entries;
		memcpy(gb->entries + rc, s, len);
	}
	return rc;
}

void gobuffer__copy(const struct gobuffer *gb, void *dest)
{
        if (gb->entries) {
		memcpy(dest, gb->entries, gobuffer__size(gb));
	} else {
		/* gobuffer__size will be 0 or 1. */
		memcpy(dest, "", gobuffer__size(gb));
	}
}

const void *gobuffer__compress(struct gobuffer *gb, unsigned int *size)
{
	z_stream z = {
		.zalloc	  = Z_NULL,
		.zfree	  = Z_NULL,
		.opaque	  = Z_NULL,
		.avail_in = gobuffer__size(gb),
		.next_in  = (Bytef *)(gobuffer__entries(gb) ? : ""),
	};
	void *bf = NULL;
	unsigned int bf_size = 0;

	if (deflateInit(&z, Z_BEST_COMPRESSION) != Z_OK)
		goto out_free;

	do {
		const unsigned int new_bf_size = bf_size + GOBUFFER__ZCHUNK;
		void *nbf = realloc(bf, new_bf_size);

		if (nbf == NULL)
			goto out_close_and_free;

		bf = nbf;
		z.avail_out = GOBUFFER__ZCHUNK;
		z.next_out  = (Bytef *)bf + bf_size;
		bf_size	    = new_bf_size;
		if (deflate(&z, Z_FINISH) == Z_STREAM_ERROR)
			goto out_close_and_free;
	} while (z.avail_out == 0);

	deflateEnd(&z);
	*size = bf_size - z.avail_out;
out:
	return bf;

out_close_and_free:
	deflateEnd(&z);
out_free:
	free(bf);
	bf = NULL;
	goto out;
}
