/*
 * mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2011-2012, Barracuda Networks Inc.
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is Barracuda Networks Inc.
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Mathieu Rene <mrene@avgs.ca>
 * Seven Du <dujinfang@gmail.com>
 *
 * rtmp.c -- RTMP Signalling functions
 *
 */

#include "mod_rtmp.h"

/* AMF */
#include "amf0.h"
#include "io.h"
#include "types.h"

/* RTMP_INVOKE_FUNCTION is a macro that expands to:
switch_status_t function(rtmp_session_t *rsession, rtmp_state_t *state, int amfnumber, int transaction_id, int argc, amf0_data *argv[])
*/

RTMP_INVOKE_FUNCTION(rtmp_i_connect)
{
	amf0_data *object1 = amf0_object_new(), *object2 = amf0_object_new(), *params = argv[0], *d;
	const char *s;

	if ((d = amf0_object_get(params, "app")) && (s = amf0_get_string(d))) {
		rsession->app = switch_core_strdup(rsession->pool, s);
	}

	if ((d = amf0_object_get(params, "flashVer")) && (s = amf0_get_string(d))) {
		rsession->flashVer = switch_core_strdup(rsession->pool, s);
	}
	if ((d = amf0_object_get(params, "swfUrl")) && (s = amf0_get_string(d))) {
		rsession->swfUrl = switch_core_strdup(rsession->pool, s);
	}
	if ((d = amf0_object_get(params, "tcUrl")) && (s = amf0_get_string(d))) {
		rsession->tcUrl = switch_core_strdup(rsession->pool, s);
	}
	if ((d = amf0_object_get(params, "pageUrl")) && (s = amf0_get_string(d))) {
		rsession->pageUrl = switch_core_strdup(rsession->pool, s);
	}

	if ((d = amf0_object_get(params, "capabilities"))) {
		rsession->capabilities = amf0_get_number(d);
	}
	if ((d = amf0_object_get(params, "audioCodecs"))) {
		rsession->audioCodecs = amf0_get_number(d);
	}
	if ((d = amf0_object_get(params, "videoCodecs"))) {
		rsession->videoCodecs = amf0_get_number(d);
	}
	if ((d = amf0_object_get(params, "videoFunction"))) {
		rsession->videoFunction = amf0_get_number(d);
	}

	amf0_object_add(object1, "fmsVer", amf0_number_new(1));
	amf0_object_add(object1, "capabilities", amf0_number_new(31));

	amf0_object_add(object2, "level", amf0_str("status"));
	amf0_object_add(object2, "code",  amf0_str("NetConnection.Connect.Success"));
	amf0_object_add(object2, "description", amf0_str("Connection succeeded"));
	amf0_object_add(object2, "clientId", amf0_number_new(217834719));
	amf0_object_add(object2, "objectEncoding", amf0_number_new(0));

	rtmp_set_chunksize(rsession, rsession->profile->chunksize);

	{
		unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW) };
		rtmp_send_message(rsession, 2, 0, RTMP_TYPE_WINDOW_ACK_SIZE, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER);
	}

	{
		unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW), 0x1 /* Soft limit */};
		rtmp_send_message(rsession, 2, 0, RTMP_TYPE_SET_PEER_BW, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER);
	}

	{
		unsigned char buf[] = {
			INT16(RTMP_CTRL_STREAM_BEGIN),
			INT32(0)
		};

		rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
	}

	/* respond with a success message */
	rtmp_send_invoke_free(rsession, amfnumber, 0, 0,
		amf0_str("_result"),
		amf0_number_new(1),
		object1,
		object2,
		NULL);

	rtmp_send_invoke_free(rsession, 3, 0, 0,
		amf0_str("connected"),
		amf0_number_new(0),
		amf0_null_new(),
		amf0_str(rsession->uuid), NULL);

	switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_NOTICE, "Sent connect reply\n");

	return SWITCH_STATUS_SUCCESS;
}


