#include "sip-uas-transaction.h"
#include "sip-transport.h"
#include "sip-internal.h"

int sip_uas_link_transaction(struct sip_agent_t* sip, struct sip_uas_transaction_t* t);
int sip_uas_unlink_transaction(struct sip_agent_t* sip, struct sip_uas_transaction_t* t);

struct sip_uas_transaction_t* sip_uas_transaction_create(struct sip_agent_t* sip, const struct sip_message_t* req, const struct sip_dialog_t* dialog, void* param)
{
	struct sip_uas_transaction_t* t;
	t = (struct sip_uas_transaction_t*)calloc(1, sizeof(*t));
	if (NULL == t) return NULL;

	t->reply = sip_message_create(SIP_MESSAGE_REPLY);
	if (0 != sip_message_init3(t->reply, req, dialog))
	{
		sip_message_destroy(t->reply);
		free(t);
		return NULL;
	}

	t->ref = 1; // for agent uac link, don't destory it
	t->agent = sip;
	t->initparam = param;
	LIST_INIT_HEAD(&t->link);
	locker_create(&t->locker);
	t->status = SIP_UAS_TRANSACTION_INIT;

	// 17.1.1.1 Overview of INVITE Transaction (p125)
	// For unreliable transports (such as UDP), the client transaction retransmits 
	// requests at an interval that starts at T1 seconds and doubles after every retransmission.
	// 17.1.2.1 Formal Description (p130)
	// For unreliable transports, requests are retransmitted at an interval which starts at T1 and doubles until it hits T2.
	t->t2 = sip_message_isinvite(req) ? (64 * T1) : T2;

	// Life cycle: from create -> destroy
	sip_uas_link_transaction(sip, t);
	sip_uas_transaction_timeout(t, TIMER_H); // trying timeout
	atomic_increment32(&s_gc.uas);
	return t;
}

int sip_uas_transaction_release(struct sip_uas_transaction_t* t)
{
	assert(t->ref > 0);
	if (0 != atomic_decrement32(&t->ref))
		return 0;

	assert(0 == t->ref);
	assert(NULL == t->timerg);
	assert(NULL == t->timerh);
	assert(NULL == t->timerij);
	assert(t->link.next == t->link.prev);// unlink on termernate

	// MUST: destroy t->reply after sip_uas_del_transaction
	//sip_message_destroy((struct sip_message_t*)t->req);
    
    if(t->ondestroy)
    {
        t->ondestroy(t->ondestroyparam);
        t->ondestroy = NULL;
    }

	// MUST unlink before reply destroy
	//sip_uas_unlink_transaction(t->agent, t);

	if (t->reply)
    {
		sip_message_destroy(t->reply);
        t->reply = NULL;
    }

    if(t->dialog)
    {
        sip_dialog_release(t->dialog);
        t->dialog = NULL;
    }
    
	locker_destroy(&t->locker);
	free(t);
	atomic_decrement32(&s_gc.uas);
	return 0;
}

int sip_uas_transaction_addref(struct sip_uas_transaction_t* t)
{
	int r;
	r = atomic_increment32(&t->ref);
	assert(r > 1);
	return r;
}

//int sip_uas_transaction_destroy(struct sip_uas_transaction_t* t)
//{
//	// unlink from uas
//	sip_uas_del_transaction(t->uas, t);
//
//	return sip_uas_transaction_release(t);
//}

int sip_uas_transaction_handler(struct sip_uas_transaction_t* t, const struct sip_message_t* req, void* param)
{
	//assert(t->param == t->initparam);
	if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_CANCEL))
	{
		return sip_uas_oncancel(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_BYE))
	{
		return sip_uas_onbye(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_PRACK))
	{
		return sip_uas_onprack(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_UPDATE))
	{
		return sip_uas_onupdate(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_INFO))
	{
		return sip_uas_oninfo(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_REGISTER))
	{
		return sip_uas_onregister(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_OPTIONS))
	{
		return sip_uas_onoptions(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_SUBSCRIBE))
	{
		return sip_uas_onsubscribe(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_NOTIFY))
	{
		return sip_uas_onnotify(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_PUBLISH))
	{
		return sip_uas_onpublish(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_REFER))
	{
		return sip_uas_onrefer(t, req, param);
	}
	else if (0 == cstrcasecmp(&req->u.c.method, SIP_METHOD_MESSAGE))
	{
		return t->handler->onmessage ? t->handler->onmessage(param, req, t, req->payload, req->size) : 0;
	}
	else
	{
		// 8.2.1 Method Inspection (p46)
		return sip_uas_reply(t, 405/*Method Not Allowed*/, NULL, 0, param);
	}
}

