/*
 *   Copyright (c) International Business Machines Corp., 2001-2004
 *
 *   This program 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.
 *
 *   This program 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 this program;  if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include "rand.h"
#include "filelist.h"
#include "util.h"
#include "rwlock.h"
#include "rbt.h"
#include "cirlist.h"

#if 0
static
void print_cl(struct cirlist *cl)
{
	struct cnode *cur = cl->head;
	printf("curlist: ");
	if (cur == NULL) {
		printf("\n");
		return;
	}
	do {
		printf("%d ", cur->obj->num);
		cur = cur->next;
	} while (cur != cl->head);
	printf("\n");
}
#endif

#if 0
static
int node_cmp(struct ffsb_file *a, struct ffsb_file *b)
{
	return a->num - b->num;
}
#endif

static
void build_dirs(struct benchfiles *bf)
{
	char buf[FILENAME_MAX];
	int i;

	if (mkdir(bf->basedir, S_IRWXU) < 0)
		if (errno != EEXIST) {
			perror(bf->basedir);
			exit(1);
		}
	for (i = 0; i < bf->numsubdirs; i++) {
		snprintf(buf, FILENAME_MAX, "%s/%s%s%d",
			 bf->basedir, bf->basename, SUBDIRNAME_BASE, i);
		if (mkdir(buf, S_IRWXU) == -1)
			if (errno != EEXIST) {
				perror(buf);
				exit(1);
			}
	}
}

void init_filelist(struct benchfiles *b, char *basedir, char *basename,
		   uint32_t numsubdirs, int builddirs)
{
	memset(b, 0, sizeof(struct benchfiles));
	b->basedir = ffsb_strdup(basedir);
	b->basename = ffsb_strdup(basename);
	b->numsubdirs = numsubdirs;
	init_rwlock(&b->fileslock);
	b->files = rbtree_construct();
	b->dirs = rbtree_construct();
	b->holes = ffsb_malloc(sizeof(struct cirlist));
	b->dholes = ffsb_malloc(sizeof(struct cirlist));
	init_cirlist(b->holes);
	init_cirlist(b->dholes);

	if (builddirs)
		build_dirs(b);
}

static void file_destructor(struct ffsb_file *file)
{
	free(file->name);
	free(file);
}

void destroy_filelist(struct benchfiles *bf)
{
	free(bf->basedir);
	free(bf->basename);

	while (!cl_empty(bf->holes)) {
		struct ffsb_file *cur = cl_remove_head(bf->holes);
		file_destructor(cur);
	}
	free(bf->holes);
	rbtree_clean(bf->files, file_destructor);
	free(bf->files);
}

struct ffsb_file *add_file(struct benchfiles *b, uint64_t size, randdata_t * rd)
{
	struct ffsb_file *newfile, *oldfile = NULL;
	int filenum = 0;

	/* We pre-allocate here, because I don't want to spend time
	 * malloc'ing while the list is locked we free it later if
	 * necessary
	 */
	newfile = ffsb_malloc(sizeof(struct ffsb_file));

	newfile->size = size;
	init_rwlock(&(newfile->lock));

	/* Write lock the filelist, begin critical section */
	rw_lock_write(&b->fileslock);

	/* First check "holes" for a file  */
	if (!cl_empty(b->holes)) {
		oldfile = cl_remove_head(b->holes);
		rbtree_insert(b->files, oldfile);
		rw_lock_write(&oldfile->lock);
	} else {
		filenum = b->listsize;
		b->listsize++;

		newfile->num = filenum;
		rbtree_insert(b->files, newfile);

		rw_lock_write(&newfile->lock);
	}

	/* unlock filelist */
	rw_unlock_write(&b->fileslock);

	if (oldfile == NULL) {
		char buf[FILENAME_MAX];
		int randdir = getrandom(rd, b->numsubdirs + 1);
		int namesize = 0;
		if (randdir == 0)
			namesize = snprintf(buf, FILENAME_MAX, "%s/%s%s%d",
					    b->basedir, b->basename,
					    FILENAME_BASE, filenum);
		else
			namesize = snprintf(buf, FILENAME_MAX,
					    "%s/%s%s%d/%s%s%d", b->basedir,
					    b->basename, SUBDIRNAME_BASE,
					    randdir - 1, b->basename,
					    FILENAME_BASE, filenum);
		if (namesize >= FILENAME_MAX)
			/* !!! do something about this ? */
			printf("warning: filename \"%s\" too long\n", buf);
		newfile->name = ffsb_strdup(buf);
		return newfile;
	} else {
		free(newfile);
		return oldfile;
	}
}

