/**
 * Start execution of call output component
 * @param component to start
 * @param session the session to output to
 * @param output the output request
 * @param iq the original request
 */
static iks *start_call_output(struct rayo_component *component, switch_core_session_t *session, iks *output, iks *iq)
{
	switch_stream_handle_t stream = { 0 };

	/* acknowledge command */
	rayo_component_send_start(component, iq);

	/* build playback command */
	SWITCH_STANDARD_STREAM(stream);
	stream.write_function(&stream, "{id=%s,session=%s,pause=%s",
		RAYO_JID(component), switch_core_session_get_uuid(session),
		OUTPUT_COMPONENT(component)->start_paused ? "true" : "false");
	if (OUTPUT_COMPONENT(component)->max_time > 0) {
		stream.write_function(&stream, ",timeout=%i", OUTPUT_COMPONENT(component)->max_time * 1000);
	}
	stream.write_function(&stream, "}fileman://rayo://%s", RAYO_JID(component));

	if (switch_ivr_displace_session(session, stream.data, 0, "m") == SWITCH_STATUS_SUCCESS) {
		RAYO_UNLOCK(component);
	} else {
		if (OUTPUT_COMPONENT(component)->document) {
			iks_delete(OUTPUT_COMPONENT(component)->document);
		}
		if (switch_channel_get_state(switch_core_session_get_channel(session)) >= CS_HANGUP) {
			rayo_component_send_complete(component, COMPONENT_COMPLETE_HANGUP);
			component = NULL;
		} else {
			rayo_component_send_complete(component, COMPONENT_COMPLETE_ERROR);
			component = NULL;
		}
	}
	switch_safe_free(stream.data);
	return NULL;
}
/**
 * Start execution of mixer output component
 */
static iks *start_mixer_output_component(struct rayo_actor *mixer, struct rayo_message *msg, void *data)
{
	iks *iq = msg->payload;
	struct rayo_component *component = NULL;
	iks *output = iks_find(iq, "output");
	iks *document = NULL;
	switch_stream_handle_t stream = { 0 };

	/* validate output attributes */
	if (!VALIDATE_RAYO_OUTPUT(output)) {
		return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
	}

	/* check if <document> exists */
	document = iks_find(output, "document");
	if (!document) {
		return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
	}

	component = create_output_component(mixer, RAT_MIXER_COMPONENT, output, iks_find_attrib(iq, "from"));
	if (!component) {
		return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create output entity");
	}

	/* build conference command */
	SWITCH_STANDARD_STREAM(stream);
	stream.write_function(&stream, "%s play ", rayo_mixer_get_name(RAYO_MIXER(mixer)), RAYO_ID(component));

	stream.write_function(&stream, "{id=%s,pause=%s",
		RAYO_JID(component),
		OUTPUT_COMPONENT(component)->start_paused ? "true" : "false");
	if (OUTPUT_COMPONENT(component)->max_time_ms > 0) {
		stream.write_function(&stream, ",timeout=%i", OUTPUT_COMPONENT(component)->max_time_ms);
	}
	if (OUTPUT_COMPONENT(component)->start_offset_ms > 0) {
		stream.write_function(&stream, ",start_offset_ms=%i", OUTPUT_COMPONENT(component)->start_offset_ms);
	}
	stream.write_function(&stream, "}fileman://rayo://%s", RAYO_JID(component));

	/* acknowledge command */
	rayo_component_send_start(component, iq);

	rayo_component_api_execute_async(component, "conference", stream.data);

	switch_safe_free(stream.data);
	RAYO_RELEASE(component);

	return NULL;
}
/**
 * Close SSML document.
 * @param handle
 * @return SWITCH_STATUS_SUCCESS
 */
static switch_status_t rayo_file_close(switch_file_handle_t *handle)
{
	struct rayo_file_context *context = (struct rayo_file_context *)handle->private_info;

	if (context && context->component) {
		struct output_component *output = OUTPUT_COMPONENT(context->component);

		/* send completion and destroy */
		if (output->stop) {
			rayo_component_send_complete(context->component, COMPONENT_COMPLETE_STOP);
		} else {
			rayo_component_send_complete(context->component, OUTPUT_FINISH);
		}
		/* TODO hangup / timed out */

		/* cleanup internals */
		switch_safe_free(context->ssml);
		context->ssml = NULL;
		if (output->document) {
			iks_delete(output->document);
			output->document = NULL;
		}

		/* close SSML file */
		if (switch_test_flag((&context->fh), SWITCH_FILE_OPEN)) {
			return switch_core_file_close(&context->fh);
		}
	}

	return SWITCH_STATUS_SUCCESS;
}
/**
 * Stop execution of output component
 */
