/*
 *   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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>

#include "ffsb_fs.h"
#include "util.h"
#include "fh.h"

/* First zero out struct, set num_dirs, and strdups basedir */
void init_ffsb_fs(ffsb_fs_t * fs, char *basedir, uint32_t num_data_dirs,
		  uint32_t numstartfiles, unsigned flags)
{
	memset(fs, 0, sizeof(ffsb_fs_t));
	fs->basedir = ffsb_strdup(basedir);
	fs->num_dirs = num_data_dirs;
	fs->num_start_files = numstartfiles;
	fs->flags = flags;
	fs->create_blocksize = FFSB_FS_DEFAULT_CREATE_BLOCKSIZE;
	fs->age_blocksize = FFSB_FS_DEFAULT_AGE_BLOCKSIZE;
	fs->age_fs = 0;
}

/*
 * Does not remove files/dirs on disk, only frees up data
 * structures
*/
void destroy_ffsb_fs(ffsb_fs_t * fs)
{
	free(fs->basedir);
	destroy_filelist(&fs->files);
	destroy_filelist(&fs->fill);
	destroy_filelist(&fs->meta);
}

void clone_ffsb_fs(ffsb_fs_t * target, ffsb_fs_t * orig)
{
	target->basedir = orig->basedir;
	target->flags = orig->flags;

	/* !!!! hackish, write a filelist_clone() function later */
	memcpy(&target->files, &orig->files, sizeof(orig->files));
	memcpy(&target->fill, &orig->fill, sizeof(orig->fill));
	memcpy(&target->meta, &orig->meta, sizeof(orig->meta));

	target->num_dirs = orig->num_dirs;
	target->num_start_files = orig->num_start_files;
	target->minfilesize = orig->minfilesize;
	target->maxfilesize = orig->maxfilesize;

	target->start_fsutil = orig->start_fsutil;
	target->desired_fsutil = orig->desired_fsutil;

	target->age_fs = orig->age_fs;
	target->num_age_dirs = orig->num_age_dirs;
	target->aging_tg = orig->aging_tg;

	target->create_blocksize = orig->create_blocksize;
	target->age_blocksize = orig->age_blocksize;

	memcpy(target->op_data, orig->op_data, sizeof(void *) * FFSB_NUMOPS);
}

static void add_files(ffsb_fs_t * fs, struct benchfiles *bf, int num,
		      uint64_t minsize, uint64_t maxsize, unsigned blocksize)
{
	struct ffsb_file *cur;
	int i, fd, condition = 0, has_directio = 0;
	randdata_t rd;
	char *buf = ffsb_malloc(blocksize);
	uint64_t initial_free = getfsutil_size(fs->basedir);

	if (fs_get_directio(fs)) {
		has_directio = 1;
		fs_set_directio(fs, 0);
	}

	assert(blocksize);

	init_random(&rd, 0);

	if (num)
		condition = num;
	else if (fs->init_size) {
		if (getfsutil(fs->basedir) != initial_free ||
		    fs->init_size > (getfsutil_size(fs->basedir) -
				     initial_free))
			condition = 1;
		else
			condition = 0;
	} else if (fs->init_fsutil) {
		if (fs->init_fsutil > getfsutil(fs->basedir))
			condition = 1;
		else
			condition = 0;
	}

	while (condition) {
		uint64_t size;
		if (fs->num_weights) {
			int num = 1 + getrandom(&rd, fs->sum_weights);
			int curop = 0;

			while (fs->size_weights[curop].weight < num) {
				num -= fs->size_weights[curop].weight;
				curop++;
			}
			size = fs->size_weights[curop].size;
		} else
			size = minsize + getllrandom(&rd, maxsize - minsize);

		cur = add_file(bf, size, &rd);
		fd = fhopencreate(cur->name, NULL, fs);
		writefile_helper(fd, size, blocksize, buf, NULL, fs);
		fhclose(fd, NULL, fs);
		unlock_file_writer(cur);

		if (num)
			condition--;
		else if (fs->init_size) {
			if (fs->init_size > getfsutil_size(fs->basedir) -
			    initial_free)
				condition = 1;
			else
				condition = 0;
		} else if (fs->init_fsutil) {
			if (fs->init_fsutil > getfsutil(fs->basedir))
				condition = 1;
			else
				condition = 0;
		}

	}
	free(buf);
	if (has_directio)
		fs_set_directio(fs, 1);
}