struct ffsb_file *add_dir(struct benchfiles *b, uint64_t size, randdata_t * rd)
{
	struct ffsb_file *newdir, *olddir = NULL;
	int dirnum = 0;

	newdir = ffsb_malloc(sizeof(struct ffsb_file));

	init_rwlock(&newdir->lock);

	/* write lock the filelist, beging critical section */
	rw_lock_write(&b->fileslock);

	/* First check "holes" for a file  */
	if (!cl_empty(b->dholes)) {
		olddir = cl_remove_head(b->dholes);
		rbtree_insert(b->files, olddir);
		rw_lock_write(&olddir->lock);
	} else {
		dirnum = b->numsubdirs;
		b->numsubdirs++;
		printf("dirnum: %d\n", dirnum);
		newdir->num = dirnum;
		rbtree_insert(b->dirs, newdir);

		rw_lock_write(&newdir->lock);
	}

	/* unlock filelist */
	rw_unlock_write(&b->fileslock);

	if (olddir == NULL) {
		char buf[FILENAME_MAX];
		int namesize = 0;
		namesize = snprintf(buf, FILENAME_MAX, "%s/%s%s%d",
				    b->basedir, b->basename,
				    SUBDIRNAME_BASE, dirnum);
		if (namesize >= FILENAME_MAX)
			printf("warning: filename \"%s\" too long\n", buf);
		/* TODO: take action here... */
		newdir->name = ffsb_strdup(buf);
		return newdir;
	} else {
		free(newdir);
		return olddir;
	}
}

/* Private version of above function used only for reusing a
 * fileset.
 */
static struct ffsb_file *add_file_named(struct benchfiles *b, uint64_t size,
					char *name)
{
	struct ffsb_file *newfile = NULL;

	newfile = ffsb_malloc(sizeof(struct ffsb_file));
	memset(newfile, 0, sizeof(struct ffsb_file));
	newfile->name = ffsb_strdup(name);
	newfile->size = size;
	init_rwlock(&newfile->lock);

	/* Write lock the filelist, begin critical section */
	rw_lock_write(&b->fileslock);

	newfile->num = b->listsize;
	b->listsize++;

	/* Add a new file to the rbtree */
	rbtree_insert(b->files, newfile);

	rw_lock_write(&newfile->lock);

	/* Unlock filelist */
	rw_unlock_write(&b->fileslock);

	return newfile;
}

#if 0
static void print_rb_helper(rb_node * cur)
{
	if (cur != NULL) {
		print_rb_helper(cur->left);
		printf("%d ", cur->object->num);
		print_rb_helper(cur->right);
	}
}

static void print_rb(rb_tree * tree)
{
	print_rb_helper(tree->root);
}
#endif

void remove_file(struct benchfiles *b, struct ffsb_file *entry)
{
	rw_lock_write(&b->fileslock);

	rbtree_remove(b->files, entry, NULL);
	/* add node to the cir. list of "holes" */
	cl_insert_tail(b->holes, entry);

	rw_unlock_write(&b->fileslock);
}

static struct ffsb_file *choose_file(struct benchfiles *b, randdata_t * rd)
{
	rb_node *cur = NULL;
	int chosen = 0;
	struct ffsb_file temp;
	temp.num = chosen;

	if (b->listsize == 0) {
		fprintf(stderr, "No more files to operate on,"
			" try making more initial files "
			"or fewer delete operations\n");
		exit(0);
	}

	while (cur == NULL) {
		chosen = getrandom(rd, b->listsize);
		temp.num = chosen;
		cur = rbtree_find(b->files, &temp);
	}
	return cur->object;
}

struct ffsb_file *choose_file_reader(struct benchfiles *bf, randdata_t * rd)
{
	struct ffsb_file *ret;

	rw_lock_read(&bf->fileslock);
	/* If b->holes->count == bf->listsize, all files have been
	 * deleted!
	 */
	assert(bf->holes->count != bf->listsize);

	ret = choose_file(bf, rd);
	if (rw_trylock_read(&ret->lock)) {
		rw_unlock_read(&bf->fileslock);
		return choose_file_reader(bf, rd);
	}

	rw_unlock_read(&bf->fileslock);
	return ret;
}

struct ffsb_file *choose_file_writer(struct benchfiles *bf, randdata_t * rd)
{
	struct ffsb_file *ret;

	rw_lock_read(&bf->fileslock);
	assert(bf->holes->count != bf->listsize);
	ret = choose_file(bf, rd);

	if (rw_trylock_write(&ret->lock)) {
		rw_unlock_read(&bf->fileslock);
		return choose_file_writer(bf, rd);
	}

	rw_unlock_read(&bf->fileslock);
	return ret;
}

void unlock_file_reader(struct ffsb_file *file)
{
	rw_unlock_read(&file->lock);
}

