/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * Video Redirection Virtual Channel - Interface Manipulation
 *
 * Copyright 2010-2011 Vic Lee
 * Copyright 2012 Hewlett-Packard Development Company, L.P.
 * Copyright 2015 Thincast Technologies GmbH
 * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <freerdp/config.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <winpr/crt.h>

#include <winpr/stream.h>

#include "tsmf_types.h"
#include "tsmf_constants.h"
#include "tsmf_media.h"
#include "tsmf_codec.h"

#include "tsmf_ifman.h"

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman)
{
	UINT32 CapabilityValue = 0;

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 4))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(ifman->input, CapabilityValue);
	DEBUG_TSMF("server CapabilityValue %" PRIu32 "", CapabilityValue);

	if (!Stream_EnsureRemainingCapacity(ifman->output, 8))
		return ERROR_INVALID_DATA;

	Stream_Write_UINT32(ifman->output, 1); /* CapabilityValue */
	Stream_Write_UINT32(ifman->output, 0); /* Result */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman)
{
	UINT32 v = 0;
	UINT32 pos = 0;
	UINT32 CapabilityType = 0;
	UINT32 cbCapabilityLength = 0;
	UINT32 numHostCapabilities = 0;

	WINPR_ASSERT(ifman);
	if (!Stream_EnsureRemainingCapacity(ifman->output, ifman->input_size + 4))
		return ERROR_OUTOFMEMORY;

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, ifman->input_size))
		return ERROR_INVALID_DATA;

	pos = Stream_GetPosition(ifman->output);
	Stream_Copy(ifman->input, ifman->output, ifman->input_size);
	Stream_SetPosition(ifman->output, pos);

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(ifman->output, numHostCapabilities);

	for (UINT32 i = 0; i < numHostCapabilities; i++)
	{
		if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 8))
			return ERROR_INVALID_DATA;

		Stream_Read_UINT32(ifman->output, CapabilityType);
		Stream_Read_UINT32(ifman->output, cbCapabilityLength);

		if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, cbCapabilityLength))
			return ERROR_INVALID_DATA;

		pos = Stream_GetPosition(ifman->output);

		switch (CapabilityType)
		{
			case 1: /* Protocol version request */
				if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
					return ERROR_INVALID_DATA;

				Stream_Read_UINT32(ifman->output, v);
				DEBUG_TSMF("server protocol version %" PRIu32 "", v);
				break;

			case 2: /* Supported platform */
				if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
					return ERROR_INVALID_DATA;

				Stream_Peek_UINT32(ifman->output, v);
				DEBUG_TSMF("server supported platform %" PRIu32 "", v);
				/* Claim that we support both MF and DShow platforms. */
				Stream_Write_UINT32(ifman->output, MMREDIR_CAPABILITY_PLATFORM_MF |
				                                       MMREDIR_CAPABILITY_PLATFORM_DSHOW);
				break;

			default:
				WLog_ERR(TAG, "skipping unknown capability type %" PRIu32 "", CapabilityType);
				break;
		}

		Stream_SetPosition(ifman->output, pos + cbCapabilityLength);
	}

	Stream_Write_UINT32(ifman->output, 0); /* Result */
	ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman)
{
	UINT32 numMediaType = 0;
	UINT32 PlatformCookie = 0;
	UINT32 FormatSupported = 1;

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 12))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(ifman->input, PlatformCookie);
	Stream_Seek_UINT32(ifman->input); /* NoRolloverFlags (4 bytes) */
	Stream_Read_UINT32(ifman->input, numMediaType);
	DEBUG_TSMF("PlatformCookie %" PRIu32 " numMediaType %" PRIu32 "", PlatformCookie, numMediaType);

	if (!tsmf_codec_check_media_type(ifman->decoder_name, ifman->input))
		FormatSupported = 0;

	if (FormatSupported)
		DEBUG_TSMF("format ok.");

	if (!Stream_EnsureRemainingCapacity(ifman->output, 12))
		return -1;

	Stream_Write_UINT32(ifman->output, FormatSupported);
	Stream_Write_UINT32(ifman->output, PlatformCookie);
	Stream_Write_UINT32(ifman->output, 0); /* Result */
	ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman)
{
	UINT status = CHANNEL_RC_OK;
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (presentation)
	{
		DEBUG_TSMF("Presentation already exists");
		ifman->output_pending = FALSE;
		return CHANNEL_RC_OK;
	}

	presentation = tsmf_presentation_new(Stream_Pointer(ifman->input), ifman->channel_callback);

	if (!presentation)
		status = ERROR_OUTOFMEMORY;
	else
		tsmf_presentation_set_audio_device(presentation, ifman->audio_name, ifman->audio_device);

	ifman->output_pending = TRUE;
	return status;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext)
{
	UINT32 StreamId = 0;
	UINT status = CHANNEL_RC_OK;
	TSMF_STREAM* stream = NULL;
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
	Stream_Seek(ifman->input, GUID_SIZE);

	if (!presentation)
	{
		WLog_ERR(TAG, "unknown presentation id");
		status = ERROR_NOT_FOUND;
	}
	else
	{
		Stream_Read_UINT32(ifman->input, StreamId);
		Stream_Seek_UINT32(ifman->input); /* numMediaType */
		stream = tsmf_stream_new(presentation, StreamId, rdpcontext);

		if (!stream)
		{
			WLog_ERR(TAG, "failed to create stream");
			return ERROR_OUTOFMEMORY;
		}

		if (!tsmf_stream_set_format(stream, ifman->decoder_name, ifman->input))
		{
			WLog_ERR(TAG, "failed to set stream format");
			return ERROR_OUTOFMEMORY;
		}

		tsmf_stream_start_threads(stream);
	}

	ifman->output_pending = TRUE;
	return status;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman)
{
	DEBUG_TSMF("");

	if (!Stream_EnsureRemainingCapacity(ifman->output, 8))
		return ERROR_OUTOFMEMORY;

	Stream_Write_UINT32(ifman->output, 1); /* TopologyReady */
	Stream_Write_UINT32(ifman->output, 0); /* Result */
	ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman)
{
	int status = CHANNEL_RC_OK;
	UINT32 StreamId = 0;
	TSMF_STREAM* stream = NULL;
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
	Stream_Seek(ifman->input, GUID_SIZE);

	if (!presentation)
	{
		status = ERROR_NOT_FOUND;
	}
	else
	{
		Stream_Read_UINT32(ifman->input, StreamId);
		stream = tsmf_stream_find_by_id(presentation, StreamId);

		if (stream)
			tsmf_stream_free(stream);
		else
			status = ERROR_NOT_FOUND;
	}

	ifman->output_pending = TRUE;
	return status;
}