int sip_uas_transaction_dosend(struct sip_uas_transaction_t* t, void* param)
{
	const struct sip_via_t *via;

	assert(t->size > 0);

	// 18.2.2 Sending Responses (p146)
	// The server transport uses the value of the top Via header field in
	// order to determine where to send a response.
	
	// If the host portion of the "sent-by" parameter
	// contains a domain name, or if it contains an IP address that differs
	// from the packet source address, the server MUST add a "received"
	// parameter to that Via header field value.
	// Via: SIP/2.0/UDP bobspc.biloxi.com:5060;received=192.0.2.4

	via = sip_vias_get(&t->reply->vias, 0);
	if (!via) return -1; // invalid via

	return t->handler->send(param, &via->protocol, &via->host, &via->received, via->rport, t->data, t->size);
}

int sip_uas_transaction_terminated(struct sip_uas_transaction_t* t)
{
	t->status = SIP_UAS_TRANSACTION_TERMINATED;

	sip_uas_stop_timer(t->agent, t, &t->timerh);
	sip_uas_stop_timer(t->agent, t, &t->timerg);
	sip_uas_stop_timer(t->agent, t, &t->timerij);

	sip_uas_unlink_transaction(t->agent, t);
	return 0;
}

void sip_uas_transaction_ontimeout(void* usrptr)
{
	char ptr[256];
	struct cstring_t id;
	struct sip_uas_transaction_t* t;
	t = (struct sip_uas_transaction_t*)usrptr;
	locker_lock(&t->locker);
	sip_uas_stop_timer(t->agent, t, &t->timerh); // hijack free timer only, don't release transaction

	if (t->status < SIP_UAS_TRANSACTION_CONFIRMED)
	{
		sip_uas_transaction_terminated(t);

		// TODO:
		// If a UAS generates a 2xx response and never receives an ACK, it
		// SHOULD generate a BYE to terminate the dialog.

		// 8.1.3.1 Transaction Layer Errors (p42)
		if (t->dialog)
		{
			sip_dialog_id(&id, t->dialog, ptr, sizeof(ptr));
			t->handler->onack(t->initparam, NULL, t, (t->dialog && t->dialog->state == DIALOG_CONFIRMED) ? t->dialog : NULL, (t->dialog && t->dialog->state == DIALOG_CONFIRMED) ? &id : NULL, 408/*Invite Timeout*/, NULL, 0);
		}
	}

	locker_unlock(&t->locker);
	sip_uas_transaction_release(t);
}

static void sip_uas_transaction_onterminated(void* usrptr)
{
	struct sip_uas_transaction_t* t;
	t = (struct sip_uas_transaction_t*)usrptr;

	locker_lock(&t->locker);
	sip_uas_stop_timer(t->agent, t, &t->timerij); // hijack free timer only, don't release transaction
	if(SIP_UAS_TRANSACTION_TERMINATED != t->status)
		sip_uas_transaction_terminated(t);
	locker_unlock(&t->locker);
	sip_uas_transaction_release(t);
}

// trying + proceeding timeout
int sip_uas_transaction_timeout(struct sip_uas_transaction_t* t, int timeout)
{
	// try stop timer H
	assert(t->status <= SIP_UAS_TRANSACTION_CONFIRMED);
	sip_uas_stop_timer(t->agent, t, &t->timerh);

	// restart timer H
	assert(NULL == t->timerh);
	t->timerh = sip_uas_start_timer(t->agent, t, timeout, sip_uas_transaction_ontimeout);
	assert(t->timerh);
	return 0;
}

// wait for network cache data
int sip_uas_transaction_timewait(struct sip_uas_transaction_t* t, int timeout)
{
	if (SIP_UAS_TRANSACTION_TERMINATED == t->status)
		return 0;

	sip_uas_stop_timer(t->agent, t, &t->timerh);
	sip_uas_stop_timer(t->agent, t, &t->timerg);

	assert(NULL == t->timerij);
	t->timerij = sip_uas_start_timer(t->agent, t, timeout, sip_uas_transaction_onterminated);
	return t->timerij ? 0 : -1;
}