RTMP_INVOKE_FUNCTION(rtmp_i_createStream)
{
	rtmp_send_invoke_free(rsession, amfnumber, 0, 0,
		amf0_str("_result"),
		amf0_number_new(transaction_id),
		amf0_null_new(),
		amf0_number_new(rsession->next_streamid),
		NULL);

	switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Replied to createStream (%u)\n", amf0_get_number(argv[1]));

	rsession->next_streamid++;

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_initStream)
{
	switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Received initStream (%u)\n", rsession->next_streamid);

	rsession->next_streamid++;

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_noop)
{
	return SWITCH_STATUS_SUCCESS;
}


RTMP_INVOKE_FUNCTION(rtmp_i_receiveaudio)
{
		switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE;

		if (enabled) {
			switch_set_flag(rsession, SFLAG_AUDIO);
		} else {
			switch_clear_flag(rsession, SFLAG_AUDIO);
		}

		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "%sending audio\n", enabled ? "S" : "Not s");

		return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_receivevideo)
{
		switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE;

		if (enabled) {
			switch_set_flag(rsession, SFLAG_VIDEO);
			if (rsession->tech_pvt) {
				switch_set_flag(rsession->tech_pvt, TFLAG_VID_WAIT_KEYFRAME);
			}
		} else {
			switch_clear_flag(rsession, SFLAG_VIDEO);
		}

		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "%sending video\n", enabled ? "S" : "Not s");

		return SWITCH_STATUS_SUCCESS;
}