static iks *stop_output_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
{
	iks *iq = msg->payload;
	switch_stream_handle_t stream = { 0 };
	char *command = switch_mprintf("%s stop", RAYO_JID(component));
	SWITCH_STANDARD_STREAM(stream);
	OUTPUT_COMPONENT(component)->stop = 1;
	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s stopping\n", RAYO_JID(component));
	switch_api_execute("fileman", command, NULL, &stream);
	switch_safe_free(stream.data);
	switch_safe_free(command);
	return iks_new_iq_result(iq);
}
/**
 * Close SSML document.
 * @param handle
 * @return SWITCH_STATUS_SUCCESS
 */
static switch_status_t rayo_file_close(switch_file_handle_t *handle)
{
	struct rayo_file_context *context = (struct rayo_file_context *)handle->private_info;

	if (context && context->component) {
		struct output_component *output = OUTPUT_COMPONENT(context->component);

		/* send completion and destroy */
		if (output->stop) {
			rayo_component_send_complete(context->component, COMPONENT_COMPLETE_STOP);
		} else {
			if (!strcmp(RAYO_ACTOR(context->component)->type, RAT_CALL_COMPONENT)) {
				/* call output... check for hangup */
				switch_core_session_t *session = switch_core_session_locate(RAYO_ACTOR(context->component)->parent->id);
				if (session) {
					if (switch_channel_get_state(switch_core_session_get_channel(session)) >= CS_HANGUP) {
						rayo_component_send_complete(context->component, COMPONENT_COMPLETE_HANGUP);
					} else {
						rayo_component_send_complete(context->component, OUTPUT_FINISH);
					}
					switch_core_session_rwunlock(session);
				} else {
					/* session is gone */
					rayo_component_send_complete(context->component, COMPONENT_COMPLETE_HANGUP);
				}
			} else {
				/* mixer output... finished */
				rayo_component_send_complete(context->component, OUTPUT_FINISH);
			}
		}
		/* TODO timed out */

		/* cleanup internals */
		switch_safe_free(context->ssml);
		context->ssml = NULL;
		if (output->document) {
			iks_delete(output->document);
			output->document = NULL;
		}

		/* close SSML file */
		if (switch_test_flag((&context->fh), SWITCH_FILE_OPEN)) {
			return switch_core_file_close(&context->fh);
		}
	}

	return SWITCH_STATUS_SUCCESS;
}
/**
 * Create new output component
 */
static struct rayo_component *create_output_component(struct rayo_actor *actor, const char *type, iks *output, const char *client_jid)
{
	switch_memory_pool_t *pool;
	struct output_component *output_component = NULL;

	switch_core_new_memory_pool(&pool);
	output_component = switch_core_alloc(pool, sizeof(*output_component));
	output_component = OUTPUT_COMPONENT(rayo_component_init((struct rayo_component *)output_component, pool, type, "output", NULL, actor, client_jid));
	if (output_component) {
		output_component->document = iks_copy(output);
		output_component->start_offset_ms = iks_find_int_attrib(output, "start-offset");
		output_component->repeat_interval_ms = iks_find_int_attrib(output, "repeat-interval");
		output_component->repeat_times = iks_find_int_attrib(output, "repeat-times");
		output_component->max_time_ms = iks_find_int_attrib(output, "max-time");
		output_component->start_paused = iks_find_bool_attrib(output, "start-paused");
		output_component->renderer = switch_core_strdup(RAYO_POOL(output_component), iks_find_attrib_soft(output, "renderer"));
		/* get custom headers */
		{
			switch_stream_handle_t headers = { 0 };
			iks *header = NULL;
			int first = 1;
			SWITCH_STANDARD_STREAM(headers);
			for (header = iks_find(output, "header"); header; header = iks_next_tag(header)) {
				if (!strcmp("header", iks_name(header))) {
					const char *name = iks_find_attrib_soft(header, "name");
					const char *value = iks_find_attrib_soft(header, "value");
					if (!zstr(name) && !zstr(value)) {
						headers.write_function(&headers, "%s%s=%s", first ? "{" : ",", name, value);
						first = 0;
					}
				}
			}
			if (headers.data) {
				headers.write_function(&headers, "}");
				output_component->headers = switch_core_strdup(RAYO_POOL(output_component), (char *)headers.data);
				free(headers.data);
			}
		}
	} else {
		switch_core_destroy_memory_pool(&pool);
	}

	return RAYO_COMPONENT(output_component);
}
/**
 * Read from SSML document
 * @param handle
 * @param data
 * @param len
 * @return
 */
