/*
 * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 * All rights reserved.
 *
 * 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.
 */

#define _ATFILE_SOURCE

#include <sys/types.h>

#include <fcntl.h>

#include "fserver.h"
#include "coherency.h"
#include "notify.h"

static unsigned int notify_hash(void *data, unsigned int len)
{
	unsigned int i;
	unsigned char *ptr = data;

	ulog("%s: len: %u, data: ", __func__, len);
	for (i=0; i<len; ++i)
		uloga("%02x ", ptr[i]);
	uloga("\n");
	return jhash(data, len, 0);
}

static int notify_read_callback(struct notify_root *r, struct callback *cb,
		struct notify_event *ev)
{
	struct fserver_crypto_engine *e = ev->priv;
	struct fserver *f = cb->data;
	struct netfs_cmd c;

	ulog("%s: file: '%s', pid: %u, id: %llu, ino: %llu.\n", __func__, ev->path, ev->cookie, ev->id, ev->ino);

	c.cmd = NETFS_PAGE_CACHE;
	c.start = 0;
	c.size = 0;
	c.ext = POHMELFS_WRITE_LOCK;
	c.csize = c.cpad = 0;

	return coherency_broadcast_hash(f, NULL, e, ev->hash, ev->len, &c, fserver_flush_writers_callback);
}

static int notify_create_callback(struct notify_root *r, struct callback *c,
		struct notify_event *e)
{
	struct fserver_crypto_engine *ce = e->priv;
	struct fserver *f = c->data;
	char buf[sizeof(struct netfs_cmd) + sizeof(struct netfs_inode_info) + 256];
	struct netfs_cmd *reply = (struct netfs_cmd *)buf;
	struct netfs_inode_info *info = (struct netfs_inode_info *)(reply + 1);
	char *name = (char *)(info + 1);
	int err, len, size;
	unsigned int hash = e->hash, hlen = e->len;
	char *end = e->path;

	err = fserver_fill_stat_inode(AT_FDCWD, e->path, info);
	if (err)
		return err;

	end = strrchr(e->path, '/');
	if (!end)
		return 0;

	if (end > e->path) {
		*end++ = '\0';
		hlen = end - e->path; /* Including 0-byte */
		hash = notify_hash(e->path, hlen);
	}

	ulog("%s: file: '%s', pid: %u, id: %llu, ino: %llu, mode: %o.\n", __func__, end, e->cookie, e->id, e->ino, info->mode);

	len = sprintf(name, "%s", end) + 1;
	size = len + sizeof(struct netfs_inode_info);

	reply->id = e->ino;
	reply->start = 0;
	reply->cmd = NETFS_READDIR;
	reply->cpad = fserver_crypto_pad(ce, size);
	reply->size = size + reply->cpad;
	reply->ext = 0;

	netfs_convert_inode_info(info);

	return coherency_broadcast_hash(f, NULL, ce, hash, hlen, reply, NULL);
}

static int notify_write_callback(struct notify_root *r, struct callback *c,
		struct notify_event *e)
{
	ulog("%s: file: '%s', pid: %u, id: %llu, ino: %llu.\n", __func__, e->path, e->cookie, e->id, e->ino);
	return notify_add_object(r, e->path, e->len, 0, 0);
}

static int notify_delete_callback(struct notify_root *r, struct callback *c,
		struct notify_event *e)
{
	ulog("%s: file: '%s', pid: %u, id: %llu, ino: %llu.\n", __func__, e->path, e->cookie, e->id, e->ino);
	return notify_remove_object(r, e->path, e->len);
}

static struct callback fserver_notify_callbacks[] = {
	{ .mask = READ_EVENT, .callback = notify_read_callback, .data = NULL, },
	//{ .mask = WRITE_EVENT, .callback = notify_write_callback, .data = NULL, },
	//{ .mask = CREATE_EVENT, .callback = notify_create_callback, .data = NULL, },
	{ .mask = DELETE_EVENT, .callback = notify_delete_callback, .data = NULL, },
};

static int fserver_notify_update_private(void *priv, void *data)
{
	struct fserver_crypto_engine *meng = data;
	struct fserver_crypto_engine *e = priv;
	int err;

	if (meng->cipher[0]) {
		err = fserver_init_cipher(e, meng);
		if (err)
			goto err_out_free;
	}

	if (meng->hash[0]) {
		err = fserver_init_hash(e, meng);
		if (err)
			goto err_out_free;
	}

	e->perform_crypto = meng->perform_crypto;
	return 0;

err_out_free:
	return err;
}

int fserver_notify_update(struct fserver *f)
{
	if (f->notify_root)
		return notify_update(f->notify_root, fserver_notify_update_private, &f->eng);
	return 0;
}

static void *fserver_notify_init_private(void *priv __unused)
{
	struct fserver_crypto_engine *e;

	e = malloc(sizeof(struct fserver_crypto_engine));
	if (!e)
		return NULL;

	memset(e, 0, sizeof(struct fserver_crypto_engine));
	return e;
}

static void fserver_notify_cleanup_private(void *priv)
{
	struct fserver_crypto_engine *e = priv;

	if (e)
		free(e);
}

static int fserver_notify_process(struct fserver *f, char *path, unsigned int len, __u64 id, __u64 ino, int add)
{
	int err;
	char *end;

	if (!f->notify_root)
		return 0;

#if 0
	end = strrchr(path, '/');
	if (end > path) {
		*end++ = '\0';
		len -= end - path + 1;
	}
#endif
	if (add)
		err = notify_add_object(f->notify_root, path, len, id, ino);
	else
		err = notify_remove_object(f->notify_root, path, len);

	if (err) {
		ulog("Failed to %s object '%s', id: %llu, inode: %llu, err: %d.\n",
				(add)?"add":"remove", path, id, ino, err);
		return err;
	}
	
	ulog("%s object '%s', id: %llu, inode: %llu, err: %d, hash: %x, len: %u.\n",
		(add)?"Added":"Remove", path, id, ino, err, notify_hash(path, len), len);

	return 0;
}

int fserver_notify_add(struct fserver *f, char *path, unsigned int len, __u64 id, __u64 ino)
{
	return fserver_notify_process(f, path, len, id, ino, 1);
}

int fserver_notify_remove(struct fserver *f, char *path, unsigned int len)
{
	return fserver_notify_process(f, path, len, 0, 0, 0);
}

void *fserver_notify_init(struct fserver *f)
{
	struct notify_root *r;
	int size, i;

	size = sizeof(fserver_notify_callbacks)/sizeof(struct callback);
	for (i=0; i<size; ++i)
		fserver_notify_callbacks[i].data = f;

	r = notify_init(f->workers_per_client, fserver_notify_callbacks, size,
			fserver_notify_init_private, fserver_notify_cleanup_private, &f->eng);
	if (!r) {
		ulog("Failed to initalize notification library.\n");
		return NULL;
	}

	return r;
}

void fserver_notify_exit(void *root)
{
	notify_exit(root);
}
