/*
 * Copyright (C) 2009 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of kamailio, a free SIP server.
 *
 * kamailio 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
 *
 * kamailio 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


/*!
 * \file
 * \brief Call transfer
 * \ingroup dialog
 * Module: \ref dialog
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/trim.h"
#include "../../core/mem/mem.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/parser/parse_from.h"
#include "../../core/parser/msg_parser.h"

#include "../../modules/tm/tm_load.h"

#include "dlg_req_within.h"
#include "dlg_handlers.h"
#include "dlg_transfer.h"

#define DLG_HOLD_SDP                                                         \
	"v=0\r\no=kamailio-bridge 0 0 IN IP4 0.0.0.0\r\ns=kamailio\r\nc=IN IP4 " \
	"0.0.0.0\r\nt=0 0\r\nm=audio 9 RTP/AVP 8 0\r\na=rtpmap:8 "               \
	"PCMA/8000\r\na=rtpmap:0 PCMU/8000\r\n"
#define DLG_HOLD_SDP_LEN (sizeof(DLG_HOLD_SDP) - 1)

/*
#define DLG_HOLD_CT_HDR "Contact: <sip:kamailio.org:5060>\r\nContent-Type: application/sdp\r\n"
#define DLG_HOLD_CT_HDR_LEN	(sizeof(DLG_HOLD_CT_HDR)-1)
*/

extern str dlg_bridge_controller;
extern str dlg_bridge_contact;

static char *dlg_bridge_hdrs_buf = NULL;
static str dlg_bridge_inv_hdrs = {0};
static str dlg_bridge_ref_hdrs = {0};

int dlg_bridge_init_hdrs(void)
{
	if(dlg_bridge_hdrs_buf != NULL)
		return 0;
	dlg_bridge_hdrs_buf =
			(char *)pkg_malloc((dlg_bridge_contact.len + 46) * sizeof(char));
	if(dlg_bridge_hdrs_buf == NULL) {
		LM_ERR("no more pkg memory\n");
		return -1;
	}
	memcpy(dlg_bridge_hdrs_buf, "Contact: <", 10);
	memcpy(dlg_bridge_hdrs_buf + 10, dlg_bridge_contact.s,
			dlg_bridge_contact.len);
	memcpy(dlg_bridge_hdrs_buf + 10 + dlg_bridge_contact.len,
			">\r\nContent-Type: application/sdp\r\n", 34);
	dlg_bridge_hdrs_buf[dlg_bridge_contact.len + 44] = '\0';
	dlg_bridge_inv_hdrs.s = dlg_bridge_hdrs_buf;
	dlg_bridge_inv_hdrs.len = dlg_bridge_contact.len + 44;
	dlg_bridge_ref_hdrs.s = dlg_bridge_hdrs_buf;
	dlg_bridge_ref_hdrs.len = dlg_bridge_contact.len + 13;
	return 0;
}

void dlg_bridge_destroy_hdrs(void)
{
	if(dlg_bridge_hdrs_buf != NULL)
		pkg_free(dlg_bridge_hdrs_buf);
}

void dlg_transfer_ctx_free(dlg_transfer_ctx_t *dtc)
{
	struct dlg_cell *dlg;

	if(dtc == NULL)
		return;
	if(dtc->from.s != NULL)
		shm_free(dtc->from.s);
	if(dtc->to.s != NULL)
		shm_free(dtc->to.s);

	dlg = dtc->dlg;
	if(dlg != NULL) {
		if(dlg->tag[DLG_CALLER_LEG].s)
			shm_free(dlg->tag[DLG_CALLER_LEG].s);

		if(dlg->tag[DLG_CALLEE_LEG].s)
			shm_free(dlg->tag[DLG_CALLEE_LEG].s);

		if(dlg->cseq[DLG_CALLER_LEG].s)
			shm_free(dlg->cseq[DLG_CALLER_LEG].s);

		if(dlg->cseq[DLG_CALLEE_LEG].s)
			shm_free(dlg->cseq[DLG_CALLEE_LEG].s);

		shm_free(dlg);
	}

	shm_free(dtc);
}