RTMP_INVOKE_FUNCTION(rtmp_i_play)
{
	amf0_data *obj = amf0_object_new();
	amf0_data *object = amf0_object_new();

	switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_DEBUG, "Got play for %s on stream %d\n", switch_str_nil(amf0_get_string(argv[1])),
		state->stream_id);

	/* Set outgoing chunk size to 1024 bytes */
	rtmp_set_chunksize(rsession, 1024);

	rsession->media_streamid = state->stream_id;

	/* Send StreamBegin on the current stream */
	{
		unsigned char buf[] = {
			INT16(RTMP_CTRL_STREAM_BEGIN),
			INT32(rsession->media_streamid)
		};
		rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
	}


	{
		unsigned char buf[] = {
			INT16(RTMP_CTRL_SET_BUFFER_LENGTH),
			INT32(rsession->media_streamid),
			INT32(rsession->profile->buffer_len)
		};
		rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
	}

	/* Send onStatus */
	amf0_object_add(object, "level", amf0_str("status"));
	amf0_object_add(object, "code", amf0_str("NetStream.Play.Reset"));
	amf0_object_add(object, "description", amf0_str("description"));
	amf0_object_add(object, "details", amf0_str("details"));
	amf0_object_add(object, "clientid", amf0_number_new(217834719));

	rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
		amf0_str("onStatus"),
		amf0_number_new(0),
		amf0_null_new(),
		object, NULL);

	object = amf0_object_new();

	amf0_object_add(object, "level", amf0_str("status"));
	amf0_object_add(object, "code", amf0_str("NetStream.Play.Start"));
	amf0_object_add(object, "description", amf0_str("description"));
	amf0_object_add(object, "details", amf0_str("details"));
	amf0_object_add(object, "clientid", amf0_number_new(217834719));

	rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
		amf0_str("onStatus"),
		amf0_number_new(0),
		amf0_null_new(),
		object, NULL);

	amf0_object_add(obj, "code", amf0_str("NetStream.Data.Start"));

	rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
		amf0_str("onStatus"),
		obj, NULL);

	rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
		amf0_str("|RtmpSampleAccess"),
		amf0_boolean_new(1),
		amf0_boolean_new(1), NULL);

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_publish)
{
	amf0_data *object = amf0_object_new();

	unsigned char buf[] = {
		INT16(RTMP_CTRL_STREAM_BEGIN),
		INT32(state->stream_id)
	};

	rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);

	amf0_object_add(object, "level", amf0_str("status"));
	amf0_object_add(object, "code", amf0_str("NetStream.Publish.Start"));
	amf0_object_add(object, "description", amf0_str("description"));
	amf0_object_add(object, "details", amf0_str("details"));
	amf0_object_add(object, "clientid", amf0_number_new(217834719));
	
	rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, state->stream_id,
		amf0_str("onStatus"),
		amf0_number_new(0),
		amf0_null_new(),
		object, NULL);


	switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Got publish on stream %u.\n", state->stream_id);

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_makeCall)
{
	switch_core_session_t *newsession = NULL;
	char *number = NULL;

	if ((number = amf0_get_string(argv[1]))) {
		switch_event_t *event = NULL;
		char *auth, *user = NULL, *domain = NULL;
		if (argc >= 3 && (auth = amf0_get_string(argv[2])) && !zstr(auth)) {
			switch_split_user_domain(auth, &user, &domain);
			if (rtmp_session_check_user(rsession, user, domain) != SWITCH_STATUS_SUCCESS) {
				switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in account [%s@%s]\n",
					number, switch_str_nil(user), switch_str_nil(domain));
				return SWITCH_STATUS_FALSE;
			}
		} else if (rsession->profile->auth_calls && !rsession->account) {
			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in\n", number);
			return SWITCH_STATUS_FALSE;
		}

		if (amf0_is_object(argv[3])) {
			amf_object_to_event(argv[3], &event);
		}

		if (rtmp_session_create_call(rsession, &newsession, 0, RTMP_DEFAULT_STREAM_AUDIO, number, user, domain, event) != SWITCH_CAUSE_SUCCESS) {
			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create call.\n");
		}

		if (event) {
			switch_event_destroy(&event);
		}
	}

	if (newsession) {
		rtmp_private_t *new_pvt = switch_core_session_get_private(newsession);
		rtmp_send_invoke_free(rsession, 3, 0, 0,
			amf0_str("onMakeCall"),
			amf0_number_new(transaction_id),
			amf0_null_new(),
			amf0_str(switch_core_session_get_uuid(newsession)),
			amf0_str(switch_str_nil(number)),
			amf0_str(switch_str_nil(new_pvt->auth)),
			NULL);

		rtmp_attach_private(rsession, switch_core_session_get_private(newsession));
	}

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_fcSubscribe)
{
	switch_status_t status;
	int ac;
	amf0_data *av[3] = { 0 };

	switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_DEBUG, "Got FCSubscribe for %s on stream %d\n", switch_str_nil(amf0_get_string(argv[1])), state->stream_id);

	ac = 3;
	av[0] = argv[0];
	av[1] = argv[1];
	av[2] = amf0_boolean_new(1);
	switch_assert(av[2]);

	status = rtmp_i_receiveaudio(rsession, state, amfnumber, transaction_id, ac, av);
	if (status != SWITCH_STATUS_SUCCESS) return status;

	status = rtmp_i_receivevideo(rsession, state, amfnumber, transaction_id, ac, av);
	if (status != SWITCH_STATUS_SUCCESS) return status;

	amf0_data_free(av[2]);

	rtmp_i_makeCall(rsession, state, amfnumber, transaction_id, argc, argv);

	return status;
}

RTMP_INVOKE_FUNCTION(rtmp_i_sendDTMF)
{
	/* Send DTMFs on the active channel */
	switch_dtmf_t dtmf = { 0 };
	switch_channel_t *channel;
	char *digits;

	if (!rsession->tech_pvt) {
		return SWITCH_STATUS_FALSE;
	}

	channel = switch_core_session_get_channel(rsession->tech_pvt->session);

	if (amf0_is_number(argv[2])) {
		dtmf.duration = amf0_get_number(argv[2]);
	} else if (!zstr(amf0_get_string(argv[2]))) {
		dtmf.duration = atoi(amf0_get_string(argv[2]));
	}

	if ((digits = amf0_get_string(argv[1]))) {
		size_t len = strlen(digits);
		size_t j;
		for (j = 0; j < len; j++) {
			dtmf.digit = digits[j];
			switch_channel_queue_dtmf(channel, &dtmf);
		}
	}

	return SWITCH_STATUS_SUCCESS;
}


