/* iksemel (XML parser for Jabber)
** Copyright (C) 2000-2004 Gurer Ozen
** This code is free software; you can redistribute it and/or
** modify it under the terms of GNU Lesser General Public License.
*/

#include "common.h"
#include "iksemel.h"

typedef double align_type ;
struct align_test { char a; align_type b; };
#define DEFAULT_ALIGNMENT  ((size_t) ((char *) &((struct align_test *) 0)->b - (char *) 0))
#define ALIGN_MASK ( DEFAULT_ALIGNMENT - 1 )
#define MIN_CHUNK_SIZE ( DEFAULT_ALIGNMENT * 8 )
#define MIN_ALLOC_SIZE DEFAULT_ALIGNMENT
#define ALIGN(x) ( ((x) + DEFAULT_ALIGNMENT - 1) & ~ALIGN_MASK )

typedef struct ikschunk_struct {
	struct ikschunk_struct *next;
	size_t size;
	size_t used;
	size_t last;
	align_type align[0] ;	// Align data, and ensure struct size matches alignment
	char data[4];
} ikschunk;

struct ikstack_struct {
	size_t allocated;
	ikschunk *meta;
	ikschunk *data;
	align_type align[0] ;	// Ensure struct size matches alignment
};

static ikschunk *
find_space (ikstack *s, ikschunk *c, size_t size)
{
	/* FIXME: dont use *2 after over allocated chunks */
	while (1) {
		if (c->size - c->used >= size) return c;
		if (!c->next) {
			if ((c->size * 2) > size) size = c->size * 2;
			c->next = iks_malloc (sizeof (ikschunk) + size);
			if (!c->next) return NULL;
			s->allocated += sizeof (ikschunk) + size;
			c = c->next;
			c->next = NULL;
			c->size = size;
			c->used = 0;
			c->last = (size_t) -1;
			return c;
		}
		c = c->next;
	}
	return NULL;
}

ikstack *
iks_stack_new (size_t meta_chunk, size_t data_chunk)
{
	ikstack *s;
	size_t len;

	if (meta_chunk < MIN_CHUNK_SIZE) meta_chunk = MIN_CHUNK_SIZE;
	if (meta_chunk & ALIGN_MASK) meta_chunk = ALIGN (meta_chunk);
	if (data_chunk < MIN_CHUNK_SIZE) data_chunk = MIN_CHUNK_SIZE;
	if (data_chunk & ALIGN_MASK) data_chunk = ALIGN (data_chunk);

	len = sizeof (ikstack) + meta_chunk + data_chunk + (sizeof (ikschunk) * 2);
	s = iks_malloc (len);
	if (!s) return NULL;
	s->allocated = len;
	s->meta = (ikschunk *) ((char *) s + sizeof (ikstack));
	s->meta->next = NULL;
	s->meta->size = meta_chunk;
	s->meta->used = 0;
	s->meta->last = (size_t) -1;
	s->data = (ikschunk *) ((char *) s + sizeof (ikstack) + sizeof (ikschunk) + meta_chunk);
	s->data->next = NULL;
	s->data->size = data_chunk;
	s->data->used = 0;
	s->data->last = (size_t) -1;
	return s;
}

void *
iks_stack_alloc (ikstack *s, size_t size)
{
	ikschunk *c;
	void *mem;

	if (size < MIN_ALLOC_SIZE) size = MIN_ALLOC_SIZE;
	if (size & ALIGN_MASK) size = ALIGN (size);

	c = find_space (s, s->meta, size);
	if (!c) return NULL;
	mem = c->data + c->used;
	c->used += size;
	return mem;
}

char *
iks_stack_strdup (ikstack *s, const char *src, size_t len)
{
	ikschunk *c;
	char *dest;

	if (!src) return NULL;
	if (0 == len) len = strlen (src);

	c = find_space (s, s->data, len + 1);
	if (!c) return NULL;
	dest = c->data + c->used;
	c->last = c->used;
	c->used += len + 1;
	memcpy (dest, src, len);
	dest[len] = '\0';
	return dest;
}

char *
iks_stack_strcat (ikstack *s, char *old, size_t old_len, const char *src, size_t src_len)
{
	char *ret;
	ikschunk *c;

	if (!old) {
		return iks_stack_strdup (s, src, src_len);
	}
	if (0 == old_len) old_len = strlen (old);
	if (0 == src_len) src_len = strlen (src);

	for (c = s->data; c; c = c->next) {
		if (c->data + c->last == old) break;
	}
	if (!c) {
		c = find_space (s, s->data, old_len + src_len + 1);
		if (!c) return NULL;
		ret = c->data + c->used;
		c->last = c->used;
		c->used += old_len + src_len + 1;
		memcpy (ret, old, old_len);
		memcpy (ret + old_len, src, src_len);
		ret[old_len + src_len] = '\0';
		return ret;
	}

	if (c->size - c->used > src_len) {
		ret = c->data + c->last;
		memcpy (ret + old_len, src, src_len);
		c->used += src_len;
		ret[old_len + src_len] = '\0';
	} else {
		/* FIXME: decrease c->used before moving string to new place */
		c = find_space (s, s->data, old_len + src_len + 1);
		if (!c) return NULL;
		c->last = c->used;
		ret = c->data + c->used;
		memcpy (ret, old, old_len);
		c->used += old_len;
		memcpy (c->data + c->used, src, src_len);
		c->used += src_len;
		c->data[c->used] = '\0';
		c->used++;
	}
	return ret;
}

void
iks_stack_stat (ikstack *s, size_t *allocated, size_t *used)
{
	ikschunk *c;

	if (allocated) {
		*allocated = s->allocated;
	}
	if (used) {
		*used = 0;
		for (c = s->meta; c; c = c->next) {
			(*used) += c->used;
		}
		for (c = s->data; c; c = c->next) {
			(*used) += c->used;
		}
	}
}

void
iks_stack_delete (ikstack *s)
{
	ikschunk *c, *tmp;

	c = s->meta->next;
	while (c) {
		tmp = c->next;
		iks_free (c);
		c = tmp;
	}
	c = s->data->next;
	while (c) {
		tmp = c->next;
		iks_free (c);
		c = tmp;
	}
	iks_free (s);
}
