/*
* This file is part of SkunkDB
* Copyright (c) 2001 Gianni Tedesco
*
*    SkunkDB 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 of the License, or
*    (at your option) any later version.
*
*    SkunkDB 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 SkunkDB; if not, write to the Free Software
*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


#include <skunk_app.h>
#include <skunk_hash.h>
#include <skunk_make.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

/* Simple buffered output routine */
static inline int write_buf(p_skunk_obuf o, u_int8_t *data, skunk_ofs_t len)
{
	if ( o->len + len >= o->max_len ) {
		if ( write(o->fd, o->buf, o->len)<0 )
			return 0;
		o->len=0;
		o->b=o->buf;
	}
	
	if ( len > o->max_len ) {
		if ( write(o->fd, (char *)data, len)<0 ) {
			return 0;
		}
		return 1;
	}

	o->len+=(size_t)len;
	memcpy(o->b, data, len);
	o->b=(u_int8_t *)(((long)o->b)+len);
	return 1;
}

/* API: Add a record to the database */
p_skunkrec make_add(
		p_skunkwr	s,
		u_int8_t	*key,
		u_int8_t	*val, 
		skunk_ofs_t	klen,
		skunk_ofs_t	vlen)
{
	p_skunkrec rec;
	p_skunk_head h;

	if ( !(rec=malloc(sizeof(skunkrec_t))) ) {
		return NULL;
	}else{
		memset(rec, 0, sizeof(skunkrec_t));
	}

	if ( !(rec->key=key) ) {
		free(rec);
		return NULL;
	}
	
	if ( !(rec->val=val) ) {
		free(rec);
		return NULL;
	}

	rec->key_len=klen;
	rec->val_len=vlen;
	rec->hash=skunk_hash((u_int8_t *)key, rec->key_len);

	/* Prepend to the list */
	h=&s->head[MOD(rec->hash)];
	rec->next=h->list;
	h->list=rec;
	h->count++;
	
	return rec;
}

#undef R_DEBUG
/* Collision resolution shuffly stuff */
static inline int skunk_resolve(p_skunkwr s, p_skunk_head h)
{
	p_skunkrec r;
	p_skunkrec *r2;
	skunk_ofs_t d=0;

	/* Make sure load factor is no greater than 66% */
	h->count+=h->count/2;

	/* Use an odd number */
	h->count|=1;

	/* Hardcoded prime number size */
	if ( h->count>512 && h->count<1021 ) h->count=1021;

	/* Make sure we aren't a power of 2 */
	h->count|=1;

	r2=malloc(h->count*sizeof(p_skunkrec));
	if ( !r2 ) {
		h->table=NULL;
		return 0;
	}else{
		memset(r2, 0, h->count*sizeof(p_skunkrec));
	}

#ifdef R_DEBUG
	printf("%u records\n", h->count);
#endif
	for(r=h->list; r; r=r->next)
	{
		skunk_ofs_t pos=(r->hash^r->key_len)%h->count;
		skunk_ofs_t dist=0;
#ifdef R_DEBUG
		skunk_ofs_t ipos=pos;
#endif
quadstart:
		if ( r2[pos] ) {
			pos=(pos+1)%h->count;
			dist++;
			goto quadstart;
		}else{
#ifdef R_DEBUG
			if ( dist ) printf("%u -> %u (%u)\n", ipos, pos, dist);
#endif
			d+=dist;
			r2[pos]=r;
		}
	}
#ifdef R_DEBUG
	printf("avg. dist %u\n\n", d/h->count);
#endif

	h->table=r2;
	return 1;
}

