#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <linux/delay.h>

typedef struct {
	char			*old_file_name;
	int			old_file_name_length;
	char			*new_file_name;
	int			new_file_name_length;
	pid_t			thread_pid;
	int			thread_present;
	wait_queue_head_t	thread_sv;	/*to make the thread sleep on certain condition */
//	wait_queue_head_t	multi_thread_sv[1]; /* in case of multi threading, second thread will sleep until first is awake */
						    /* but will this be possible? a module can be loaded only once!!! */
} thread_struct;

#define prc_file_name "test_proc_file_name"

thread_struct *ts = NULL;

char *file_name = NULL;
int file_name_size = 0;

int my_thread_fun (void *arg) {

	int ret;
	thread_struct *my_thread = (thread_struct *) arg;
	if (!try_module_get(THIS_MODULE)) {
		printk(KERN_INFO "%s:\tunable to get the module\n",__FUNCTION__);
		ret = -EINVAL;
		goto out;
	}
	daemonize("test_thread");
	allow_signal(SIGKILL);
	my_thread->thread_present = 1;
	printk(KERN_INFO "%s:\tthread id = %d name of the old file= %s, name of the new file = %s\n",
				__FUNCTION__, my_thread->thread_pid, my_thread->new_file_name, my_thread->old_file_name);
	for (;;) {
		if (signal_pending(current)) {
			my_thread->thread_pid = 0;
			printk(KERN_INFO "%s:\tgot a signal to commit suicide \n",__FUNCTION__);
			ret = -EINTR;
			goto out;
		}

		if (my_thread->new_file_name == NULL || my_thread->old_file_name == NULL) {
			printk(KERN_INFO "%s:\ttime to sleep as nothing to compare\n",__FUNCTION__);
			interruptible_sleep_on(&my_thread->thread_sv);
		}

		printk(KERN_INFO "%s:\tthread id = %d name of the old file= %s, name of the new file = %s\n",
				__FUNCTION__, my_thread->thread_pid, my_thread->new_file_name, my_thread->old_file_name);
	
		/* adding this continue because the thread may
		 * sleep on the above condition and when it wakes
		 * it needs to check again if the old and new file
		 * name are NULL or not
		 */
		continue;

		if (my_thread->new_file_name != NULL) {
			printk(KERN_INFO "%s:\tname of the new file is %s\n",__FUNCTION__, my_thread->new_file_name);
		}

		if (my_thread->old_file_name != NULL) {
			printk(KERN_INFO "%s:\tname of the old file is %s\n",__FUNCTION__, my_thread->old_file_name);
		}
		
		printk(KERN_INFO "%s:\told file(%s) new file(%s) going for comparision\n",
				__FUNCTION__, my_thread->old_file_name, my_thread->new_file_name);
		ssleep(2);
//		schedule_timeout(HZ);

		if (!memcmp(my_thread->old_file_name, my_thread->new_file_name,my_thread->new_file_name_length)) {
//				(my_thread->old_file_name_length > my_thread->new_file_name_length ?
//				 my_thread->old_file_name_length : my_thread->new_file_name_length))) {
			ssleep(2);
			printk(KERN_INFO "%s:\tname of the new file (%s) is same as old file (%s) sleeping\n",
						__FUNCTION__, my_thread->new_file_name, my_thread->old_file_name);
			interruptible_sleep_on(&my_thread->thread_sv);
		}
		ssleep(2);
		printk(KERN_INFO "%s:\tnew file(%s)\n",__FUNCTION__, my_thread->new_file_name);
//		schedule_timeout(HZ);
		my_thread->old_file_name = my_thread->new_file_name;
		my_thread->old_file_name_length = my_thread->new_file_name_length;
		ssleep(2);
		printk(KERN_INFO "%s:\trounding off\n",__FUNCTION__);
	}
out:
	my_thread->thread_present = 0;
	module_put(THIS_MODULE);
	return ret;
}

