/*
 * 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.
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

#include "list.h"
#include "rbtree.h"
#include "jhash.h"
#include "coherency.h"
#include "fserver.h"

struct coherency_entry
{
	struct rb_node		coherency_entry;

	struct list_head	user_list;

	unsigned int		hash;
	unsigned int		len;
};

static int coherency_cmp(struct coherency_entry *old, unsigned int hash, unsigned int len)
{
	if (old->hash > hash)
		return -1;
	if (old->hash < hash)
		return 1;
	
	if (old->len > len)
		return -1;
	if (old->len < len)
		return 1;

	return 0;
}

static struct coherency_entry *coherency_entry_search(struct rb_root *root, unsigned int hash, unsigned int len)
{
	struct rb_node *n = root->rb_node;
	struct coherency_entry *e;
	int cmp;

	while (n) {
		e = rb_entry(n, struct coherency_entry, coherency_entry);

		cmp = coherency_cmp(e, hash, len);
		if (cmp < 0)
			n = n->rb_left;
		else if (cmp > 0)
			n = n->rb_right;
		else
			return e;
	}
	return NULL;
}

static struct coherency_entry *coherency_entry_add(struct rb_root *root, struct coherency_entry *a)
{
	struct rb_node **n = &root->rb_node, *parent = NULL;
	struct coherency_entry *e;
	int cmp;

	while (*n) {
		parent = *n;

		e = rb_entry(parent, struct coherency_entry, coherency_entry);

		cmp = coherency_cmp(e, a->hash, a->len);
		if (cmp < 0)
			n = &parent->rb_left;
		else if (cmp > 0)
			n = &parent->rb_right;
		else
			return e;
	}
	
	rb_link_node(&a->coherency_entry, parent, n);
	rb_insert_color(&a->coherency_entry, root);

	return NULL;
}

static void coherency_entry_del(struct rb_root *root, struct coherency_entry *a)
{
	if (a)
		rb_erase(&a->coherency_entry, root);
}

static int coherency_remove_client_from_dir(struct coherency_entry *e, struct fserver_client *c)
{
	struct coherency_user *u, *tmp;
	int err = -ENOENT;

	list_for_each_entry_safe(u, tmp, &e->user_list, user_entry) {
		if (u->client == c) {
			list_del(&u->user_entry);
			free(u);
			err = 0;
			break;
		}
	}

	if (list_empty(&e->user_list)) {
		coherency_entry_del(&c->f->coherency_root, e);
		free(e);
	}

	return err;
}

int coherency_remove_object(struct fserver_client *c, char *path, unsigned int len)
{
	struct fserver *f = c->f;
	int err;
	struct coherency_entry *e;
	unsigned int hash = jhash(path, len, 0);

	pthread_mutex_lock(&f->coherency_lock);
	e = coherency_entry_search(&f->coherency_root, hash, len);
	if (!e) {
		err = -ENOENT;
		goto err_out_unlock;
	}

	fserver_notify_remove(f, path, len);

	err = coherency_remove_client_from_dir(e, c);
	pthread_mutex_unlock(&f->coherency_lock);

	return 0;

err_out_unlock:
	pthread_mutex_unlock(&f->coherency_lock);
	return err;
}

static int coherency_user_add(struct coherency_entry *e, struct fserver_client *c,
		__u64 id, __u64 ino, int intent)
{
	struct coherency_user *u, *found = NULL;

	list_for_each_entry(u, &e->user_list, user_entry) {
		if ((u->client == c) && (u->ino == ino)) {
			if (u->intent == POHMELFS_READ_LOCK)
				u->intent = intent;
			u->id = id;
			found = u;
			break;
		}
	}

	if (!found) {
		u = malloc(sizeof(struct coherency_user));
		if (!u)
			return -ENOMEM;

		u->client = c;
		u->id = id;
		u->ino = ino;
		u->intent = intent;

		list_add_tail(&u->user_entry, &e->user_list);
	}

	return 0;
}

int coherency_add_object(struct fserver_client *c, char *path, unsigned int len, __u64 id, __u64 ino, int intent)
{
	struct fserver *f = c->f;
	int err = -ENOMEM;
	struct coherency_entry *e;
	unsigned int hash = jhash(path, len, 0);

	pthread_mutex_lock(&f->coherency_lock);
	e = coherency_entry_search(&f->coherency_root, hash, len);
	if (!e) {
		e = malloc(sizeof(struct coherency_entry));
		if (!e)
			goto err_out_unlock;

		INIT_LIST_HEAD(&e->user_list);
		e->len = len;
		e->hash = hash;

		coherency_entry_add(&f->coherency_root, e);
	}

	err = coherency_user_add(e, c, id, ino, intent);
	if (err)
		goto err_out_unlock;

	fserver_notify_add(f, path, len, id, ino);

	pthread_mutex_unlock(&f->coherency_lock);

	ulog("%s: client: %p: added object: '%s', ino: %llu, id: %llu, hash: %x, len: %u, intent: %d.\n",
			__func__, c, path, ino, id, hash, len, intent);

	return 0;

err_out_unlock:
	pthread_mutex_unlock(&f->coherency_lock);
	ulog("%s: client: %p: failed to add object: '%s', ino: %llu, id: %llu, hash: %x, len: %u, intent: %d.\n",
			__func__, c, path, ino, id, hash, len, intent);
	return err;
}

void coherency_remove_client_from_all_dirs(struct fserver_client *c)
{
	struct fserver *f = c->f;
	struct rb_node *rb_node;
	struct coherency_entry *e;

	pthread_mutex_lock(&f->coherency_lock);
	for (rb_node = rb_first(&f->coherency_root); rb_node; ) {
		e = rb_entry(rb_node, struct coherency_entry, coherency_entry);

		rb_node = rb_next(rb_node);

		coherency_remove_client_from_dir(e, c);
	}
	pthread_mutex_unlock(&f->coherency_lock);
}

int coherency_broadcast_hash(struct fserver *f,
		struct fserver_client *self,
		struct fserver_crypto_engine *eng,
		unsigned int hash, unsigned int len,
		struct netfs_cmd *cmd,
		int (* callback)(struct coherency_user *u, struct netfs_cmd *cmd))
{
	struct fserver_client *c;
	struct coherency_entry *e;
	struct coherency_user *u;
	int err = 0, sent = 0;
	struct netfs_cmd orig = *cmd;

	uloga("%s: client: %p, hash: %x, len: %u, ext: %d, cmd: %u ->\n",
			__func__, self, hash, len, cmd->ext, cmd->cmd);

	pthread_mutex_lock(&f->coherency_lock);
	e = coherency_entry_search(&f->coherency_root, hash, len);
	if (!e)
		goto out_unlock;

	list_for_each_entry(u, &e->user_list, user_entry) {
		c = u->client;
		if (c == self)
			continue;

		cmd->id = u->id;
		if (cmd->cmd == NETFS_PAGE_CACHE)
			cmd->id = u->ino;

		err = 0;
		if (callback)
			err = callback(u, cmd);

		uloga("              %s:%d: id: %llu, intent: %d, err: %d\n", fserver_addr_convert(c),
				fserver_port_convert(c), u->id, u->intent, err);

		if (!err) {
			err = fserver_put_client_data_direct(eng, c, cmd);
			if (!err)
				sent++;
		}

		*cmd = orig;
	}

out_unlock:
	pthread_mutex_unlock(&f->coherency_lock);

	if (err < 0)
		sent = err;

	return sent;
}