void dlg_refer_tm_callback(struct cell *t, int type, struct tmcb_params *ps)
{
	dlg_transfer_ctx_t *dtc = NULL;
	dlg_t *dialog_info = NULL;
	str met = {"BYE", 3};
	int result;
	struct dlg_cell *dlg;
	uac_req_t uac_r;

	if(ps->param == NULL || *ps->param == 0) {
		LM_DBG("message id not received\n");
		return;
	}
	dtc = *((dlg_transfer_ctx_t **)ps->param);
	if(dtc == NULL)
		return;
	LM_DBG("REFER completed with status %d\n", ps->code);

	/* we send the BYE anyhow */
	dlg = dtc->dlg;
	if((dialog_info = build_dlg_t(dlg, DLG_CALLEE_LEG)) == 0) {
		LM_ERR("failed to create dlg_t\n");
		goto error;
	}

	/* after REFER, the CSeq must be increased */
	dialog_info->loc_seq.value++;

	set_uac_req(&uac_r, &met, NULL, NULL, dialog_info, 0, NULL, NULL);
	result = d_tmb.t_request_within(&uac_r);

	if(result < 0) {
		LM_ERR("failed to send the REFER request\n");
		/* todo: clean-up dtc */
		goto error;
	}

	free_tm_dlg(dialog_info);
	dlg_transfer_ctx_free(dtc);

	LM_DBG("BYE sent\n");
	return;

error:
	dlg_transfer_ctx_free(dtc);
	if(dialog_info)
		free_tm_dlg(dialog_info);
	return;
}

static int dlg_refer_callee(dlg_transfer_ctx_t *dtc)
{
	/*verify direction*/
	dlg_t *dialog_info = NULL;
	str met = {"REFER", 5};
	int result;
	str hdrs;
	struct dlg_cell *dlg;
	uac_req_t uac_r;

	dlg = dtc->dlg;

	if((dialog_info = build_dlg_t(dlg, DLG_CALLEE_LEG)) == 0) {
		LM_ERR("failed to create dlg_t\n");
		goto error;
	}

	hdrs.len = 23 + 2 * CRLF_LEN + dlg_bridge_controller.len + dtc->to.len
			   + dlg_bridge_ref_hdrs.len;
	LM_DBG("sending REFER [%d] <%.*s>\n", hdrs.len, dtc->to.len, dtc->to.s);
	hdrs.s = (char *)pkg_malloc(hdrs.len * sizeof(char));
	if(hdrs.s == NULL)
		goto error;
	memcpy(hdrs.s, "Referred-By: ", 13);
	memcpy(hdrs.s + 13, dlg_bridge_controller.s, dlg_bridge_controller.len);
	memcpy(hdrs.s + 13 + dlg_bridge_controller.len, CRLF, CRLF_LEN);
	memcpy(hdrs.s + 13 + dlg_bridge_controller.len + CRLF_LEN,
			"Refer-To: ", 10);
	memcpy(hdrs.s + 23 + dlg_bridge_controller.len + CRLF_LEN, dtc->to.s,
			dtc->to.len);
	memcpy(hdrs.s + 23 + dlg_bridge_controller.len + CRLF_LEN + dtc->to.len,
			CRLF, CRLF_LEN);
	memcpy(hdrs.s + 23 + dlg_bridge_controller.len + CRLF_LEN + dtc->to.len
					+ CRLF_LEN,
			dlg_bridge_ref_hdrs.s, dlg_bridge_ref_hdrs.len);

	set_uac_req(&uac_r, &met, &hdrs, NULL, dialog_info, TMCB_LOCAL_COMPLETED,
			dlg_refer_tm_callback, (void *)dtc);
	result = d_tmb.t_request_within(&uac_r);

	pkg_free(hdrs.s);
	if(result < 0) {
		LM_ERR("failed to send the REFER request\n");
		/* todo: clean-up dtc */
		goto error;
	}

	free_tm_dlg(dialog_info);

	LM_DBG("REFER sent\n");
	return 0;

error:
	if(dialog_info)
		free_tm_dlg(dialog_info);
	return -1;
}


