#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/list.h>

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

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

static int ftp_lookup_validate(struct dentry*, int);
static int ftp_hash_dentry(struct dentry*, struct qstr*);
static int ftp_compare_dentry(struct dentry*, struct qstr*, struct qstr*);
static int ftp_delete_dentry(struct dentry*);

static struct dentry_operations ftpfs_dentry_operations = {
	d_revalidate:	ftp_lookup_validate,
	d_hash:		ftp_hash_dentry,
	d_compare:	ftp_compare_dentry,
	d_delete:	ftp_delete_dentry,
};

static int ftp_readdir(struct file*, void*, filldir_t);
static int ftp_dir_open(struct inode*, struct file*);

static struct dentry *ftp_lookup(struct inode*, struct dentry*);
static int ftp_mkdir(struct inode*, struct dentry*, int);
static int ftp_create(struct inode*, struct dentry*, int);
static int ftp_rmdir(struct inode*, struct dentry*);
static int ftp_rename(struct inode*, struct dentry*, struct inode*, struct dentry*);
static int ftp_unlink(struct inode*, struct dentry*);

struct file_operations ftp_dir_operations =  {
	read:		generic_read_dir,
	readdir:	ftp_readdir,
	open:		ftp_dir_open,
};

struct inode_operations ftp_dir_inode_operations = {
	create:		ftp_create,
	lookup:		ftp_lookup,
	unlink:		ftp_unlink,
	mkdir:		ftp_mkdir,
	rmdir:		ftp_rmdir,
	rename:		ftp_rename,
};


static int
ftp_lookup_validate(struct dentry* dentry, int flags){
	struct inode *inode = dentry->d_inode;
	int valid;

	DEBUG("\n");
	if(inode){
		lock_kernel();
		valid = is_bad_inode(inode) ? 0 : 1;
		unlock_kernel();
		return valid;
	}
	return 1;
}

static int
ftp_hash_dentry(struct dentry* dentry, struct qstr* this){
	unsigned long hash;
	int i;

	DEBUG("\n");

	hash = init_name_hash();
	for(i = 0; i < this->len; i++)
		hash = partial_name_hash(tolower(this->name[i]), hash);
	this->hash = end_name_hash(hash);
	return 0;
}

static int
ftp_compare_dentry(struct dentry* dentry, struct qstr* a, struct qstr* b){
	int i, res = 1;

	DEBUG("\n");
	if(a->len != b->len)
		goto out;
	for(i = 0; i < a->len; i++)
		if(tolower(a->name[i]) != tolower(b->name[i]))
			goto out;
	res = 0;
out:
	return res;
}

static int
ftp_delete_dentry(struct dentry* dentry){

	DEBUG("\n");
	if(dentry->d_inode){
		if(is_bad_inode(dentry->d_inode))
			DEBUG("Done\n");
			return 1;
	}
	DEBUG("Done\n");
	return 0;
}

static int
ftp_readdir(struct file* f , void* dirent, filldir_t filldir){
	struct dentry *dentry = f->f_dentry;
	struct inode *inode = dentry->d_inode;
	struct super_block *sb = inode->i_sb;
	struct ftp_sb_info *info = (struct ftp_sb_info*)sb->u.generic_sbp;
	char buf[FTP_MAXPATHLEN];
	int result, pos, res;

	struct ftp_directory *dir;
	struct ftp_dirlist_node *file;
	
	if(!info->mnt.mount_point[0]){
		ftp_get_name(f->f_vfsmnt->mnt_mountpoint, info->mnt.mount_point);
	}
	
	DEBUG(" reading %s, f_pos=%d\n", dentry->d_name.name, (int)f->f_pos);
	if(ftp_get_name(dentry, buf) < 0){
		VERBOSE(" ftp_get_name failed!\n");
		return -1;
	}
	
	result = 0;
	switch((unsigned int) f->f_pos){
		case 0:
			if(filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR) < 0)
				goto out;
			f->f_pos = 1;
		case 1:
			if(filldir(dirent, "..", 2, 1, dentry->d_parent->d_inode->i_ino, DT_DIR) < 0)
				goto out;
			f->f_pos = 2;
		default:
			ftp_lock(info);
			res = ftp_cache_get(info, buf, &dir);
			ftp_unlock(info);
			if(res < 0){
				VERBOSE(" ftp_cache_get failed!\n");
				switch(res){
				    case -ERESTARTSYS:	res = -EINTR;
							ftp_disconnect(info);
							break;
				}
				return res;
			}

			pos = 2;
			for(file = dir->head; file != NULL; file = file->next){
				if((pos == f->f_pos)){
					struct qstr qname;
					unsigned long ino;

					qname.name = file->entry.name;
					qname.len = strlen(qname.name);

					ino = find_inode_number(dentry, &qname);
					if(!ino)
						ino = iunique(dentry->d_sb, 2);
					if(filldir(dirent, qname.name, qname.len, f->f_pos, ino, DT_UNKNOWN) >= 0)
						f->f_pos++;
					
				}
				pos++;
			}

	}