/* Write out the initial fixed size hash table */
skunk_ofs_t make_table1(p_skunkwr s)
{
	skunk_ofs_t cur=SKUNK_DIRECTORY_SIZE;
	skunk_table_t buf[SKUNK_DIRECTORY_MOD];
	skunk_head_t *h=(skunk_head_t *)s->head;
	int i;

	/* Write the initial table */
	for(i=0; i<SKUNK_DIRECTORY_MOD; i++) {
		if ( h[i].count ) {
			if ( !skunk_resolve(s, &h[i]) ) return 0;
			buf[i].ofs=cur;
			buf[i].len=h[i].count; /* Changed by skunk_resolve */
			cur+=buf[i].len*(sizeof(skunk_bucket_t));
		}else{
			memset(&buf[i], 0, sizeof(buf[i]));
		}
	}

	/* Write the lot */
	if ( !write_buf(&s->wrbuf,
		(u_int8_t *)buf, 
		sizeof(buf)) ) {
		return 0;
	}

	return cur;
}

/* API: Write out the database */
int make_write(p_skunkwr s)
{
	int i;
	skunk_ofs_t cur;
	skunk_ofs_t data,pos;
	skunk_bucket_t b;
	p_skunkrec r;
	skunk_head_t *h=(skunk_head_t *)s->head;

	/* Write initial fixed size table */
	if ( !(data=cur=make_table1(s)) )
		return 0;
	
	/* Write main hash tables */
	for(i=0; i<SKUNK_DIRECTORY_MOD; i++) {
		if ( !h[i].count ) continue;

		for(pos=0; pos<h[i].count; pos++) {
			if ( (r=h[i].table[pos]) ) {
				b.hash=r->hash;
				b.ofs=cur;
				b.klen=r->key_len;
				b.vlen=r->val_len;
				cur+=r->key_len+r->val_len;
			}else{
				memset(&b, 0, sizeof(b));
			}

			if ( !write_buf(&s->wrbuf, (u_int8_t *)&b, sizeof(b)) ) {
				return 0;
			}
		}
	}

	/* write the record data */
	for(i=0; i<SKUNK_DIRECTORY_MOD; i++) {
		if ( !h[i].count ) continue;

		for( pos=0; pos<h[i].count; pos++) {
			if ( (r=h[i].table[pos]) ) {
				if ( !write_buf(&s->wrbuf, (u_int8_t *)r->key, r->key_len) )
					return 0;
				if ( !write_buf(&s->wrbuf, (u_int8_t *)r->val, r->val_len) )
					return 0;
			}
		}
		if ( h->table ) {
			free(h->table);
			h->table=NULL;
		}
	}

	/* flush the buffer */
	if ( s->wrbuf.len && write(s->wrbuf.fd, s->wrbuf.buf, s->wrbuf.len )<0 )
		return 0;

	return 1;
}

/* API: Initialise a skunk writing session */
int make_init(p_skunkwr s, int fd, int buffer)
{
	if ( !s ) return 0;

	memset((char *)&s->head, 0, sizeof(s->head));
	s->wrbuf.buf=NULL;
	s->wrbuf.fd=-1;
	
	if ( !(s->wrbuf.buf=malloc(buffer)) ) {
		return 0;
	}
	s->wrbuf.b=s->wrbuf.buf;
	s->wrbuf.len=0;
	s->wrbuf.max_len=buffer;
	s->wrbuf.fd=fd;

	return 1;
}

/* Destroy a head, freeup all elements */
void inline make_destroy(p_skunk_head h)
{
	p_skunkrec foo, bar;
	
	if ( h->table ) {
		free(h->table);
		h->table=NULL;
	}
	for (foo=h->list; foo;) {
		bar=foo;
		foo=foo->next;
		free(bar);
	}
}

/* API: Close a database session and cleanup the data etc.. */
void make_close(p_skunkwr s)
{
	int i;
	
	if ( s->wrbuf.buf ) {
		free(s->wrbuf.buf);
		s->wrbuf.buf=NULL;
	}
	s->wrbuf.fd=-1;

	for(i=0; i<SKUNK_DIRECTORY_MOD; i++) {
		if ( !s->head[i].count ) continue;
		make_destroy(&s->head[i]);
	}
}