RTMP_INVOKE_FUNCTION(rtmp_i_login)
{
	char *user, *auth, *domain, *ddomain = NULL;


	user = amf0_get_string(argv[1]);
	auth = amf0_get_string(argv[2]);

	if (zstr(user) || zstr(auth)) {
		return SWITCH_STATUS_FALSE;
	}

	if ((domain = strchr(user, '@'))) {
		*domain++ = '\0';
	}

	if (zstr(domain)) {
		ddomain = switch_core_get_domain(SWITCH_TRUE);
		domain = ddomain;
	}


	if (rtmp_check_auth(rsession, user, domain, auth) == SWITCH_STATUS_SUCCESS) {
		rtmp_session_login(rsession, user, domain);
	} else {
		rtmp_send_invoke_free(rsession, 3, 0, 0,
			amf0_str("onLogin"),
			amf0_number_new(0),
			amf0_null_new(),
			amf0_str("failure"),
			amf0_null_new(),
			amf0_null_new(), NULL);
	}


	switch_safe_free(ddomain);

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_logout)
{
	char *auth = amf0_get_string(argv[1]);
	char *user = NULL, *domain = NULL;

	/* Unregister from that user */
	rtmp_clear_registration(rsession, auth, NULL);

	switch_split_user_domain(auth, &user, &domain);

	if (!zstr(user) && !zstr(domain)) {
		rtmp_session_logout(rsession, user, domain);
	}

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_register)
{
	char *auth = amf0_get_string(argv[1]);
	const char *user = NULL, *domain = NULL;
	char *dup = NULL;
	switch_status_t status;

	if (!rsession->account) {
		return SWITCH_STATUS_FALSE;
	}

	if (!zstr(auth)) {
		dup = strdup(auth);
		switch_split_user_domain(dup, (char**)&user, (char**)&domain);
	} else {
		dup = auth = switch_mprintf("%s@%s", rsession->account->user, rsession->account->domain);
		user = rsession->account->user;
		domain = rsession->account->domain;
	}

	if (rtmp_session_check_user(rsession, user, domain) == SWITCH_STATUS_SUCCESS) {
		rtmp_add_registration(rsession, auth, amf0_get_string(argv[2]));
		status = SWITCH_STATUS_SUCCESS;
	} else {
		status = SWITCH_STATUS_FALSE;
	}

	switch_safe_free(dup);

	return status;
}