static void age_fs(ffsb_fs_t * fs, double utilization);
static ffsb_fs_t *construct_new_fileset(ffsb_fs_t * fs);
static ffsb_fs_t *check_existing_fileset(ffsb_fs_t * fs);

void *construct_ffsb_fs(void *data)
{
	ffsb_fs_t *fs = (ffsb_fs_t *) data;
	ffsb_fs_t *ret = NULL;

	if (fs_get_reuse_fs(fs)) {
		printf("checking existing fs: %s\n", fs->basedir);
		ret = check_existing_fileset(fs);
		if (ret == NULL) {
			printf("recreating new fileset\n");
			ret = construct_new_fileset(fs);
		}
	} else {
		printf("creating new fileset %s\n", fs->basedir);
		ret = construct_new_fileset(fs);
	}
	if (ret == NULL) {
		printf("fs setup on %s failed\n", fs->basedir);
		exit(1);
	}
	return ret;
}

static int verify_file(struct benchfiles *bf, char *fname, void *fs_ptr)
{
	ffsb_fs_t *fs = (ffsb_fs_t *) fs_ptr;
	uint64_t minsize = fs->minfilesize;
	uint64_t maxsize = fs->maxfilesize;
	uint64_t filesize = 0;
	int fd = 0;
	DIR *dirptr = NULL;

	/* If it is a directory and it passed the name verification we
	 * don't need to do anything here
	 */
	dirptr = opendir(fname);
	if (dirptr) {
		closedir(dirptr);
		return 0;
	}

	fd = open(fname, O_RDONLY);
	/* If we can't open it for read we're done */
	if (fd < 0) {
		printf("verify_file: error opening %s for readonly\n", fname);
		perror(fname);
		return 1;
	}
	close(fd);
	filesize = ffsb_get_filesize(fname);

	if (filesize < minsize || filesize > maxsize) {
		printf("size %llu bytes for file %s is invalid\n",
		       filesize, fname);
		return 1;
	}

	return 0;
}

