/*
    Copyright (C) 2005-2009  Michel de Boer <michel@twinklephone.com>

    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.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "subscription.h"

#include "dialog.h"
#include "line.h"
#include "log.h"
#include "phone_user.h"
#include "audits/memman.h"
#include "parser/hdr_event.h"

extern t_event_queue	*evq_trans_mgr;
extern t_event_queue	*evq_timekeeper;
extern t_timekeeper	*timekeeper;

string t_subscription_state2str(t_subscription_state state) {
	switch (state) {
	case SS_NULL:		return "SS_NULL";
	case SS_ESTABLISHED:	return "SS_ESTABLISHED";
	case SS_UNSUBSCRIBING:	return "SS_UNSUBSCRIBING";
	case SS_UNSUBSCRIBED:	return "SS_UNSUBSCRIBED";
	case SS_TERMINATED:	return "SS_TERMINATED";
	}
	
	return "UNKNOWN";
}

/////////////
// PROTECTED
/////////////

void t_subscription::log_event() const {
	log_file->write_raw("Event:    ");
	log_file->write_raw(event_type);
	log_file->write_endl();
	log_file->write_raw("Event id: ");
	log_file->write_raw(event_id);
	log_file->write_endl();
}

void t_subscription::remove_client_request(t_client_request **cr) {
	if ((*cr)->dec_ref_count() == 0) {
		MEMMAN_DELETE(*cr);
		delete *cr;
	}

	*cr = NULL;
}

t_request *t_subscription::create_subscribe(unsigned long expires) const {
	// RFC 3265 3.1.4
	t_request *r = dialog->create_request(SUBSCRIBE);
	r->hdr_expires.set_time(expires);
	r->hdr_event.set_event_type(event_type);
	if (event_id.size() > 0) r->hdr_event.set_id(event_id);
	
	// Re-calculate the destination as the event type may
	// influence the route to be taken.
	// The destination has been calculated already at the
	// dialog level.
	r->calc_destinations(*user_config);

	return r;
}

t_request *t_subscription::create_notify(const string &sub_state,
		const string &reason) const
{
	// RFC 3265 3.2.2
	t_request *r = dialog->create_request(NOTIFY);
	r->hdr_event.set_event_type(event_type);
	if (event_id.size() > 0) r->hdr_event.set_id(event_id);
	r->hdr_subscription_state.set_substate(sub_state);

	// Subscription state specific parameters
	if (sub_state == SUBSTATE_ACTIVE || sub_state == SUBSTATE_PENDING) {
		// Add expires parameter with remaining time
		if (id_subscription_timeout) {
			long remaining = timekeeper->
				get_remaining_time(id_subscription_timeout);
			r->hdr_subscription_state.set_expires(remaining / 1000);
		}
	} else if (sub_state == SUBSTATE_TERMINATED) {
		// Add reason parameter
		if (reason.size() > 0) {
			r->hdr_subscription_state.set_reason(reason);
		}
	}

	return r;
}

void t_subscription::send_request(t_user *user_config, t_request *r, t_tuid tuid) const {
	evq_trans_mgr->push_user(user_config, (t_sip_message *)r, tuid, 0);
}

void t_subscription::send_response(t_user *user_config, t_response *r, 
		t_tuid tuid, t_tid tid) const 
{
	evq_trans_mgr->push_user(user_config, (t_sip_message *)r, tuid, tid);
}

void t_subscription::start_timer(t_subscribe_timer timer, long duration) {
	t_tmr_subscribe		*t;
	t_object_id		oid_line = 0;

	switch(timer) {
	case STMR_SUBSCRIPTION:
		if (dynamic_cast<t_dialog *>(dialog) != NULL) {
			oid_line = dynamic_cast<t_dialog *>(dialog)->get_line()->get_object_id();
		}
		t = new t_tmr_subscribe(duration, timer, oid_line,
				dialog->get_object_id(), event_type, event_id);
		MEMMAN_NEW(t);
		id_subscription_timeout = t->get_object_id();
		break;
	default:
		assert(false);
	}

	evq_timekeeper->push_start_timer(t);
	MEMMAN_DELETE(t);
	delete t;
}

void t_subscription::stop_timer(t_subscribe_timer timer) {
	unsigned short	*id;

	switch(timer) {
	case STMR_SUBSCRIPTION:
		id = &id_subscription_timeout;
		break;
	default:
		assert(false);
	}

	if (*id != 0) evq_timekeeper->push_stop_timer(*id);
	*id = 0;
}

//////////
// PUBLIC
//////////

t_subscription::t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role,
			const string &_event_type) 
{
	dialog = _dialog;
	
	user_config = dialog->get_user();
	assert(user_config);
	
	role = _role;
	state = SS_NULL;
	resubscribe_after = 0;
	may_resubscribe = false;
	pending = true;
	id_subscription_timeout = 0;
	req_out = NULL;
	event_type = _event_type;
	auto_refresh = true;
	subscription_expiry = 3600;
	default_duration = 3600;
}

t_subscription::t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role,
			const string &_event_type, const string &_event_id)
{
	dialog = _dialog;
	
	user_config = dialog->get_user();
	assert(user_config);
	
	role = _role;
	state = SS_NULL;
	resubscribe_after = 0;
	may_resubscribe = false;
	pending = true;
	id_subscription_timeout = 0;
	req_out = NULL;
	event_type = _event_type;
	event_id = _event_id;
	auto_refresh = true;
	subscription_expiry = 3600;
	default_duration = 3600;
}

t_subscription::~t_subscription() {
	if (req_out) remove_client_request(&req_out);
	if (id_subscription_timeout) stop_timer(STMR_SUBSCRIPTION);

	// Cleanup list of unsent NOTIFY messages
	while (!queue_notify.empty()) {
		t_request *r = queue_notify.front();
		queue_notify.pop();
		MEMMAN_DELETE(r);
		delete r;
	}
}

t_subscription_role t_subscription::get_role(void) const {
	return role;
}

t_subscription_state t_subscription::get_state(void) const {
	return state;
}

string t_subscription::get_reason_termination(void) const {
	return reason_termination;
}

unsigned long t_subscription::get_resubscribe_after(void) const {
	return resubscribe_after;
}

bool t_subscription::get_may_resubscribe(void) const {
	return may_resubscribe;
}

string t_subscription::get_event_type(void) const {
	return event_type;
}

string t_subscription::get_event_id(void) const {
	return event_id;
}

unsigned long t_subscription::get_expiry(void) const {
	return subscription_expiry;
}

bool t_subscription::recv_subscribe(t_request *r, t_tuid tuid, t_tid tid) {
	if (role != SR_NOTIFIER) {
		// Reject a SUBSCRIBE coming in for a SUBSCRIBER
		// TODO: is this ok??
		log_file->write_header("t_subscription::recv_subscribe", LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("SUBSCRIBER receives SUBSCRIBE.\n");
		log_event();
		log_file->write_endl();
		log_file->write_raw(r->encode());
		log_file->write_endl();
		log_file->write_footer();
		
		t_response *resp = r->create_response(R_603_DECLINE);
		send_response(user_config, resp, 0, tid);
		MEMMAN_DELETE(resp);
		delete resp;
		return true;
	}

	// If the subscription is already in the terminated state
	// then a SUBSCRIBE is not allowed anymore.
	if (state == SS_TERMINATED) {
		t_response *resp = r->create_response(R_481_TRANSACTION_NOT_EXIST,
			REASON_481_SUBSCRIPTION_NOT_EXIST);
		send_response(user_config, resp, 0, tid);
		MEMMAN_DELETE(resp);
		delete resp;
		return true;
	}

	if (state == SS_NULL) state = SS_ESTABLISHED;

	// If there is no expires header, then the implementation of
	// the event specific package must deal with this subscribe
	if (!r->hdr_expires.is_populated()) {
		return false;
	}

	// An expiry time of 0 is an unsubscribe
	if (r->hdr_expires.time == 0) {
		stop_timer(STMR_SUBSCRIPTION);
		state = SS_TERMINATED;
		return false;
	}

	// Check if the requested expiry is not too small
	if (r->hdr_expires.time < MIN_DUR_SUBSCRIPTION) {
		t_response *resp = r->create_response(
					R_423_INTERVAL_TOO_BRIEF);
		resp->hdr_min_expires.set_time(MIN_DUR_SUBSCRIPTION);
		send_response(user_config, resp, 0, tid);
		MEMMAN_DELETE(resp);
		delete resp;
		return true;
	}

	// Restart subscription timer
	stop_timer(STMR_SUBSCRIPTION);
	start_timer(STMR_SUBSCRIPTION, r->hdr_expires.time * 1000);
	return false;
}

bool t_subscription::recv_notify(t_request *r, t_tuid tuid, t_tid tid) {
	if (role != SR_SUBSCRIBER) {
		// Reject a NOTIFY coming in for a NOTIFIER
		// TODO: is this ok??
		log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("NOTIFIER receives NOTIFY.\n");
		log_event();
		log_file->write_endl();
		log_file->write_raw(r->encode());
		log_file->write_endl();
		log_file->write_footer();
		
		t_response *resp = r->create_response(R_603_DECLINE);
		send_response(user_config, resp, 0, tid);
		MEMMAN_DELETE(resp);
		delete resp;
		return true;
	}
	
	if (state == SS_NULL) {
		log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("NOTIFY establishes subscription.\n");
		log_event();
		log_file->write_footer();
		
		state = SS_ESTABLISHED;
	}

	if (r->hdr_subscription_state.substate == SUBSTATE_ACTIVE && pending) {
		log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("NOTIFY ends pending state.\n");
		log_event();
		log_file->write_footer();
		
		pending = false;
	}

	if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) {
		stop_timer(STMR_SUBSCRIPTION);
		state = SS_TERMINATED;
		reason_termination = r->hdr_subscription_state.reason;
		resubscribe_after = r->hdr_subscription_state.retry_after;
		
		// RFC 3264 3.2.4
		if (resubscribe_after > 0) {
			may_resubscribe = true;
		} else {
			if (reason_termination == EV_REASON_DEACTIVATED ||
			    reason_termination == EV_REASON_TIMEOUT)
			{
				may_resubscribe = true;
			} else if (reason_termination == EV_REASON_PROBATION ||
			           reason_termination == EV_REASON_GIVEUP)
			{
				may_resubscribe = true;
				resubscribe_after = DUR_RESUBSCRIBE;
			}
		}
		
		log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("NOTIFY terminates subscription.\n");
		log_event();
		log_file->write_raw("Termination reason: ");
		log_file->write_raw(reason_termination);
		log_file->write_endl();
		log_file->write_raw("May resubscribe:    ");
		log_file->write_bool(may_resubscribe);
		log_file->write_endl();
		log_file->write_raw("Resubscribe after:  ");
		log_file->write_raw(resubscribe_after);
		log_file->write_endl();
		log_file->write_footer();
	}

	if (r->hdr_subscription_state.expires > 0 && state == SS_ESTABLISHED) {
		log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("Received NOTIFY on established subscription.\n");
		log_event();
		log_file->write_footer();
		
		unsigned long dur = r->hdr_subscription_state.expires;
		if (auto_refresh) {
			if (!id_subscription_timeout ||
			    timekeeper->get_remaining_time(id_subscription_timeout) >=
			    	dur * 1000)
			{
				// Adjust timer to expiry duration indicated
				// in NOTIFY.
				dur -= dur / 10;
				stop_timer(STMR_SUBSCRIPTION);
				start_timer(STMR_SUBSCRIPTION, dur * 1000);
			}
		} else {
			if (!id_subscription_timeout ||
			    timekeeper->get_remaining_time(id_subscription_timeout) <
			    	dur * 1000)
			{
				// Adjust timer to expiry duration indicated
				// in NOTIFY.
				dur += dur / 10;
				stop_timer(STMR_SUBSCRIPTION);
				start_timer(STMR_SUBSCRIPTION, dur * 1000);
			}
		}
	}

	return false;
}

bool t_subscription::recv_response(t_response *r, t_tuid tuid, t_tid tid) {
	switch (r->hdr_cseq.method) {
	case NOTIFY:
		return recv_notify_response(r, tuid, tid);
		break;
	case SUBSCRIBE:
		return recv_subscribe_response(r, tuid, tid);
		break;
	default:
		break;
	}
	
	return false;
}

bool t_subscription::recv_notify_response(t_response *r, t_tuid tuid, t_tid tid) {
	// Discard response if it does not match a pending request
	if (!req_out) return true;
	if (r->hdr_cseq.method != req_out->get_request()->method) return true;

	// Ignore provisional responses
	if (r->is_provisional()) return true;
	
	// Successful response
	if (r->is_success()) {
		if (req_out->get_request()->hdr_subscription_state.substate ==
				SUBSTATE_TERMINATED)
		{
			// This is a 2XX respsone on a NOTIFY that terminates the
			// subscription.
			state = SS_TERMINATED;
			
			log_file->write_header("t_subscription::recv_notify_response", 
				LOG_NORMAL, LOG_DEBUG);
			log_file->write_raw("Subscription terminated by 2XX NOTIFY.\n");
			log_file->write_raw(r->code);
			log_file->write_raw(" " + r->reason + "\n");
			log_event();
			log_file->write_footer();
		}
		remove_client_request(&req_out);
	} else {
		// RFC 3265 3.2.2
		// NOTIFY failed, terminate subscription
		remove_client_request(&req_out);
		state = SS_TERMINATED;
		
		log_file->write_header("t_subscription::recv_notify_response", 
			LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("Subscription terminated by NOTIFY failure response.\n");
		log_file->write_raw(r->code);
		log_file->write_raw(" " + r->reason + "\n");
		log_event();
		log_file->write_footer();
		return true;
	}

	// If there is a NOTIFY in the queue, then send it
	if (!queue_notify.empty()) {
		log_file->write_header("t_subscription::recv_notify_response", 
			LOG_NORMAL, LOG_DEBUG);
		log_file->write_raw("Get NOTIFY from queue.");
		log_event();
		log_file->write_footer();
	
		t_request *notify = queue_notify.front();
		queue_notify.pop();
		req_out = new t_client_request(user_config, notify,0);
		MEMMAN_NEW(req_out);
		send_request(user_config, notify, req_out->get_tuid());
		MEMMAN_DELETE(notify);
		delete notify;
	}

	return true;
}

bool t_subscription::recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) {
	// Discard response if it does not match a pending request
	if (!req_out) return true;
	if (r->hdr_cseq.method != req_out->get_request()->method) return true;

	// Ignore provisional responses
	if (r->is_provisional()) return true;
	
	// Successful response
	if (r->is_success()) {
		if (state == SS_NULL) {
			log_file->write_header("t_subscription::recv_subscribe_response", 
				LOG_NORMAL, LOG_DEBUG);
			log_file->write_raw("Subscription established by 2XX SUBSCRIBE.\n");
			log_file->write_raw(r->code);
			log_file->write_raw(" " + r->reason + "\n");
			log_event();
			log_file->write_footer();
			
			state = SS_ESTABLISHED;
		}
		
		// RFC 3265 7.1, 7.2 says that the Expires header is mandatory
		// in a 2XX response. Some SIP servers do not include this
		// however. To interoperate with such servers, assume that
		// the granted expiry time equals the requested expiry time.
		if (!r->hdr_expires.is_populated()) {
			r->hdr_expires.set_time(
				req_out->get_request()->hdr_expires.time);
				
			log_file->write_header(
				"t_subscription::recv_subscribe_response",
				LOG_NORMAL, LOG_WARNING);
			log_file->write_raw("Mandatory Expires header missing.\n");
			log_file->write_raw("Assuming expires = ");
			log_file->write_raw(r->hdr_expires.time);
			log_file->write_endl();
			log_event();
			log_file->write_footer();
		}

		// If some faulty server sends a non-zero expiry time in
		// a response on an unsubscribe request, then ignore
		// the expiry time.
		if (r->hdr_expires.time == 0 || state == SS_UNSUBSCRIBING) {
			// Unsubscription succeeded.
			stop_timer(STMR_SUBSCRIPTION);
			
			// Start the unsubscribe guard. If the triggered
			// NOTIFY is never received, this guard assures, that
			// the subscription will be cleaned up at timeout.
			start_timer(STMR_SUBSCRIPTION, DUR_UNSUBSCRIBE_GUARD);

			// The subscription will only
			// terminate after a NOTIFY triggered by the unsubscribe
			// has been received or this guard timer expires.
			state = SS_UNSUBSCRIBED;
			auto_refresh = false;
			
			log_file->write_header("t_subscription::recv_subscribe_response", 
				LOG_NORMAL, LOG_DEBUG);
			log_file->write_raw("Unsubcription successful.\n");
			log_event();
			log_file->write_footer();
		} else {
			// Start/refresh subscribe timer
			stop_timer(STMR_SUBSCRIPTION);
			unsigned long dur = r->hdr_expires.time;
			if (auto_refresh) {
				dur -= dur / 10;
			} else {
				dur += dur / 10;
			}
			start_timer(STMR_SUBSCRIPTION, dur * 1000);
		}

		remove_client_request(&req_out);
		return true;
	}

	// RFC 3265 3.1.4.1
	// SUBSCRIBE failed, terminate subscription
	// NOTE: redirection and authentication responses should have
	// been handled already (eg. on line or phone user level).
	remove_client_request(&req_out);
	state = SS_TERMINATED;
	
	log_file->write_header("t_subscription::recv_subscribe_response", 
		LOG_NORMAL, LOG_DEBUG);
	log_file->write_raw("Subscription terminated by SUBSCRIBE failure response.\n");
	log_file->write_raw(r->code);
	log_file->write_raw(" " + r->reason + "\n");
	log_event();
	log_file->write_footer();

	return true;
}

bool t_subscription::timeout(t_subscribe_timer timer) {
	switch (timer) {
	case STMR_SUBSCRIPTION:
		id_subscription_timeout = 0;
		
		if (role == SR_SUBSCRIBER) {
			log_file->write_header("t_subcription::timeout");
			log_file->write_raw("Subscriber timed out.\n");
			log_event();
			log_file->write_footer();

			if (auto_refresh) {
				// Refresh subscription
				refresh_subscribe();
			} else {
				// The cause for timeout may be temporary.
				// Allow resubscription to overcome a transient problem.
				may_resubscribe = true;
				state = SS_TERMINATED;
			}

			return true;
		}
		break;
	default:
		assert(false);
	}

	return false;
}

bool t_subscription::match_timer(t_subscribe_timer timer, t_object_id id_timer) const {
	return id_timer == id_subscription_timeout;
}

bool t_subscription::match(t_request *r) const {
	if (!r->hdr_event.is_populated()) return false;
	
	// If the subscription has been terminated, then do not match
	// any request.
	if (state == SS_TERMINATED) return false;

	return (r->hdr_event.event_type == event_type &&
	        r->hdr_event.id == event_id);
}

bool t_subscription::is_pending(void) const {
	return pending;
}

void t_subscription::subscribe(unsigned long expires) {
	if (req_out) {
		// Delete previous outgoing request
		MEMMAN_DELETE(req_out);
		delete req_out;
	}
	
	if (expires > 0) {
		subscription_expiry = expires;
	} else {
		subscription_expiry = default_duration;
	}
	
	t_request *r = create_subscribe(subscription_expiry);
	req_out = new t_client_request(user_config, r ,0);
	MEMMAN_NEW(req_out);
	send_request(user_config, r, req_out->get_tuid());
	MEMMAN_DELETE(r);
	delete r;
}

void t_subscription::unsubscribe(void) {
	if (state != SS_ESTABLISHED) {
		state = SS_TERMINATED;
		return;
	}
	
	if (req_out) {
		// Delete previous outgoing request
		MEMMAN_DELETE(req_out);
		delete req_out;
	}

	stop_timer(STMR_SUBSCRIPTION);
	
	t_request *r = create_subscribe(0);
	req_out = new t_client_request(user_config, r ,0);
	MEMMAN_NEW(req_out);
	send_request(user_config, r, req_out->get_tuid());
	MEMMAN_DELETE(r);
	delete r;
	
	// NOTE: the subscription is only ended when the response is received.
	state = SS_UNSUBSCRIBING;
}

void t_subscription::refresh_subscribe(void) {
	if (state != SS_ESTABLISHED) return;
	stop_timer(STMR_SUBSCRIPTION);
	subscribe(subscription_expiry);
}