RTMP_INVOKE_FUNCTION(rtmp_i_unregister)
{
	rtmp_clear_registration(rsession, amf0_get_string(argv[1]), amf0_get_string(argv[2]));

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_answer)
{
	switch_channel_t *channel = NULL;
	char *uuid = amf0_get_string(argv[1]);

	if (!zstr(uuid)) {
		rtmp_private_t *new_tech_pvt = rtmp_locate_private(rsession, uuid);
		if (new_tech_pvt) {
			switch_channel_mark_answered(switch_core_session_get_channel(new_tech_pvt->session));
			rtmp_attach_private(rsession, new_tech_pvt);
		}
		return SWITCH_STATUS_FALSE;
	}

	if (!rsession->tech_pvt) {
		return SWITCH_STATUS_FALSE;
	}

	/* No UUID specified but we're attached to a channel, mark it as answered */
	channel = switch_core_session_get_channel(rsession->tech_pvt->session);
	switch_channel_mark_answered(channel);
	rtmp_attach_private(rsession, rsession->tech_pvt);

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_attach)
{
	rtmp_private_t *tech_pvt = NULL;
	char *uuid = amf0_get_string(argv[1]);

	if (!zstr(uuid)) {
		tech_pvt = rtmp_locate_private(rsession, uuid);
	}
	/* Will detach if an empty (or invalid) uuid is received */
	rtmp_attach_private(rsession, tech_pvt);

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_hangup)
{
	/* CallID (or null/nothing to hangup the current call) */
	char *uuid = amf0_get_string(argv[1]);
	char *scause;
	switch_channel_t *channel = NULL;
	switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;

	if (!zstr(uuid)) {
		rtmp_private_t *tech_pvt = rtmp_locate_private(rsession, uuid);
		if (tech_pvt) {
			channel = switch_core_session_get_channel(tech_pvt->session);
		}
	}

	if (!channel) {
		if (!rsession->tech_pvt) {
			return SWITCH_STATUS_FALSE;
		}
		channel = switch_core_session_get_channel(rsession->tech_pvt->session);
	}

	if (amf0_is_number(argv[2])) {
		cause = amf0_get_number(argv[2]);
	} else if ((scause = amf0_get_string(argv[2])) && !zstr(scause)) {
		cause = switch_channel_str2cause(scause);
	}

	switch_channel_hangup(channel, cause);

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_transfer)
{
	char *uuid = amf0_get_string(argv[1]);
	char *dest = amf0_get_string(argv[2]);
	rtmp_private_t *tech_pvt;

	if (zstr(uuid) || zstr(dest)) {
		return SWITCH_STATUS_FALSE;
	}

	if ((tech_pvt = rtmp_locate_private(rsession, uuid))) {
		const char *other_uuid = switch_channel_get_partner_uuid(tech_pvt->channel);
		switch_core_session_t *session;

		if (!zstr(other_uuid) && (session = switch_core_session_locate(other_uuid))) {
			switch_ivr_session_transfer(session, dest, NULL, NULL);
			switch_core_session_rwunlock(session);
		}
	}

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_join)
{
	char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) };
	const char *other_uuid[2];
	rtmp_private_t *tech_pvt[2];

	if (zstr(uuid[0]) || zstr(uuid[1])) {
		return SWITCH_STATUS_SUCCESS;
	}

	if (!(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) ||
		!(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) {
		return SWITCH_STATUS_FALSE;
	}

	if (tech_pvt[0] == tech_pvt[1]) {
		return SWITCH_STATUS_FALSE;
	}

	if ((other_uuid[0] = switch_channel_get_partner_uuid(tech_pvt[0]->channel)) &&
		(other_uuid[1] = switch_channel_get_partner_uuid(tech_pvt[1]->channel))) {

#ifndef RTMP_DONT_HOLD
		if (switch_test_flag(tech_pvt[0], TFLAG_DETACHED)) {
			switch_ivr_unhold(tech_pvt[0]->session);
		}
		if (switch_test_flag(tech_pvt[1], TFLAG_DETACHED)) {
			switch_ivr_unhold(tech_pvt[1]->session);
		}
#endif

		switch_ivr_uuid_bridge(other_uuid[0], other_uuid[1]);
	}

	return SWITCH_STATUS_SUCCESS;
}

/*

3-way:

[0] is always the current active call
[1] is the call to be brought into the call, and that will be running the three_way application

- Set the current app of other[1] to three_way with other_uuid[0]
- Put tech_pvt[0] to sleep: set state to CS_HIBERNATE
- set CF_TRANSFER, set state CS_EXECUTE (do we need CF_TRANSFER here?)

- setup a state handler in other[1] to detect when it hangs up

Check list:
tech_pvt[0] or other[0] hangs up
	If we were attached to the call, switch the active call to tech_pvt[1]
tech_pvt[1] or other[1] hangs up
	Clear up any 3-way indications on the tech_pvt[0]

*/

static switch_status_t three_way_on_soft_execute(switch_core_session_t *session);
#if 0
static switch_status_t three_way_on_hangup(switch_core_session_t *session);
#endif

static const switch_state_handler_table_t three_way_state_handlers_remote = {
	/*.on_init */ NULL,
	/*.on_routing */ NULL,
	/*.on_execute */ NULL,
	/*.on_hangup */ NULL,
	/*.on_exchange_media */ NULL,
	/*.on_soft_execute */ three_way_on_soft_execute,
	/*.on_consume_media */ NULL,
	/*.on_hibernate */ NULL
};