void unlock_file_writer(struct ffsb_file *file)
{
	rw_unlock_write(&file->lock);
}

void rename_file(struct ffsb_file *file)
{
	char *newname = malloc(strlen(file->name) + 2);
	sprintf(newname, "%sa", file->name);
	file->name = newname;
}

int validate_filename(struct benchfiles *bf, char *name)
{
	int retval = -1;
	char fmt_str[FILENAME_MAX];
	if (FILENAME_MAX <= snprintf(fmt_str, FILENAME_MAX,
				     "%s%s%%d", bf->basename, FILENAME_BASE)) {
		printf("filename is too long declaring it invalid\n");
		return -1;
	}

	sscanf(name, fmt_str, &retval);
	return retval;
}

int validate_dirname(struct benchfiles *bf, char *name)
{
	int retval = -1;
	char fmt_str[FILENAME_MAX];
	if (FILENAME_MAX <= snprintf(fmt_str, FILENAME_MAX, "%s%s%%d",
				     bf->basename, SUBDIRNAME_BASE)) {
		printf("dirname is too long declaring it invalid\n");
		return -1;
	}

	sscanf(name, fmt_str, &retval);
	return retval;
}

/* Do all the dirty work of recursing through a directory structure
 * check everything for validitiy and update everything properly.
 * Note it does not check filesizes !!!, it doesn't know anything
 * about them
 */
static int add_dir_to_filelist(struct benchfiles *bf, DIR * subdir,
			       char *subdir_path, fl_validation_func_t vfunc,
			       void *vf_data)
{
	int retval = 0;
	struct dirent *d_ent = NULL;

	while ((d_ent = readdir(subdir)) != NULL) {
		DIR *tmp = NULL;
		char filename_buf[FILENAME_MAX * 2];

		if (FILENAME_MAX < snprintf(filename_buf, FILENAME_MAX, "%s/%s",
					    subdir_path, d_ent->d_name)) {
			printf("filename \"%s\" too long aborting\n",
			       filename_buf);
			return -1;
		}
		tmp = opendir(filename_buf);
		if (tmp == NULL) {
			struct ffsb_file *ffsb_file = NULL;

			if (validate_filename(bf, d_ent->d_name) < 0) {
				printf("filename \"%s\" is invalid aborting\n",
				       d_ent->d_name);
				return -1;
			}
			/* Verify size/other attributes via callback  */
			if (vfunc(bf, filename_buf, vf_data)) {
				printf("filename \"%s\" didn't pass "
				       "validation\n", d_ent->d_name);
				return -1;
			}
			/* Add file to data structure */
			ffsb_file =
			    add_file_named(bf, ffsb_get_filesize(filename_buf),
					   filename_buf);
			unlock_file_writer(ffsb_file);
		} else {
			/* Check for the usual suspects and skip them */
			if ((0 == strcmp(".", d_ent->d_name)) ||
			    (0 == strcmp("..", d_ent->d_name))) {
				closedir(tmp);
				continue;
			}
			if (validate_dirname(bf, d_ent->d_name) < 0) {
				printf("dirname \"%s\" is invalid aborting\n",
				       d_ent->d_name);
				closedir(tmp);
				return -1;
			}
			if (vfunc(bf, filename_buf, vf_data)) {
				printf("dir \"%s\" didn't pass validation\n",
				       d_ent->d_name);
				closedir(tmp);
				return -1;
			}
			/* Update filelist */
			bf->numsubdirs++;

			/* recurse */
			retval += add_dir_to_filelist(bf, tmp, filename_buf,
						      vfunc, vf_data);

			/* clean up */
			closedir(tmp);
		}
	}
	return retval;
}

int grab_old_fileset(struct benchfiles *bf, char *basename,
		     fl_validation_func_t vfunc, void *vfunc_data)
{
	int retval = 0;
	char buf[FILENAME_MAX * 2];
	DIR *lc_dir = NULL;

	if (FILENAME_MAX < snprintf(buf, FILENAME_MAX, "%s", bf->basedir)) {
		printf("filename \"%s\" is too long aborting\n", buf);
		return -1;
	}

	lc_dir = opendir(buf);
	if (lc_dir == NULL) {
		perror("opendir");
		return -1;
	}

	retval = add_dir_to_filelist(bf, lc_dir, buf, vfunc, vfunc_data);

	closedir(lc_dir);
	return retval;
}

/* Get the number of files */
uint32_t get_listsize(struct benchfiles * bf)
{
	return bf->listsize;
}

/* Get the number of subdirectories */
uint32_t get_numsubdirs(struct benchfiles * bf)
{
	return bf->numsubdirs;
}