/* Record the number of files and directorys there are supposed to be
 * grab (check and build the structures) the regular data fileset then
 * check to make sure the number of directories and files in that
 * filelist matches up.  Then grab the meta filelist and verify that
 * the meta filelist is empty.  Set up the filelist for fill (aging)
 * and setup the ops for the benchmark.
*/
static ffsb_fs_t *check_existing_fileset(ffsb_fs_t * fs)
{
	char buf[FILENAME_MAX * 3];
	int retval = 0;
	uint32_t num_dirs = fs->num_dirs;
	uint32_t num_files = fs->num_start_files;

	if (fs->age_fs) {
		printf("Aging and reusing the fileset are mutually "
		       "exclusive\n");
		printf("aborting\n");
		return NULL;
	}

	/* Set up bench/age dir */
	if (FILENAME_MAX <=
	    snprintf(buf, FILENAME_MAX, "%s/%s", fs->basedir, FILES_BASE)) {
		printf("pathname \"%s\" is too long, aborting\n", buf);
		return NULL;
	}

	/* Make a "dummy" filelist that has numsubdirs set to 0 and
	 * numstartfiles set to 0
	 */
	init_filelist(&fs->files, buf, FILES_BASE, 0, 0);

	retval = grab_old_fileset(&fs->files, buf, verify_file, fs);

	if (retval)
		return NULL;

	if ((get_listsize(&fs->files) != num_files) ||
	    (get_numsubdirs(&fs->files) != num_dirs)) {
		printf("check_existing_fileset: number of files (%u)"
		       " or directorys (%u) don't match up\n",
		       get_listsize(&fs->files), get_numsubdirs(&fs->files));
		destroy_filelist(&fs->files);
		return NULL;
	}

	if (FILENAME_MAX <=
	    snprintf(buf, FILENAME_MAX, "%s/%s", fs->basedir, META_BASE)) {
		printf("pathname \"%s\" is too long, aborting\n", buf);
		return NULL;
	}

	init_filelist(&fs->meta, buf, META_BASE, 0, 1);
	retval = grab_old_fileset(&fs->meta, buf, verify_file, fs);

	if (retval) {
		destroy_filelist(&fs->files);
		return NULL;
	}

	if ((get_listsize(&fs->meta) != 0) || (get_numsubdirs(&fs->meta) != 0)) {
		printf("check_existing_fileset: meta directory isn't empty\n"
		       "aborting\n");
		destroy_filelist(&fs->files);
		destroy_filelist(&fs->meta);
		return NULL;
	}

	/* Even though we won't use it, we still need to be consistent
	 * here.
	 */
	init_filelist(&fs->fill, buf, AGE_BASE, 0, 0);

	/* Have to do this or everything else could break. */
	ops_setup_bench(fs);

	return fs;
}

/*
 *  clean up fs, "rm -rf data meta"
 *  record utilization
 *  set up the dirs: files, meta
 *  age filesystem
 *  have ffsb_ops setup their data
 *  create starting files in files
 */
static ffsb_fs_t *construct_new_fileset(ffsb_fs_t * fs)
{
	char buf[FILENAME_MAX * 3];

	/* TODO: Convert this quick and dirty rm -rf to a "real"
	 * programmatic version, that doesn't rely on the rm command.
	 */
	if (FILENAME_MAX * 3 <= snprintf(buf, FILENAME_MAX * 3,
					 "rm -rf %s/data %s/meta",
					 fs->basedir, fs->basedir)) {
		printf("pathname too long for command \"%s\"\n", buf);
		return NULL;
	}

	if (ffsb_system(buf) < 0) {
		perror(buf);
		return NULL;
	}

	fs->start_fsutil = getfsutil(fs->basedir);

	/* Set up bench/age dir */
	if (FILENAME_MAX <=
	    snprintf(buf, FILENAME_MAX, "%s/%s", fs->basedir, FILES_BASE)) {
		printf("pathname \"%s\" is too long, aborting\n", buf);
		return NULL;
	}

	ffsb_mkdir(buf);

	/* Regular files and aging share this directory */
	init_filelist(&fs->files, buf, FILES_BASE, fs->num_dirs, 1);
	init_filelist(&fs->fill, buf, AGE_BASE, fs->num_age_dirs, 1);

	/* Set up meta dir */
	snprintf(buf, FILENAME_MAX, "%s/%s", fs->basedir, META_BASE);

	ffsb_mkdir(buf);

	init_filelist(&fs->meta, buf, META_BASE, 0, 1);

	/* Do aging */
	if (fs->age_fs)
		age_fs(fs, fs->desired_fsutil);

	/* Call back into ops, set for benchmark */
	ops_setup_bench(fs);

	/* Create initial fileset */
	add_files(fs, &fs->files, fs->num_start_files, fs->minfilesize,
		  fs->maxfilesize, fs->create_blocksize);
	return fs;
}

struct poll_data {
	ffsb_fs_t *fs;
	double util;
};

static int fs_get_util(void *data)
{
	struct poll_data *pd = (struct poll_data *)data;
	double fsutil = getfsutil(pd->fs->basedir);

	if (fsutil >= pd->util)
		return 1;

	return 0;
}