/* runs on other_session[1] */
static switch_status_t three_way_on_soft_execute(switch_core_session_t *other_session)
{
	switch_channel_t *other_channel = switch_core_session_get_channel(other_session);
	const char *uuid = switch_channel_get_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE);
	const char *my_uuid = switch_channel_get_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE);
	switch_core_session_t *my_session;
	switch_channel_t *my_channel;
	rtmp_private_t *tech_pvt;

	if (zstr(uuid) || zstr(my_uuid)) {
		return SWITCH_STATUS_SUCCESS;
	}

	if (zstr(my_uuid) || !(my_session = switch_core_session_locate(my_uuid))) {
		return SWITCH_STATUS_SUCCESS;
	}

	if (!switch_core_session_check_interface(my_session, rtmp_globals.rtmp_endpoint_interface)) {
		/* In case someone tempers with my variables, since we get tech_pvt from there */
		switch_core_session_rwunlock(my_session);
		return SWITCH_STATUS_SUCCESS;
	}

	my_channel = switch_core_session_get_channel(my_session);
	tech_pvt = switch_core_session_get_private(my_session);

	switch_ivr_eavesdrop_session(other_session, uuid, NULL, ED_MUX_READ | ED_MUX_WRITE);

	/* 3-way call ended, whatever the reason
	 * We need to go back to our original state. */
	if (!switch_channel_up(other_channel)) {
		/* channel[1] hung up, check if we have special post-bridge actions, and hangup otherwise */
		/* if my_channel isn't ready, it means something else has control of it, leave it alone */
		if (switch_channel_ready(my_channel)) {
			const char *s;
			if ((s = switch_channel_get_variable(my_channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE)) && switch_true(s)) {
				switch_ivr_park_session(my_session);
			} else if ((s = switch_channel_get_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE)) && !zstr(s)) {
				int argc;
				char *argv[4] = { 0 };
				char *mydata = switch_core_session_strdup(my_session, s);

				switch_channel_set_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE, NULL);

				if ((argc = switch_split(mydata, ':', argv)) >= 1) {
					switch_ivr_session_transfer(my_session, argv[0], argv[1], argv[2]);
				} else {
					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(my_session), SWITCH_LOG_ERROR, "No extension specified.\n");
				}
			} else {
				switch_channel_hangup(my_channel, SWITCH_CAUSE_NORMAL_CLEARING);
			}
		}
	} else if (switch_channel_ready(other_channel)) {
		/* channel[1] didn't hangup, must be channel[0] then, rebridge this one with its original partner */
		switch_ivr_uuid_bridge(switch_core_session_get_uuid(other_session), my_uuid);
	} else {
		/* channel[1] being taken out of our control, take the other leg out of CS_HIBERNATE if its ready, or else leave it alone */
		if (switch_channel_ready(my_channel)) {
			switch_channel_set_state(my_channel, CS_EXECUTE);
		}
	}

	switch_channel_clear_state_handler(other_channel, &three_way_state_handlers_remote);

	switch_channel_set_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL);
	switch_channel_set_variable(my_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL);
	switch_channel_set_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE, NULL);

	switch_clear_flag(tech_pvt, TFLAG_THREE_WAY);

	if (my_session) {
		switch_core_session_rwunlock(my_session);
	}

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_three_way)
{
	/* The first uuid is the (local) uuid of the current call, the 2nd one should be already detached */
	char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) };
	rtmp_private_t *tech_pvt[2];
	const char *other_uuid[2];
	switch_core_session_t *other_session[2] = { 0 };
	switch_channel_t *other_channel[2] = { 0 };

	if (zstr(uuid[0]) || zstr(uuid[1]) ||
		!(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) ||
		!(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) {
		return SWITCH_STATUS_FALSE;
	}

	/* Make sure we don't 3-way with the same call, and that it doesnt turn into a 4-way, we aren't that permissive */
	if (tech_pvt[0] == tech_pvt[1] || switch_test_flag(tech_pvt[0], TFLAG_THREE_WAY) ||
			switch_test_flag(tech_pvt[1], TFLAG_THREE_WAY)) {
		return SWITCH_STATUS_FALSE;
	}

	if (!(other_uuid[0] = switch_channel_get_partner_uuid(tech_pvt[0]->channel)) ||
		!(other_uuid[1] = switch_channel_get_partner_uuid(tech_pvt[1]->channel))) {
		return SWITCH_STATUS_FALSE; /* Both calls aren't bridged */
	}

	if (!(other_session[0] = switch_core_session_locate(other_uuid[0])) ||
		!(other_session[1] = switch_core_session_locate(other_uuid[1]))) {
		goto done;
	}

	other_channel[0] = switch_core_session_get_channel(other_session[0]);
	other_channel[1] = switch_core_session_get_channel(other_session[1]);

	/* Save which uuid is the 3-way target */
	switch_channel_set_variable(other_channel[1], RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]);
	switch_channel_set_variable(tech_pvt[1]->channel, RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]);

	/* Attach redirect */
	switch_set_flag(tech_pvt[1], TFLAG_THREE_WAY);

	/* Set soft_holding_uuid to the uuid of the other matching channel, so they can can be bridged back when the 3-way is over */
	switch_channel_set_variable(tech_pvt[1]->channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, other_uuid[1]);
	switch_channel_set_variable(other_channel[1], SWITCH_SOFT_HOLDING_UUID_VARIABLE, uuid[1]);

	/* Start the 3-way on the 2nd channel using a media bug */
	switch_channel_add_state_handler(other_channel[1], &three_way_state_handlers_remote);

	switch_channel_set_flag(tech_pvt[1]->channel, CF_TRANSFER);
	switch_channel_set_state(tech_pvt[1]->channel, CS_HIBERNATE);
	switch_channel_set_flag(other_channel[1], CF_TRANSFER);
	switch_channel_set_state(other_channel[1], CS_SOFT_EXECUTE);

