/**
 * @file sipsess/reply.c  SIP Session Reply
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <re_types.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_sa.h>
#include <re_list.h>
#include <re_hash.h>
#include <re_fmt.h>
#include <re_uri.h>
#include <re_tmr.h>
#include <re_sip.h>
#include <re_sipsess.h>
#include "sipsess.h"


struct sipsess_reply {
	struct le le;
	struct tmr tmr;
	struct tmr tmrg;
	const struct sip_msg *msg;
	struct mbuf *mb;
	struct sipsess *sess;
	bool awaiting_answer;
	uint32_t seq;
	uint32_t txc;
};


static void destructor(void *arg)
{
	struct sipsess_reply *reply = arg;

	list_unlink(&reply->le);
	tmr_cancel(&reply->tmr);
	tmr_cancel(&reply->tmrg);
	mem_deref((void *)reply->msg);
	mem_deref(reply->mb);
}


static void tmr_handler(void *arg)
{
	struct sipsess_reply *reply = arg;
	struct sipsess *sess = reply->sess;

	mem_deref(reply);

	/* wait for all pending ACKs */
	if (sess->replyl.head)
		return;

	/* we want to send bye */
	sess->established = true;

	if (!sess->terminated)
		sipsess_terminate(sess, ETIMEDOUT, NULL);
	else
		mem_deref(sess);
}


static void retransmit_handler(void *arg)
{
	struct sipsess_reply *reply = arg;

	(void)sip_send(reply->sess->sip, reply->msg->sock, reply->msg->tp,
		       &reply->msg->src, reply->mb);

	reply->txc++;
	tmr_start(&reply->tmrg, MIN(SIP_T1<<reply->txc, SIP_T2),
		  retransmit_handler, reply);
}


int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
		      uint16_t scode, const char *reason, struct mbuf *desc,
		      const char *fmt, va_list *ap)
{
	struct sipsess_reply *reply;
	int err = ENOMEM;

	reply = mem_zalloc(sizeof(*reply), destructor);
	if (!reply)
		goto out;

	list_append(&sess->replyl, &reply->le, reply);
	reply->seq  = msg->cseq.num;
	reply->msg  = mem_ref((void *)msg);
	reply->sess = sess;

	err = sip_treplyf(&sess->st, &reply->mb, sess->sip,
			  msg, true, scode, reason,
			  "Contact: <sip:%s@%J%s>\r\n"
			  "%v"
			  "%s%s%s"
			  "Content-Length: %zu\r\n"
			  "\r\n"
			  "%b",
			  sess->cuser, &msg->dst, sip_transp_param(msg->tp),
			  fmt, ap,
			  desc ? "Content-Type: " : "",
			  desc ? sess->ctype : "",
			  desc ? "\r\n" : "",
			  desc ? mbuf_get_left(desc) : (size_t)0,
			  desc ? mbuf_buf(desc) : NULL,
			  desc ? mbuf_get_left(desc) : (size_t)0);

	if (err)
		goto out;

	tmr_start(&reply->tmr, 64 * SIP_T1, tmr_handler, reply);
	tmr_start(&reply->tmrg, SIP_T1, retransmit_handler, reply);

	if (!mbuf_get_left(msg->mb) && desc) {
		reply->awaiting_answer = true;
		sess->awaiting_answer = true;
	}

 out:
	if (err) {
		sess->st = mem_deref(sess->st);
		mem_deref(reply);
	}

	return err;
}


static bool cmp_handler(struct le *le, void *arg)
{
	struct sipsess_reply *reply = le->data;
	const struct sip_msg *msg = arg;

	return msg->cseq.num == reply->seq;
}


int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
		      bool *awaiting_answer)
{
	struct sipsess_reply *reply;

	reply = list_ledata(list_apply(&sess->replyl, false, cmp_handler,
				       (void *)msg));
	if (!reply)
		return ENOENT;

	*awaiting_answer = reply->awaiting_answer;

	mem_deref(reply);

	return 0;
}