int read_proc_file(
	char *buf,
	char **buf_loc,
	off_t offset,
	int buf_len,
	int *eof,
	void *data)
{

	int ret;
	printk(KERN_INFO "%s reading from the proc file %s\n", __FUNCTION__,prc_file_name);
	if (offset > 0) {
		return 0;
	} else {
		memcpy(buf, file_name, file_name_size);
		ret = file_name_size;
	}
	return ret;
}

int write_proc_file (
	struct file *filp,
	const char __user *buf,
	unsigned long len,
	void *data) {

	printk(KERN_INFO "%s:\tlength of file name = %ld\n",__FUNCTION__, len);
	printk(KERN_INFO "%s:\twriting into the proc file\n", __FUNCTION__);
	file_name_size = len;

	file_name = (char *) kmalloc(len, GFP_KERNEL);
	if (copy_from_user(file_name, buf, file_name_size)) {
		return -1;
	}

	/* calling the function to print the page information for this file */
	printk(KERN_INFO "%s:\tname of the file is: %s\n", __FUNCTION__, file_name);
	if (ts && ts->thread_present) {
//		schedule_timeout(HZ);
		ssleep(2);
		printk(KERN_INFO "%s:\ta thread exists and is sleeping. time to wake it and make him do some work\n",
				__FUNCTION__);
		if (ts->old_file_name != NULL) {
			printk(KERN_INFO "old entry exists free the memory it occupies\n");
			kfree(ts->old_file_name);
		}
		printk(KERN_INFO "%s:\tadding the new entries to the daemon structure\n",__FUNCTION__);

//		schedule_timeout(HZ);
		ssleep(2);

		ts->old_file_name = ts->new_file_name;
		ts->old_file_name_length = ts->new_file_name_length;
		ts->new_file_name = file_name;
		ts->new_file_name_length = file_name_size;
		printk(KERN_INFO "%s:\twakkkkkkeeee\n",__FUNCTION__);

//		schedule_timeout(HZ);
		ssleep(2);
		wake_up(&ts->thread_sv);
	}
	return file_name_size;
}

int __init
init_thread_module(void) {

	struct proc_dir_entry   *my_entry;
	ts = (thread_struct *) kmalloc(sizeof(thread_struct), GFP_KERNEL);
	if (ts == NULL) {
		printk(KERN_INFO "%s:\tunable to allocate memory for the thread structure\n",__FUNCTION__);
		return 0;
	}
	ts->new_file_name = NULL;
	ts->old_file_name = NULL;
	init_waitqueue_head(&ts->thread_sv);
	printk(KERN_INFO "%s:\tmodule loaded\n",__FUNCTION__);
	printk(KERN_INFO "%s:\ttrying to initiate a thread\n",__FUNCTION__);

	my_entry = create_proc_entry(prc_file_name, 0644, NULL);
	if (my_entry == NULL) {
		printk(KERN_INFO "%s:\tunable to allocate a proc entry\n",__FUNCTION__);
		return 0;
	}
	my_entry->read_proc = read_proc_file;
	my_entry->write_proc = write_proc_file;
	my_entry->owner = THIS_MODULE;

	ts->thread_pid = kernel_thread(my_thread_fun, (void *) ts,
			(CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD));
	if (ts->thread_pid < 0) {
		printk(KERN_INFO "%s:\tunable to create a thread\n", __FUNCTION__);
	}
	return 0;
}

void __exit
exit_thread_module(void) {

	while (ts->thread_present) {
		/* kill the deamon */
		if (kill_proc(ts->thread_pid, SIGKILL, 1) != 0) {
			printk(KERN_INFO "%s:\tunable to kill the thread. dont know what to do now \n", __FUNCTION__);
		}
	}
	if (ts->new_file_name != NULL)
		kfree(ts->new_file_name);
	if (ts->old_file_name != NULL)
		kfree(ts->old_file_name);
	kfree(ts);
	remove_proc_entry(prc_file_name, NULL);
	printk(KERN_INFO "%s:\ttime to unload the module\n", __FUNCTION__);
	return;
}

module_init(init_thread_module)
module_exit(exit_thread_module)
