#include "presentity.h"
#include "pa_mod.h"
#include "ptime.h"
#include "notify.h"
#include "async_auth.h"
#include "tuple.h"
#include "pres_notes.h"
#include "extension_elements.h"

static void process_watchers(presentity_t* _p, int *changed)
{
	watcher_t *next, *w;
	int presentity_changed;
	int notify;
	
	/* !!! "changed" is not initialized here it is only set if change
	 * in presentity occurs */
	
	presentity_changed = _p->flags & PFLAG_PRESENCE_CHANGED;

	w = _p->first_watcher;
	while (w) {
		/* changes status of expired watcher */
		if (w->expires <= act_time) {
			LOG(L_DBG, "Expired watcher %.*s\n", w->uri.len, w->uri.s);
			w->expires = 0;
			set_watcher_terminated_status(w);
			_p->flags |= PFLAG_WATCHERINFO_CHANGED;
			w->flags |= WFLAG_SUBSCRIPTION_CHANGED;
			if (changed) *changed = 1;
		}

		/* send NOTIFY if needed */
		notify = 0;
		if ((w->flags & WFLAG_SUBSCRIPTION_CHANGED)) {
			notify = 1;
			if (changed) *changed = 1; /* ??? */
		}
		if (presentity_changed && is_watcher_authorized(w)) notify = 1;
		if (notify) send_notify(_p, w);
		w->flags &= ~WFLAG_SUBSCRIPTION_CHANGED;
		
		if (is_watcher_terminated(w)) {
			next = w->next;
			remove_watcher(_p, w);
			free_watcher(w);
			w = next;
			if (changed) *changed = 1;
		}
		else w = w->next;
	}
}

static void process_winfo_watchers(presentity_t* _p, int *changed)
{
	watcher_t *next, *w;
	int notify;
	
	/* !!! "changed" is not initialized here it is only set if change
	 * in presentity occurs */
	
	w = _p->first_winfo_watcher;
	while (w) {
		/* changes status of expired watcher */
		if (w->expires <= act_time) {
			LOG(L_DBG, "Expired watcher %.*s\n", w->uri.len, w->uri.s);
			w->expires = 0;
			set_watcher_terminated_status(w);
			w->flags |= WFLAG_SUBSCRIPTION_CHANGED;
			if (changed) *changed = 1;
		}

		/* send NOTIFY if needed */
		notify = 0;
		if ((w->flags & WFLAG_SUBSCRIPTION_CHANGED)) {
			notify = 1;
			if (changed) *changed = 1; /* ??? */
		}
		if ((_p->flags & PFLAG_WATCHERINFO_CHANGED) && 
			is_watcher_authorized(w)) notify = 1;
		if (notify) send_notify(_p, w);
		w->flags &= ~WFLAG_SUBSCRIPTION_CHANGED;
		
		if (is_watcher_terminated(w)) {
			next = w->next;
			remove_watcher(_p, w);
			free_watcher(w);
			w = next;
			if (changed) *changed = 1;
		}
		else w = w->next;
	}
}

/* static void mark_expired_tuples(presentity_t *_p, int *changed)
{
	presence_tuple_t *t;

	t = _p->tuples;
	while (t) {	
		if (t->expires < act_time) {
			t->state = PS_OFFLINE;
			if (changed) *changed = 1;
			_p->flags |= PFLAG_PRESENCE_CHANGED;
		}
		t = t->next;
	}
}*/

static void remove_expired_tuples(presentity_t *_p, int *changed)
{
	presence_tuple_t *t, *n;

	t = (presence_tuple_t*)_p->data.first_tuple;
	while (t) {
		n = (presence_tuple_t *)t->data.next;
		if (t->expires < act_time) {
			DBG("Expiring tuple %.*s\n", t->data.contact.len, t->data.contact.s);
			remove_presence_tuple(_p, t);
			free_presence_tuple(t);
			if (changed) *changed = 1;
			_p->flags |= PFLAG_PRESENCE_CHANGED;
		}
		t = n;
	}
}

static void remove_expired_notes(presentity_t *_p)
{
	pa_presence_note_t *n, *nn;

	n = (pa_presence_note_t*)_p->data.first_note;
	while (n) {
		nn = (pa_presence_note_t *)n->data.next;
		if (n->expires < act_time) {
			DBG("Expiring note %.*s\n", FMT_STR(n->data.value));
			remove_pres_note(_p, n);
			_p->flags |= PFLAG_PRESENCE_CHANGED;
		}
		n = nn;
	}
}