static void age_fs(ffsb_fs_t * fs, double utilization)
{
	ffsb_barrier_t barrier;
	pthread_t thread;
	struct poll_data pdata;
	ffsb_tg_t *tg = fs_get_aging_tg(fs);
	tg_run_params_t params;
	ffsb_config_t fc;

	printf("aging fs %s from %.2lf to %.2lf\n", fs->basedir,
	       fs->start_fsutil, utilization);
	ffsb_barrier_init(&barrier, tg_get_numthreads(tg));

	init_ffsb_config_1fs(&fc, fs, tg);

	pdata.fs = fs;
	pdata.util = utilization;

	params.tg = tg;
	params.poll_fn = fs_get_util;
	params.poll_data = &pdata;
	params.wait_time = 1;
	params.fc = &fc;

	params.tg_barrier = NULL;
	params.thread_barrier = &barrier;

	/* Call back into ops, setup for aging */
	ops_setup_age(fs);

	/* Throw in some files to start off, so there's something */
	add_files(fs, &fs->fill, 10, 0, 0, fs->age_blocksize);

	pthread_create(&thread, NULL, tg_run, &params);
	pthread_join(thread, NULL);
}

void fs_set_create_blocksize(ffsb_fs_t * fs, uint32_t blocksize)
{
	fs->create_blocksize = blocksize;
}

void fs_set_age_blocksize(ffsb_fs_t * fs, uint32_t blocksize)
{
	fs->age_blocksize = blocksize;
}

uint32_t fs_get_create_blocksize(ffsb_fs_t * fs)
{
	return fs->create_blocksize;
}

uint32_t fs_get_age_blocksize(ffsb_fs_t * fs)
{
	return fs->age_blocksize;
}

char *fs_get_basedir(ffsb_fs_t * fs)
{
	return fs->basedir;
}

uint32_t fs_get_numstartfiles(ffsb_fs_t * fs)
{
	return fs->num_start_files;
}

uint32_t fs_get_numdirs(ffsb_fs_t * fs)
{
	return fs->num_dirs;
}

int fs_get_libcio(ffsb_fs_t * fs)
{
	return fs->flags & FFSB_FS_LIBCIO;
}

void fs_set_libcio(ffsb_fs_t * fs, int lio)
{
	if (lio)
		fs->flags |= FFSB_FS_LIBCIO;
	else
		fs->flags &= ~0 & ~FFSB_FS_LIBCIO;
}

int fs_get_directio(ffsb_fs_t * fs)
{
	return fs->flags & FFSB_FS_DIRECTIO;
}

void fs_set_directio(ffsb_fs_t * fs, int dio)
{
	if (dio)
		fs->flags |= FFSB_FS_DIRECTIO;
	else
		fs->flags &= ~0 & ~FFSB_FS_DIRECTIO;
}

int fs_get_alignio(ffsb_fs_t * fs)
{
	return fs->flags & FFSB_FS_ALIGNIO4K;
}

void fs_set_alignio(ffsb_fs_t * fs, int aio)
{
	if (aio)
		fs->flags |= FFSB_FS_ALIGNIO4K;
	else
		fs->flags &= ~0 & ~FFSB_FS_ALIGNIO4K;
}

int fs_get_reuse_fs(ffsb_fs_t * fs)
{
	return fs->flags & FFSB_FS_REUSE_FS;
}

void fs_set_reuse_fs(ffsb_fs_t * fs, int rfs)
{
	if (rfs)
		fs->flags |= FFSB_FS_REUSE_FS;
	else
		fs->flags &= ~0 & ~FFSB_FS_REUSE_FS;
}

struct benchfiles *fs_get_datafiles(ffsb_fs_t * fs)
{
	return &fs->files;
}

struct benchfiles *fs_get_metafiles(ffsb_fs_t * fs)
{
	return &fs->meta;
}

struct benchfiles *fs_get_agefiles(ffsb_fs_t * fs)
{
	return &fs->fill;
}

