#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>

#include "hashtab.h"
#include "strtab.h"

#define MULTIPLIER	31	/* or 37 */

struct kmemprof_strtab {
	struct hashtab hashtab;
	char * buf;
	unsigned long buf_size;
	unsigned long head_pos;
};

/* Per-CPU data */
static struct kmemprof_strtab kmemprof_strtab[NR_CPUS];

static int strtab_size = PAGE_SIZE;
MODULE_PARM(strtab_size, "i");

static struct file_operations strtab_fops;


static int kmemprof_strtab_init (struct kmemprof_strtab *strtab, unsigned long size)
{
	int ret = -ENOMEM;
#define	HASHTAB_RATIO	1
	unsigned long hashtab_size = HASHTAB_RATIO * size;

	memset(strtab, 0, sizeof(*strtab));

	if (!(strtab->buf = vmalloc(size * sizeof(char)))) {
		return ret;
	}
	memset(strtab->buf, 0, size * sizeof(char));
	strtab->buf_size = size;
	strtab->head_pos = 8;	/* 0 is reserved by NULL string */

	if ((ret = hashtab_init (&strtab->hashtab, hashtab_size)) < 0)
		goto fail;

	return 0;

fail:
	vfree(strtab->buf);

	return ret;
}

static void kmemprof_strtab_destroy (struct kmemprof_strtab * strtab)
{
	hashtab_destroy(&strtab->hashtab);
	if (strtab->buf)
		vfree(strtab->buf);
	memset(strtab, 0, sizeof(*strtab));
}

extern struct proc_dir_entry * kmemprof_trace_dir;

int kmemprof_strtab_create(int num_cpus)
{
	int ret, i;
	struct proc_dir_entry * entry;
	char name[100];

#ifdef DEBUG
	BUG_ON(num_cpus > NR_CPUS);
#endif
	for (i = 0; i < num_cpus; i++) {
		struct kmemprof_strtab * strtab = &kmemprof_strtab[i];

		if ((ret = kmemprof_strtab_init(strtab, strtab_size)) < 0) {
			goto fail;
		}
		sprintf(name, "strtab-%d", i);
		if (!(entry = create_proc_entry(name, 0, kmemprof_trace_dir))) {
			ret -ENOMEM;
			goto fail;
		}
		entry->data = strtab;
		entry->proc_fops = &strtab_fops;
	}

	return 0;

fail:
	while (--i >=0) {
		struct kmemprof_strtab * strtab = &kmemprof_strtab[i];

		sprintf(name, "strtab-%d", i);
		remove_proc_entry(name, kmemprof_trace_dir);
		kmemprof_strtab_destroy(strtab);
	}

	return ret;
}

void kmemprof_strtab_remove ()
{
	int i;
	char name[100];

	for (i = 0; i < NR_CPUS; i++) {
		struct kmemprof_strtab * strtab = &kmemprof_strtab[i];

		sprintf(name, "strtab-%d", i);
		remove_proc_entry(name, kmemprof_trace_dir);
		kmemprof_strtab_destroy(strtab);
	}
}

static unsigned long strtab_hash (const char *str)
{
	unsigned long h = 0;
	unsigned char *p;

	for (p = (unsigned char *) str; *p != '\0'; p++)
		h = MULTIPLIER * h + *p;

	return h;
}

/* if there are not any space to store the string, returns 0 */
static int kmemprof_strtab_lookup(struct kmemprof_strtab * strtab, const char * str)
{
	unsigned long h, value, save_flags;
	struct bucket * bucket;
	struct hashtab * hashtab = &strtab->hashtab;

#ifdef DEBUG
	BUG_ON(!str || *str == '\0');
#endif
	h = strtab_hash(str) % hashtab->table_size;

	local_irq_save(save_flags);
	for (bucket = hashtab_entry(hashtab, h);
		bucket != hashtab_null_bucket(hashtab);
		bucket = hashtab_next(hashtab, bucket)) {
		value = hashtab_get_value(hashtab, bucket);
		if (!strcmp(str, &strtab->buf[value]))
			goto out;
	}

	if (strtab->buf_size <= strlen(str) + 1 + strtab->head_pos) {
		value =  0;	/* no more space */
		goto out;
	}

	value = strtab->head_pos;
	strcpy(&strtab->buf[value], str);
	strtab->head_pos += strlen(str) + 1;

	bucket = hashtab_chain(hashtab, h);
	if (bucket == hashtab_null_bucket(hashtab)) {
		value =  0;	/* no more buckets */
		goto out;
	}
	hashtab_set_value(hashtab, bucket, value);

out:
	local_irq_restore(save_flags);

	return value;
}

extern int kmemprof_strtab_store(const char * str)
{
	struct kmemprof_strtab * strtab = &kmemprof_strtab[smp_processor_id()];

	
	return (!strtab->buf_size) ? 0 : kmemprof_strtab_lookup(strtab, str);
}

/** file_operations **/

static int strtab_open (struct inode * inode, struct file * file)
{
	struct proc_dir_entry * de = inode->u.generic_ip;
	struct kmemprof_strtab * strtab = de->data;

	file->private_data = strtab;

	return 0;
}

static ssize_t strtab_read (struct file * file, char * buf, size_t count,
		loff_t * pos)
{
	ssize_t bytes_to_copy;
	struct kmemprof_strtab * strtab = file->private_data;

	bytes_to_copy = strtab->head_pos - *pos;
	bytes_to_copy = min((ssize_t)count, bytes_to_copy);
	if (bytes_to_copy > 0) {
		copy_to_user(buf, &strtab->buf[*pos], bytes_to_copy);
		*pos += bytes_to_copy;

		return bytes_to_copy;
	}

	return 0;
}

static int strtab_release (struct inode * inode, struct file * file)
{
	return 0;
}

static struct file_operations strtab_fops = {
	.open = strtab_open,
	.read = strtab_read,
	.release = strtab_release
};

