#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>

#include <asm/system.h>
#include <asm/uaccess.h>

#include "ftpfs.h"
#include "ftpfs_proc.h"

#define dir_cache info->cache

void
ftp_cache_init(struct ftp_sb_info *info){
	memset(&dir_cache, 0, sizeof(struct ftp_dir_cache));
}

unsigned long
ftp_cache_hash(char *name){
	unsigned long hash = 0;
	int i;

	for(i = 0; i < strlen(name); i++)
		hash += name[i];
	return hash % FTP_CACHE_HASH;
}

int
ftp_cache_deldir(struct ftp_hashlist_node *n){
	struct ftp_dirlist_node *p, *q;

	DEBUG(" deleting a cache entry...\n");

	if(!n) return -1;
	for(p = n->directory.head; p != NULL; p = q){
		q = p->next;
		if(p->entry.name != NULL){
			kfree(p->entry.name);
			p->entry.name = NULL;
		}
		kfree(p);
	}

	kfree(n->directory.name);
	if(n->prev != NULL)
		n->prev->next = n->next;
	if(n->next != NULL)
		n->next->prev = n->prev;
	DEBUG("done\n");
	return 0;
}

int
ftp_cache_infront(unsigned long hsh, struct ftp_hashlist_node *n, struct ftp_sb_info *info){
	unsigned long h;

	DEBUG(" bringing in front %s\n", n->directory.name);
	if(n->prev){
		h = ftp_cache_hash(n->directory.name);
		if(h != hsh)
			VERBOSE("oops!");
			
		n->prev->next = n->next;

		if(n->next)
			n->next->prev = n->prev;

		if(dir_cache.hash[h])
			dir_cache.hash[h]->prev = n;
		n->prev = NULL;
		n->next = dir_cache.hash[h];
		dir_cache.hash[h] = n;
	}
	return 0;
}


int
ftp_cache_shrink(unsigned long hsh, struct ftp_sb_info *info){
	struct ftp_hashlist_node *p;

	DEBUG(" Shrinking hash bucket %u\n", (unsigned)hsh);

	if(dir_cache.len[hsh] == 0) return -1;
	for(p = dir_cache.hash[hsh]; p->next != NULL; p = p->next);
	ftp_cache_deldir(p);
	dir_cache.len[hsh]--;
	
	return 0;
}

int
ftp_cache_add(struct ftp_sb_info *info, char *name, struct ftp_directory **dir){
	unsigned long hsh;
	struct ftp_hashlist_node *p;
	int res;
	
	hsh = ftp_cache_hash(name);
	
	while(dir_cache.len[hsh] >= FTP_CACHE_LEN)
		ftp_cache_shrink(hsh, info);
	p = (struct ftp_hashlist_node*)kmalloc(sizeof(struct ftp_hashlist_node), GFP_KERNEL);
	if(!p){
		res = -1;
		VERBOSE("kmalloc error!\n");
		goto out;
	}

	memset(p, 0, sizeof(struct ftp_hashlist_node));
	p->directory.valid = 1;
	p->directory.time = CURRENT_TIME;
	p->directory.name = (char*)kmalloc(strlen(name) + 1, GFP_KERNEL);
	if(!p->directory.name){
		VERBOSE("kmalloc error!\n");
		res = -1;
		goto out1;
	}
	strcpy(p->directory.name, name);

	DEBUG(" loading dir...\n");
	if((res = ftp_loaddir(info, name, &p->directory)) < 0){
		VERBOSE(" Couldn't load directory!\n");
		goto out2;
	}
	p->prev = NULL;
	p->next = dir_cache.hash[hsh];
	if(p->next)
		p->next->prev = p;
	dir_cache.hash[hsh] = p;

	dir_cache.len[hsh]++;
	*dir = &p->directory;
	return 0;
out2:
	kfree(p->directory.name);
out1:
	kfree(p);
out:
	return res;
}

int
ftp_cache_get(struct ftp_sb_info *info, char *name, struct ftp_directory **dir){
	struct ftp_hashlist_node *p;
	unsigned long hsh;

	DEBUG(" looking for %s\n", name);
	hsh = ftp_cache_hash(name);
	for(p = dir_cache.hash[hsh]; p != NULL; p = p->next)
		if((strcmp(name, p->directory.name) == 0)&&(p->directory.valid)){
			DEBUG(" found in cache!\n");
			if(CURRENT_TIME - p->directory.time > FTP_CACHE_TTL){
				DEBUG(" cache entry too old!\n");
				return  ftp_cache_add(info, name, dir);
			}
			ftp_cache_infront(hsh, p, info);
			*dir = &p->directory;
			return 0;
		}
	DEBUG(" %s not found in cache. Adding...!\n", name);
	return ftp_cache_add(info, name, dir);
}

int
ftp_cache_empty(struct ftp_sb_info *info){
	int i,j;
	struct ftp_hashlist_node *p;

	DEBUG(" closing cache...\n");
	for(i = 0; i < FTP_CACHE_HASH; i++)
		for(j = 0; j < dir_cache.len[i]; j++){
			p = dir_cache.hash[i];
			dir_cache.hash[i] = p->next;
			ftp_cache_deldir(p);
		}
	DEBUG(" OK\n");
	return 0;
}

void
ftp_cache_invalidate(struct dentry *dentry){
	struct super_block *sb = dentry->d_inode->i_sb;
	struct ftp_sb_info *info = (struct ftp_sb_info*)sb->u.generic_sbp;
	char buf[FTP_MAXPATHLEN];
	struct ftp_hashlist_node *p;
	unsigned long hsh;
		
	ftp_get_name(dentry, buf);
	hsh = ftp_cache_hash(buf);
	
	for(p = dir_cache.hash[hsh]; p != NULL; p = p->next)
		if(strcmp(buf, p->directory.name) == 0){
			p->directory.valid = 0;
			return;
		}
}