out:
	return result;
}

static int
ftp_dir_open(struct inode* inode, struct file* f){
	DEBUG("\n");
	return 0;
}


static struct dentry*
ftp_lookup(struct inode* dir, struct dentry* dentry){
	struct super_block *sb = dir->i_sb;
	struct ftp_sb_info *info = (struct ftp_sb_info*)sb->u.generic_sbp;
	struct ftp_fattr fattr;
	struct inode *inode;
	int res;
	
	DEBUG(" dname:%s\n", dentry->d_name.name);
	if((res = ftp_get_attr(dentry, &fattr, (struct ftp_sb_info*)dir->i_sb->u.generic_sbp)) < 0){
		VERBOSE(" file not found!\n");
		inode = NULL;
		dentry->d_op = &ftpfs_dentry_operations;
		d_add(dentry, inode);
		switch(res){
		    case -ERESTARTSYS:	res = -EINTR;
					ftp_disconnect(info);
					return ERR_PTR(res);
		}
		return NULL; 
	}

	fattr.f_ino = iunique(dentry->d_sb, 2);
	inode = ftp_iget(dir->i_sb, &fattr);
	if(inode){
		dentry->d_op = &ftpfs_dentry_operations;
		d_add(dentry, inode);
	}
	
	return NULL; 
}

static int
ftp_instantiate(struct dentry* dentry){
	struct ftp_sb_info *info = (struct ftp_sb_info*)dentry->d_sb->u.generic_sbp;
	struct ftp_fattr fattr;
	struct inode *inode;
	
	if(ftp_get_attr(dentry, &fattr, info) < 0){
		VERBOSE(" ftp_get_attr failed!\n");
		return -1;
	}
	
	fattr.f_ino = iunique(dentry->d_sb, 2);
	inode = ftp_iget(dentry->d_sb, &fattr);
	if(!inode)
		return -EACCES;
		
	d_instantiate(dentry, inode);
	
	return 0;	
}

static int
ftp_mkdir(struct inode *dir, struct dentry *dentry, int mode){
	struct ftp_sb_info *info = (struct ftp_sb_info*)dentry->d_sb->u.generic_sbp;
	char buf[FTP_MAXPATHLEN];
	int res;
	
	DEBUG("\n");
	
	ftp_get_name(dentry, buf);
	if((res = ftp_proc_mkdir(info, buf)) < 0){
		VERBOSE("mkdir failed!\n");
		return res;
	}
	ftp_cache_invalidate(dentry->d_parent);
	return ftp_instantiate(dentry);
}

static int
ftp_create(struct inode* dir, struct dentry *dentry, int mode){
	struct ftp_sb_info *info = (struct ftp_sb_info*)dentry->d_sb->u.generic_sbp;
	char buf[FTP_MAXPATHLEN];
	int res;
	
	DEBUG("\n");
	ftp_get_name(dentry, buf);
	if((res = ftp_proc_create(info, buf)) < 0){
		VERBOSE(" Create failed!\n");
		return res;
	}
	
	ftp_cache_invalidate(dentry->d_parent);
	return ftp_instantiate(dentry);
}

static int
ftp_rmdir(struct inode* dir, struct dentry *dentry){
	struct ftp_sb_info *info = (struct ftp_sb_info*)dentry->d_sb->u.generic_sbp;
	char buf[FTP_MAXPATHLEN];
	
	if(!d_unhashed(dentry))
		return -EBUSY;
	
	ftp_get_name(dentry, buf);
	
	ftp_cache_invalidate(dentry->d_parent);
	
	return ftp_proc_rmdir(info, buf);
}

static int
ftp_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry){
	struct ftp_sb_info *info = (struct ftp_sb_info*)old_dentry->d_sb->u.generic_sbp;
	char buf1[FTP_MAXPATHLEN], buf2[FTP_MAXPATHLEN];
	int res;
	
	ftp_get_name(old_dentry, buf1);
	ftp_get_name(new_dentry, buf2);
	
	if(new_dentry->d_inode){
		d_delete(new_dentry);
	}
	
	if((res = ftp_proc_rename(info, buf1, buf2)) < 0)
		return res;
		
	ftp_cache_invalidate(old_dentry->d_parent);
	ftp_cache_invalidate(new_dentry->d_parent);
	
	return 0;
}

static int
ftp_unlink(struct inode *dir, struct dentry *dentry){
	struct ftp_sb_info *info = (struct ftp_sb_info*)dentry->d_sb->u.generic_sbp;
	char buf[FTP_MAXPATHLEN];

	ftp_get_name(dentry, buf);
	ftp_cache_invalidate(dentry->d_parent);
	return ftp_proc_unlink(info, buf);
}