void dlg_bridge_tm_callback(struct cell *t, int type, struct tmcb_params *ps)
{
	struct sip_msg *msg = NULL;
	dlg_transfer_ctx_t *dtc = NULL;
	struct dlg_cell *dlg = NULL;
	str s;
	str cseq;
	str empty = {"", 0};

	if(ps->param == NULL || *ps->param == 0) {
		LM_DBG("message id not received\n");
		return;
	}
	dtc = *((dlg_transfer_ctx_t **)ps->param);
	if(dtc == NULL)
		return;
	LM_DBG("completed with status %d\n", ps->code);
	if(ps->code >= 300)
		goto error;

	/* 2xx - build dialog/send refer */
	msg = ps->rpl;
	if(parse_headers(msg, HDR_EOH_F, 0) < 0) {
		LM_ERR("failed to parse the reply headers\n");
		goto error;
	}
	if((msg->cseq == NULL || parse_headers(msg, HDR_CSEQ_F, 0) < 0)
			|| msg->cseq == NULL || msg->cseq->parsed == NULL) {
		LM_ERR("bad sip message or missing CSeq hdr :-/\n");
		goto error;
	}
	cseq = (get_cseq(msg))->number;

	if((msg->to == NULL && parse_headers(msg, HDR_TO_F, 0) < 0)
			|| msg->to == NULL) {
		LM_ERR("bad request or missing TO hdr\n");
		goto error;
	}
	if(parse_from_header(msg)) {
		LM_ERR("bad request or missing FROM hdr\n");
		goto error;
	}
	if((msg->callid == NULL && parse_headers(msg, HDR_CALLID_F, 0) < 0)
			|| msg->callid == NULL) {
		LM_ERR("bad request or missing CALLID hdr\n");
		goto error;
	}
	s = msg->callid->body;
	trim(&s);

	/* some sanity checks */
	if(s.len == 0 || get_from(msg)->tag_value.len == 0) {
		LM_ERR("invalid request -> callid (%d) or from TAG (%d) empty\n", s.len,
				get_from(msg)->tag_value.len);
		goto error;
	}

	dlg = build_new_dlg(&s /*callid*/, &(get_from(msg)->uri) /*from uri*/,
			&(get_to(msg)->uri) /*to uri*/,
			&(get_from(msg)->tag_value) /*from_tag*/,
			&(get_to(msg)->uri) /*use to as r-uri*/);
	if(dlg == 0) {
		LM_ERR("failed to create new dialog\n");
		goto error;
	}
	dtc->dlg = dlg;
	if(dlg_set_leg_info(dlg, &(get_from(msg)->tag_value), &empty,
			   &dlg_bridge_controller, &cseq, DLG_CALLER_LEG)
			!= 0) {
		LM_ERR("dlg_set_leg_info failed\n");
		goto error;
	}

	if(populate_leg_info(dlg, msg, t, DLG_CALLEE_LEG, &(get_to(msg)->tag_value))
			!= 0) {
		LM_ERR("could not add further info to the dialog\n");
		shm_free(dlg);
		goto error;
	}

	if(dlg_refer_callee(dtc) != 0)
		goto error;
	return;

error:
	dlg_transfer_ctx_free(dtc);
	return;
}


int dlg_bridge(str *from, str *to, str *op, str *bd)
{
	dlg_transfer_ctx_t *dtc;
	int ret;
	str s_method = {"INVITE", 6};
	str s_body;
	uac_req_t uac_r;

	dtc = (dlg_transfer_ctx_t *)shm_malloc(sizeof(dlg_transfer_ctx_t));
	if(dtc == NULL) {
		LM_ERR("no shm\n");
		return -1;
	}
	memset(dtc, 0, sizeof(dlg_transfer_ctx_t));
	dtc->from.s = (char *)shm_malloc((from->len + 1) * sizeof(char));
	if(dtc->from.s == NULL) {
		LM_ERR("no shm\n");
		shm_free(dtc);
		return -1;
	}
	dtc->to.s = (char *)shm_malloc((to->len + 1) * sizeof(char));
	if(dtc->to.s == NULL) {
		LM_ERR("no shm\n");
		shm_free(dtc->from.s);
		shm_free(dtc);
		return -1;
	}
	memcpy(dtc->from.s, from->s, from->len);
	dtc->from.len = from->len;
	dtc->from.s[dtc->from.len] = '\0';
	memcpy(dtc->to.s, to->s, to->len);
	dtc->to.len = to->len;
	dtc->to.s[dtc->to.len] = '\0';

	LM_DBG("bridge <%.*s> to <%.*s>\n", dtc->from.len, dtc->from.s, dtc->to.len,
			dtc->to.s);
	if(bd != NULL && bd->s != NULL) {
		s_body.s = bd->s;
		s_body.len = bd->len;
	} else {
		s_body.s = DLG_HOLD_SDP;
		s_body.len = DLG_HOLD_SDP_LEN;
	}

	memset(&uac_r, '\0', sizeof(uac_req_t));
	uac_r.method = &s_method;
	uac_r.headers = &dlg_bridge_inv_hdrs;
	uac_r.body = &s_body;
	uac_r.cb_flags = TMCB_LOCAL_COMPLETED;
	uac_r.cb = dlg_bridge_tm_callback;
	uac_r.cbp = (void *)(long)dtc;
	ret = d_tmb.t_request(&uac_r,					/* UAC Req */
			&dtc->from,								/* Request-URI (To) */
			&dtc->from,								/* To */
			&dlg_bridge_controller,					/* From */
			(op != NULL && op->len > 0) ? op : NULL /* Outbound-URI */
	);

	if(ret < 0) {
		dlg_transfer_ctx_free(dtc);
		return -1;
	}
	return 0;
}