static float tsmf_stream_read_float(wStream* s)
{
	float fValue = NAN;
	UINT32 iValue = 0;
	Stream_Read_UINT32(s, iValue);
	CopyMemory(&fValue, &iValue, 4);
	return fValue;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman)
{
	UINT status = CHANNEL_RC_OK;
	float Left = NAN;
	float Top = NAN;
	float Right = NAN;
	float Bottom = NAN;
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 32))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
	Stream_Seek(ifman->input, GUID_SIZE);

	if (!presentation)
	{
		status = ERROR_NOT_FOUND;
	}
	else
	{
		Left = tsmf_stream_read_float(ifman->input);   /* Left (4 bytes) */
		Top = tsmf_stream_read_float(ifman->input);    /* Top (4 bytes) */
		Right = tsmf_stream_read_float(ifman->input);  /* Right (4 bytes) */
		Bottom = tsmf_stream_read_float(ifman->input); /* Bottom (4 bytes) */
		DEBUG_TSMF("SetSourceVideoRect: Left: %f Top: %f Right: %f Bottom: %f", Left, Top, Right,
		           Bottom);
	}

	ifman->output_pending = TRUE;
	return status;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (presentation)
		tsmf_presentation_free(presentation);
	else
	{
		WLog_ERR(TAG, "unknown presentation id");
		return ERROR_NOT_FOUND;
	}

	if (!Stream_EnsureRemainingCapacity(ifman->output, 4))
		return ERROR_OUTOFMEMORY;

	Stream_Write_UINT32(ifman->output, 0); /* Result */
	ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	UINT32 newVolume = 0;
	UINT32 muted = 0;
	DEBUG_TSMF("on stream volume");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (!presentation)
	{
		WLog_ERR(TAG, "unknown presentation id");
		return ERROR_NOT_FOUND;
	}

	Stream_Seek(ifman->input, 16);
	Stream_Read_UINT32(ifman->input, newVolume);
	DEBUG_TSMF("on stream volume: new volume=[%" PRIu32 "]", newVolume);
	Stream_Read_UINT32(ifman->input, muted);
	DEBUG_TSMF("on stream volume: muted=[%" PRIu32 "]", muted);

	if (!tsmf_presentation_volume_changed(presentation, newVolume, muted))
		return ERROR_INVALID_OPERATION;

	ifman->output_pending = TRUE;
	return 0;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("on channel volume");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (presentation)
	{
		UINT32 channelVolume = 0;
		UINT32 changedChannel = 0;
		Stream_Seek(ifman->input, 16);
		Stream_Read_UINT32(ifman->input, channelVolume);
		DEBUG_TSMF("on channel volume: channel volume=[%" PRIu32 "]", channelVolume);
		Stream_Read_UINT32(ifman->input, changedChannel);
		DEBUG_TSMF("on stream volume: changed channel=[%" PRIu32 "]", changedChannel);
	}

	ifman->output_pending = TRUE;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman)
{
	DEBUG_TSMF("");
	ifman->output_pending = TRUE;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	UINT32 numGeometryInfo = 0;
	UINT32 Left = 0;
	UINT32 Top = 0;
	UINT32 Width = 0;
	UINT32 Height = 0;
	UINT32 cbVisibleRect = 0;
	RDP_RECT* rects = NULL;
	int num_rects = 0;
	UINT error = CHANNEL_RC_OK;
	size_t pos = 0;

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 32))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (!presentation)
		return ERROR_NOT_FOUND;

	Stream_Seek(ifman->input, 16);
	Stream_Read_UINT32(ifman->input, numGeometryInfo);
	pos = Stream_GetPosition(ifman->input);
	Stream_Seek(ifman->input, 12); /* VideoWindowId (8 bytes), VideoWindowState (4 bytes) */
	Stream_Read_UINT32(ifman->input, Width);
	Stream_Read_UINT32(ifman->input, Height);
	Stream_Read_UINT32(ifman->input, Left);
	Stream_Read_UINT32(ifman->input, Top);
	Stream_SetPosition(ifman->input, pos + numGeometryInfo);
	Stream_Read_UINT32(ifman->input, cbVisibleRect);
	num_rects = cbVisibleRect / 16;
	DEBUG_TSMF("numGeometryInfo %" PRIu32 " Width %" PRIu32 " Height %" PRIu32 " Left %" PRIu32
	           " Top %" PRIu32 " cbVisibleRect %" PRIu32 " num_rects %d",
	           numGeometryInfo, Width, Height, Left, Top, cbVisibleRect, num_rects);

	if (num_rects > 0)
	{
		rects = (RDP_RECT*)calloc(num_rects, sizeof(RDP_RECT));

		for (UINT32 i = 0; i < num_rects; i++)
		{
			Stream_Read_UINT16(ifman->input, rects[i].y); /* Top */
			Stream_Seek_UINT16(ifman->input);
			Stream_Read_UINT16(ifman->input, rects[i].x); /* Left */
			Stream_Seek_UINT16(ifman->input);
			Stream_Read_UINT16(ifman->input, rects[i].height); /* Bottom */
			Stream_Seek_UINT16(ifman->input);
			Stream_Read_UINT16(ifman->input, rects[i].width); /* Right */
			Stream_Seek_UINT16(ifman->input);
			rects[i].width -= rects[i].x;
			rects[i].height -= rects[i].y;
			DEBUG_TSMF("rect %d: %" PRId16 " %" PRId16 " %" PRId16 " %" PRId16 "", i, rects[i].x,
			           rects[i].y, rects[i].width, rects[i].height);
		}
	}

	if (!tsmf_presentation_set_geometry_info(presentation, Left, Top, Width, Height, num_rects,
	                                         rects))
		return ERROR_INVALID_OPERATION;

	ifman->output_pending = TRUE;
	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman)
{
	DEBUG_TSMF("");
	ifman->output_pending = TRUE;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman)
{
	DEBUG_TSMF("");
	tsmf_ifman_on_playback_paused(ifman);
	ifman->output_pending = TRUE;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	TSMF_STREAM* stream = NULL;
	UINT32 StreamId = 0;
	UINT64 SampleStartTime = 0;
	UINT64 SampleEndTime = 0;
	UINT64 ThrottleDuration = 0;
	UINT32 SampleExtensions = 0;
	UINT32 cbData = 0;
	UINT error = 0;

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 60))
		return ERROR_INVALID_DATA;

	Stream_Seek(ifman->input, 16);
	Stream_Read_UINT32(ifman->input, StreamId);
	Stream_Seek_UINT32(ifman->input); /* numSample */
	Stream_Read_UINT64(ifman->input, SampleStartTime);
	Stream_Read_UINT64(ifman->input, SampleEndTime);
	Stream_Read_UINT64(ifman->input, ThrottleDuration);
	Stream_Seek_UINT32(ifman->input); /* SampleFlags */
	Stream_Read_UINT32(ifman->input, SampleExtensions);
	Stream_Read_UINT32(ifman->input, cbData);

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, cbData))
		return ERROR_INVALID_DATA;

	DEBUG_TSMF("MessageId %" PRIu32 " StreamId %" PRIu32 " SampleStartTime %" PRIu64
	           " SampleEndTime %" PRIu64 " "
	           "ThrottleDuration %" PRIu64 " SampleExtensions %" PRIu32 " cbData %" PRIu32 "",
	           ifman->message_id, StreamId, SampleStartTime, SampleEndTime, ThrottleDuration,
	           SampleExtensions, cbData);
	presentation = tsmf_presentation_find_by_id(ifman->presentation_id);

	if (!presentation)
	{
		WLog_ERR(TAG, "unknown presentation id");
		return ERROR_NOT_FOUND;
	}

	stream = tsmf_stream_find_by_id(presentation, StreamId);

	if (!stream)
	{
		WLog_ERR(TAG, "unknown stream id");
		return ERROR_NOT_FOUND;
	}

	if (!tsmf_stream_push_sample(stream, ifman->channel_callback, ifman->message_id,
	                             SampleStartTime, SampleEndTime, ThrottleDuration, SampleExtensions,
	                             cbData, Stream_Pointer(ifman->input)))
	{
		WLog_ERR(TAG, "unable to push sample");
		return ERROR_OUTOFMEMORY;
	}

	if ((error = tsmf_presentation_sync(presentation)))
	{
		WLog_ERR(TAG, "tsmf_presentation_sync failed with error %" PRIu32 "", error);
		return error;
	}

	ifman->output_pending = TRUE;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman)
{
	UINT32 StreamId = 0;
	TSMF_PRESENTATION* presentation = NULL;
	TSMF_STREAM* stream = NULL;

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20))
		return ERROR_INVALID_DATA;

	Stream_Seek(ifman->input, 16);
	Stream_Read_UINT32(ifman->input, StreamId);
	DEBUG_TSMF("StreamId %" PRIu32 "", StreamId);
	presentation = tsmf_presentation_find_by_id(ifman->presentation_id);

	if (!presentation)
	{
		WLog_ERR(TAG, "unknown presentation id");
		return ERROR_NOT_FOUND;
	}

	/* Flush message is for a stream, not the entire presentation
	 * therefore we only flush the stream as intended per the MS-RDPEV spec
	 */
	stream = tsmf_stream_find_by_id(presentation, StreamId);

	if (stream)
	{
		if (!tsmf_stream_flush(stream))
			return ERROR_INVALID_OPERATION;
	}
	else
		WLog_ERR(TAG, "unknown stream id");

	ifman->output_pending = TRUE;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman)
{
	UINT32 StreamId = 0;
	TSMF_STREAM* stream = NULL;
	TSMF_PRESENTATION* presentation = NULL;

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));
	Stream_Seek(ifman->input, 16);
	Stream_Read_UINT32(ifman->input, StreamId);

	if (presentation)
	{
		stream = tsmf_stream_find_by_id(presentation, StreamId);

		if (stream)
			tsmf_stream_end(stream, ifman->message_id, ifman->channel_callback);
	}

	DEBUG_TSMF("StreamId %" PRIu32 "", StreamId);
	ifman->output_pending = TRUE;
	ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");

	if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 16))
		return ERROR_INVALID_DATA;

	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (presentation)
		tsmf_presentation_start(presentation);
	else
		WLog_ERR(TAG, "unknown presentation id");

	if (!Stream_EnsureRemainingCapacity(ifman->output, 16))
		return ERROR_OUTOFMEMORY;

	Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION);         /* FunctionId */
	Stream_Write_UINT32(ifman->output, 0);                                 /* StreamId */
	Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_START_COMPLETED); /* EventId */
	Stream_Write_UINT32(ifman->output, 0);                                 /* cbData */
	ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");
	ifman->output_pending = TRUE;
	/* Added pause control so gstreamer pipeline can be paused accordingly */
	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (presentation)
	{
		if (!tsmf_presentation_paused(presentation))
			return ERROR_INVALID_OPERATION;
	}
	else
		WLog_ERR(TAG, "unknown presentation id");

	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");
	ifman->output_pending = TRUE;
	/* Added restart control so gstreamer pipeline can be resumed accordingly */
	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (presentation)
	{
		if (!tsmf_presentation_restarted(presentation))
			return ERROR_INVALID_OPERATION;
	}
	else
		WLog_ERR(TAG, "unknown presentation id");

	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman)
{
	TSMF_PRESENTATION* presentation = NULL;
	DEBUG_TSMF("");
	presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input));

	if (presentation)
	{
		if (!tsmf_presentation_stop(presentation))
			return ERROR_INVALID_OPERATION;
	}
	else
		WLog_ERR(TAG, "unknown presentation id");

	if (!Stream_EnsureRemainingCapacity(ifman->output, 16))
		return ERROR_OUTOFMEMORY;

	Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION);        /* FunctionId */
	Stream_Write_UINT32(ifman->output, 0);                                /* StreamId */
	Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_STOP_COMPLETED); /* EventId */
	Stream_Write_UINT32(ifman->output, 0);                                /* cbData */
	ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman)
{
	DEBUG_TSMF("");

	if (!Stream_EnsureRemainingCapacity(ifman->output, 16))
		return ERROR_OUTOFMEMORY;

	Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION);        /* FunctionId */
	Stream_Write_UINT32(ifman->output, 0);                                /* StreamId */
	Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_MONITORCHANGED); /* EventId */
	Stream_Write_UINT32(ifman->output, 0);                                /* cbData */
	ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY;
	return CHANNEL_RC_OK;
}