static switch_status_t rayo_file_read(switch_file_handle_t *handle, void *data, size_t *len)
{
	switch_status_t status;
	struct rayo_file_context *context = (struct rayo_file_context *)handle->private_info;
	size_t llen = *len;

	if (OUTPUT_COMPONENT(context->component)->stop) {
		return SWITCH_STATUS_FALSE;
	} else {
		status = switch_core_file_read(&context->fh, data, len);
		if (status != SWITCH_STATUS_SUCCESS) {
			if ((status = next_file(handle)) != SWITCH_STATUS_SUCCESS) {
				return status;
			}
			*len = llen;
			status = switch_core_file_read(&context->fh, data, len);
		}
	}

	return status;
}
/**
 * open next file for reading
 * @param handle the file handle
 */
static switch_status_t next_file(switch_file_handle_t *handle)
{
	int loops = 0;
	struct rayo_file_context *context = handle->private_info;
	struct output_component *output = context->component ? OUTPUT_COMPONENT(context->component) : NULL;

  top:

	if (switch_test_flag((&context->fh), SWITCH_FILE_OPEN)) {
		switch_core_file_close(&context->fh);
	}

	if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
		/* unsupported */
		return SWITCH_STATUS_FALSE;
	}

	if (!context->cur_doc) {
		context->cur_doc = iks_find(output->document, "document");
		if (!context->cur_doc) {
			iks_delete(output->document);
			output->document = NULL;
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing <document>\n");
			return SWITCH_STATUS_FALSE;
		}
	} else {
		context->cur_doc = iks_next_tag(context->cur_doc);
	}

	/* done? */
	if (!context->cur_doc) {
		if (context->could_open && ++loops < 2 && (output->repeat_times == 0 || ++context->play_count < output->repeat_times)) {
			/* repeat all document(s) */
			if (!output->repeat_interval_ms) {
				goto top;
			}
		} else {
			/* no more files to play */
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Done playing\n");
			return SWITCH_STATUS_FALSE;
		}
	}

	if (!context->cur_doc) {
		/* play silence between repeats */
		switch_safe_free(context->ssml);
		context->ssml = switch_mprintf("silence_stream://%i", output->repeat_interval_ms);
	} else {
		/* play next document */
		iks *speak = NULL;

		switch_safe_free(context->ssml);
		context->ssml = NULL;
 		speak = iks_find(context->cur_doc, "speak");
		if (speak) {
			/* <speak> is child node */
			char *ssml_str = iks_string(NULL, speak);
			if (zstr(output->renderer)) {
				/* FS must parse the SSML */
				context->ssml = switch_mprintf("ssml://%s", ssml_str);
			} else {
				/* renderer will parse the SSML */
				if (!zstr(output->headers) && !strncmp("unimrcp", output->renderer, 7)) {
					/* pass MRCP headers */
					context->ssml = switch_mprintf("tts://%s||%s%s", output->renderer, output->headers, ssml_str);
				} else {
					context->ssml = switch_mprintf("tts://%s||%s", output->renderer, ssml_str);
				}
			}
			iks_free(ssml_str);
		} else if (iks_has_children(context->cur_doc)) {
			/* check if <speak> is in CDATA */
			const char *ssml_str = NULL;
			iks *ssml = iks_child(context->cur_doc);
			if (ssml && iks_type(ssml) == IKS_CDATA) {
				ssml_str = iks_cdata(ssml);
			}
			if (zstr(ssml_str)) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing <document> CDATA\n");
				return SWITCH_STATUS_FALSE;
			}
			if (zstr(output->renderer)) {
				/* FS must parse the SSML */
				context->ssml = switch_mprintf("ssml://%s", ssml_str);
			} else {
				/* renderer will parse the SSML */
				if (!zstr(output->headers) && !strncmp("unimrcp", output->renderer, 7)) {
					/* pass MRCP headers */
					context->ssml = switch_mprintf("tts://%s||%s%s", output->renderer, output->headers, ssml_str);
				} else {
					context->ssml = switch_mprintf("tts://%s||%s", output->renderer, ssml_str);
				}
			}
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing <speak>\n");
			return SWITCH_STATUS_FALSE;
		}
	}
	if (switch_core_file_open(&context->fh, context->ssml, handle->channels, handle->samplerate, handle->flags, NULL) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to open %s\n", context->ssml);
		goto top;
	} else {
		context->could_open = 1;
	}

	handle->samples = context->fh.samples;
	handle->format = context->fh.format;
	handle->sections = context->fh.sections;
	handle->seekable = context->fh.seekable;
	handle->speed = context->fh.speed;
	handle->vol = context->fh.vol;
	handle->offset_pos = context->fh.offset_pos;
	handle->interval = context->fh.interval;

	if (switch_test_flag((&context->fh), SWITCH_FILE_NATIVE)) {
		switch_set_flag(handle, SWITCH_FILE_NATIVE);
	} else {
		switch_clear_flag(handle, SWITCH_FILE_NATIVE);
	}

	return SWITCH_STATUS_SUCCESS;
}