void fs_set_aging_tg(ffsb_fs_t * fs, struct ffsb_tg *tg, double util)
{
	fs->aging_tg = tg;
	fs->age_fs = 1;
	fs->desired_fsutil = util;
}

struct ffsb_tg *fs_get_aging_tg(ffsb_fs_t * fs)
{
	return fs->aging_tg;
}

int fs_get_agefs(ffsb_fs_t * fs)
{
	return fs->age_fs;
}

/* TODO: Implement this!!!*/
void fs_set_num_age_dirs(ffsb_fs_t * fs, uint32_t numdirs)
{
	fs->num_age_dirs = numdirs;
}

void fs_set_opdata(ffsb_fs_t * fs, void *data, unsigned opnum)
{
	fs->op_data[opnum] = data;
}

void *fs_get_opdata(ffsb_fs_t * fs, unsigned opnum)
{
	return fs->op_data[opnum];
}

void fs_set_min_filesize(ffsb_fs_t * fs, uint64_t size)
{
	fs->minfilesize = size;
}

void fs_set_max_filesize(ffsb_fs_t * fs, uint64_t size)
{
	fs->maxfilesize = size;
}

uint64_t fs_get_min_filesize(ffsb_fs_t * fs)
{
	return fs->minfilesize;
}

uint64_t fs_get_max_filesize(ffsb_fs_t * fs)
{
	return fs->maxfilesize;
}

double fs_get_desired_fsutil(ffsb_fs_t * fs)
{
	return fs->desired_fsutil;
}

void fs_print_config(ffsb_fs_t * fs)
{
	char buf[256];

	printf("FileSystem %s\n", fs->basedir);
	printf("==========\n");
	printf("\t num_dirs         = %u\n", fs->num_dirs);
	printf("\t starting files   = %u\n", fs->num_start_files);
	printf("\t\n");
	if (fs->num_weights) {
		int i;
		printf("\t Fileset weight:\n");
		for (i = 0; i < fs->num_weights; i++)
			printf("\t\t %12llu (%6s) -> %u (%.2f\%)\n",
			       fs->size_weights[i].size,
			       ffsb_printsize(buf, fs->size_weights[i].size,
					      256), fs->size_weights[i].weight,
			       ((float)fs->size_weights[i].weight /
				(float)fs->sum_weights) * 100);
	} else {
		printf("\t min file size    = %llu\t(%s)\n", fs->minfilesize,
		       ffsb_printsize(buf, fs->minfilesize, 256));
		printf("\t max file size    = %llu\t(%s)\n", fs->maxfilesize,
		       ffsb_printsize(buf, fs->maxfilesize, 256));
	}
	printf("\t directio         = %s\n", (fs->flags & FFSB_FS_DIRECTIO) ?
	       "on" : "off");
	printf("\t alignedio        = %s\n", (fs->flags & FFSB_FS_ALIGNIO4K) ?
	       "on" : "off");
	printf("\t bufferedio       = %s\n", (fs->flags & FFSB_FS_LIBCIO) ?
	       "on" : "off");
	printf("\t\n");
	printf("\t aging is %s\n", (fs->age_fs) ? "on" : "off");
	printf("\t current utilization = %.2f\%\n",
	       getfsutil(fs->basedir) * 100);
	if (fs->age_fs) {
		printf("\t desired utilization = %.2lf%\n",
		       fs->desired_fsutil * 100);
		printf("\t \n");
		tg_print_config_aging(fs->aging_tg, fs->basedir);
	}
	printf("\t\n");
}

int fs_needs_stats(ffsb_fs_t * fs, syscall_t sys)
{
	return (fs != NULL) ? (int)fs->fsd.config : 0;
}

void fs_add_stat(ffsb_fs_t * fs, syscall_t sys, uint32_t val)
{
	if (fs)
		ffsb_add_data(&fs->fsd, sys, val);
}