static void remove_expired_extension_elements(presentity_t *_p)
{
	pa_extension_element_t *n, *nn;

	n = (pa_extension_element_t *)_p->data.first_unknown_element;
	while (n) {
		nn = (pa_extension_element_t *)n->data.next;
		if (n->expires < act_time) {
			DBG("Expiring person element %.*s\n", FMT_STR(n->dbid));
			remove_extension_element(_p, n);
			_p->flags |= PFLAG_PRESENCE_CHANGED;
		}
		n = nn;
	}
}

static inline int refresh_auth_rules(presentity_t *p)
{
	/* TODO reload authorization rules if needed */
	if ((p->auth_rules_refresh_time > 0) && 
			(p->auth_rules_refresh_time <= act_time)) {
/*		INFO("refreshing auth rules\n"); */
		ask_auth_rules(p); /* it will run next time if fails now */
		p->auth_rules_refresh_time = act_time + auth_rules_refresh_time;
	}
	return 0;
}

static void process_tuple_change(presentity_t *p, tuple_change_info_t *info)
{
	presence_tuple_t *tuple = NULL;
	basic_tuple_status_t orig;
	time_t e;

	DBG("processing tuple change message: %.*s, %.*s, %d\n",
			FMT_STR(info->user), FMT_STR(info->contact), info->state);

	if (is_str_empty(&info->contact)) {
		/* error - registered tuples need contact address */
		ERR("invalid registered tuple (empty contact)\n");
		return;
	}
	
	if (info->state == presence_tuple_closed) {
		e = act_time + 2 * timer_interval;
	}
	else {
		e = INT_MAX; /* act_time + default_expires; */
		/* hack - re-registrations don't call the callback */
	}
	
	/* Find only registered (not published) tuple - don't overwrite
	 * published information! */
	if (find_registered_presence_tuple(&info->contact, p, &tuple) != 0) {
		/* not found -> create new tuple */
		new_presence_tuple(&info->contact, e, &tuple, 0, NULL, NULL, NULL);
		if (!tuple) return; /* error */
		
		tuple->data.status.basic = info->state;
		add_presence_tuple(p, tuple);
		p->flags |= PFLAG_PRESENCE_CHANGED;
	}
	else {
		/* tuple found -> update */
		orig = tuple->data.status.basic;
		tuple->data.status.basic = info->state;
		tuple->expires = e;
		db_update_presence_tuple(p, tuple, 0);
			
		if (orig != tuple->data.status.basic) p->flags |= PFLAG_PRESENCE_CHANGED;
	}
}

static int process_qsa_message(presentity_t *p, client_notify_info_t *info)
{
	TRACE("received QSA notification for presentity %.*s\n", FMT_STR(p->data.uri));

	/* TODO: handle it as publish for special tuple (but handle merging 
	 * from multiple QSA sources in any way) */
	
	return 0;
}

static void process_presentity_messages(presentity_t *p)
{
	mq_message_t *msg;
	tuple_change_info_t *info;
	client_notify_info_t *qsa_info;

	while ((msg = pop_message(&p->mq)) != NULL) {

		/* FIXME: ugly data type detection */
		if (msg->destroy_function == (destroy_function_f)free_tuple_change_info_content) {
			info = (tuple_change_info_t*)get_message_data(msg);
			if (info) process_tuple_change(p, info);
		}
		else {
			/* QSA message */
			qsa_info = (client_notify_info_t *)get_message_data(msg);
			if (qsa_info) process_qsa_message(p, qsa_info);
		}
			
		free_message(msg);
	}
}


int timer_presentity(presentity_t* _p)
{
	int old_flags;
	int presentity_changed;

	PROF_START(pa_timer_presentity)
	old_flags = _p->flags;

	/* reload authorization rules if needed */
	refresh_auth_rules(_p);
	
	process_presentity_messages(_p);
	
	remove_expired_tuples(_p, NULL);
	
	remove_expired_notes(_p);
	remove_expired_extension_elements(_p);
	
	/* notify watchers and remove expired */
	process_watchers(_p, NULL);	
	/* notify winfo watchers and remove expired */
	process_winfo_watchers(_p, NULL); 
	
	/* notify internal watchers */
	presentity_changed = _p->flags & PFLAG_PRESENCE_CHANGED;
	if (presentity_changed) {
		/* DBG("presentity %.*s changed\n", _p->uri.len, _p->uri.s); */
		notify_qsa_watchers(_p);
	}

	/* clear presentity "change" flags */
	_p->flags &= ~(PFLAG_PRESENCE_CHANGED | PFLAG_WATCHERINFO_CHANGED);
	
	/* update DB record if something changed - USELESS */
/*	if (changed) {
		db_update_presentity(_p);
	} 
*/	
	PROF_STOP(pa_timer_presentity)
	return 0;
}