#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "fcache.h"
#include "lib.h"

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE !FALSE
#endif

typedef struct fcache_entry {
	struct fcache_entry *next;	
	void (*freefn)(void **);
	char *filename;
	void *data;
} FEntry;

struct fcache {
	int items,size,max,upto;
	FEntry **table;
};

static FEntry *fcache_find(FCache *f, char *filename, FEntry **pprev, FEntry **last, int *index);

FCache *fcache_init(int hashsize, int max_items) {
	FCache *f;

	f = (FCache *)calloc(1,sizeof(FCache));
	f->table = (FEntry **)calloc(hashsize,sizeof(FEntry *));
	f->size = hashsize;
	f->max = max_items;

	return f;
}

void fcache_free(FCache **f) {
	FEntry *p,*n;
	int i;

	if (!f || !*f) return ;
	for(i = 0; i < (*f)->size; i++) {
		for(p = (*f)->table[i]; p; p = n) {
			n = p->next;
			if (p->freefn)
				p->freefn(&p->data);
			free(p->filename);			
			free(p);
		}
	}
	free((*f)->table);
	free(*f);
}

int fcache_add(FCache *f, char *filename, void *data, void (*freefn)(void **)) {
	FEntry *p,*prev;
	int i;

	if (!f || !filename) return FALSE;

	p = fcache_find(f,filename,&prev,NULL,&i);
	if (p) return FALSE;

	p = calloc(1,sizeof(FEntry));
	p->filename = strdup(filename);
	p->freefn = freefn;
	p->data = data;

	if (prev) prev->next = p;
	else {
		if (f->upto == i) f->upto++;
		f->table[i] = p;
	}
	f->items++;

	for(; f->items >= f->max; f->upto++) {
		if (f->upto == f->size) f->upto = 0;
		p = f->table[f->upto];
		if (!p) continue;		
		f->table[f->upto] = p->next;
		if (p->freefn)
			p->freefn(&p->data);
		free(p->filename);
		free(p);
		f->items--;
	}

	return TRUE;
}
int fcache_rmv(FCache *f, char *filename) {
	FEntry *p,*prev;
	int i;

	if (!f || !filename) return FALSE;
	p = fcache_find(f,filename,&prev,NULL,&i);
	if (!p) return FALSE;
	
	if (prev) prev->next = p->next;
	else f->table[i] = p->next;
	f->items--;

	free(p->filename);
	if (p->freefn)
		p->freefn(&p->data);
	free(p);	

	return TRUE;
}
void *fcache_get(FCache *f, char *filename) {
	FEntry *p,*prev,*last;
	int i;

	if (!f || !filename) return NULL;

	p = fcache_find(f,filename,&prev,&last,&i);
	if (!p) return NULL;

	/*
	if (p != f->table[i]) {
		// Remove from list
		if (prev) prev->next = p->next;
		else f->table[i] = p->next;
		p->next = NULL;

		// Add to end of list
		if (last != p) last->next = p;
		else {
			if (prev) prev->next = p;
			else f->table[i] = p;
		}
	}
	*/
	
	return p->data;
}

FEntry *fcache_find(FCache *f, char *filename, FEntry **pprev, FEntry **last, int *index) {
	FEntry *p,*prev;
	int i;

	i = lib_str_hash(filename,f->size);
	if (pprev) *pprev = NULL;
	if (last) *last = NULL;
	if (index) *index = i;

	for(p = f->table[i], prev = NULL; p; prev = p, p = p->next) {
		if (strcmp(p->filename,filename)==0) {
			if (last) for(*last = p; (*last)->next; *last = (*last)->next);
			if (pprev) *pprev = prev;
			return p;
		}
	}
	if (last) *last = prev;
	return NULL;
}

#define _DEBUG_FCACHE
#ifdef _DEBUG_FCACHE
void fcache_test() {
	FCache *f = fcache_init(1000,5000);
	char bf[BFSZ];
	int i,*j,hits;

	for(i = 0; i < 10000; i++) {
		sprintf(bf,"%d",i);
		fcache_add(f,bf,&i,NULL);
	}

	for(hits = i = 0; i < 10000; i++) {
		sprintf(bf,"%d",i);
		j = fcache_get(f,bf);
		if (j) hits++;
	}

	fcache_free(&f);
}
#endif