done:

	if (other_session[0]) {
		switch_core_session_rwunlock(other_session[0]);
	}

	if (other_session[1]) {
		switch_core_session_rwunlock(other_session[1]);
	}

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_sendevent)
{
	amf0_data *obj = NULL;
	switch_event_t *event = NULL;
	switch_status_t status = SWITCH_STATUS_SUCCESS;
	const char *uuid = NULL;

	if (argv[1] && argv[1]->type == AMF0_TYPE_OBJECT) {
		obj = argv[1];
	} else if (argv[2] && argv[2]->type == AMF0_TYPE_OBJECT) {
		uuid = amf0_get_string(argv[1]);
		obj = argv[2];
	} else {
		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Bad argument for sendevent");
		return SWITCH_STATUS_FALSE;
	}


	if (switch_event_create_subclass(&event, zstr(uuid) ? SWITCH_EVENT_CUSTOM : SWITCH_EVENT_MESSAGE,
		zstr(uuid) ? RTMP_EVENT_CLIENTCUSTOM : NULL) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create event\n");
		return SWITCH_STATUS_FALSE;
	}

	rtmp_event_fill(rsession, event);

	/* Build event using amf array */
	if ((status = amf_object_to_event(obj, &event)) != SWITCH_STATUS_SUCCESS) {
		switch_event_destroy(&event);
		return SWITCH_STATUS_FALSE;
	}

	if (!zstr(uuid)) {
		rtmp_private_t *session_pvt = rtmp_locate_private(rsession, uuid);
		if (session_pvt) {
			if (switch_core_session_queue_event(session_pvt->session, &event) != SWITCH_STATUS_SUCCESS) {
				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_pvt->session), SWITCH_LOG_ERROR, "Couldn't queue event to session\n");
				switch_event_destroy(&event);
				status = SWITCH_STATUS_FALSE;
			} else {
				status = SWITCH_STATUS_SUCCESS;
			}
		}
	}

	switch_event_fire(&event);

	return SWITCH_STATUS_SUCCESS;
}

RTMP_INVOKE_FUNCTION(rtmp_i_log)
{
	const char *data = amf0_get_string(argv[1]);

	switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Log: %s\n", data);

	return SWITCH_STATUS_SUCCESS;
}

/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
 */