int dlg_transfer(struct dlg_cell *dlg, str *to, int side)
{
	dlg_transfer_ctx_t *dtc = NULL;
	struct dlg_cell *ndlg = NULL;
	str from;
	str empty = {"", 0};

	dtc = (dlg_transfer_ctx_t *)shm_malloc(sizeof(dlg_transfer_ctx_t));
	if(dtc == NULL) {
		LM_ERR("no shm\n");
		return -1;
	}
	if(side == DLG_CALLEE_LEG) {
		from = dlg->from_uri;
	} else {
		from = dlg->to_uri;
	}
	memset(dtc, 0, sizeof(dlg_transfer_ctx_t));
	dtc->from.s = (char *)shm_malloc((from.len + 1) * sizeof(char));
	if(dtc->from.s == NULL) {
		LM_ERR("no shm\n");
		shm_free(dtc);
		return -1;
	}
	dtc->to.s = (char *)shm_malloc((to->len + 1) * sizeof(char));
	if(dtc->to.s == NULL) {
		LM_ERR("no shm\n");
		shm_free(dtc->from.s);
		shm_free(dtc);
		return -1;
	}
	memcpy(dtc->from.s, from.s, from.len);
	dtc->from.len = from.len;
	dtc->from.s[dtc->from.len] = '\0';
	memcpy(dtc->to.s, to->s, to->len);
	dtc->to.len = to->len;
	dtc->to.s[dtc->to.len] = '\0';

	if(side == DLG_CALLER_LEG)
		ndlg = build_new_dlg(&dlg->callid /*callid*/, &dlg->to_uri /*from uri*/,
				&dlg->from_uri /*to uri*/, &dlg->tag[side] /*from_tag*/,
				&dlg->req_uri /*req uri */);
	else
		ndlg = build_new_dlg(&dlg->callid /*callid*/,
				&dlg->from_uri /*from uri*/, &dlg->to_uri /*to uri*/,
				&dlg->tag[side] /*from_tag*/, &dlg->req_uri /*req uri */);
	if(ndlg == 0) {
		LM_ERR("failed to create new dialog\n");
		goto error;
	}
	dtc->dlg = ndlg;
	if(dlg_set_leg_info(ndlg, &dlg->tag[side], &empty, &dlg->contact[side],
			   &dlg->cseq[side], DLG_CALLER_LEG)
			!= 0) {
		LM_ERR("dlg_set_leg_info failed for caller\n");
		goto error;
	}
	if(side == DLG_CALLEE_LEG)
		side = DLG_CALLER_LEG;
	else
		side = DLG_CALLEE_LEG;
	if(dlg_set_leg_info(ndlg, &dlg->tag[side], &dlg->route_set[side],
			   &dlg->contact[side], &dlg->cseq[side], DLG_CALLEE_LEG)
			!= 0) {
		LM_ERR("dlg_set_leg_info failed for caller\n");
		goto error;
	}

	if(dlg_refer_callee(dtc) != 0)
		goto error;
	return 0;

error:
	dlg_transfer_ctx_free(dtc);
	return -1;
}
