/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * Clipboard Virtual Channel
 *
 * Copyright 2009-2011 Jay Sorg
 * Copyright 2010-2011 Vic Lee
 * 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 <winpr/crt.h>
#include <winpr/print.h>
#include <winpr/clipboard.h>

#include <freerdp/types.h>
#include <freerdp/freerdp.h>
#include <freerdp/settings.h>
#include <freerdp/constants.h>
#include <freerdp/client/cliprdr.h>

#include "cliprdr_main.h"
#include "cliprdr_format.h"
#include "../cliprdr_common.h"

CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask,
                                               const UINT32 checkMask)
{
	const UINT32 maskData =
	    checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL);
	const UINT32 maskFiles =
	    checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
	WINPR_ASSERT(list);

	CLIPRDR_FORMAT_LIST filtered = { 0 };
	filtered.common.msgType = CB_FORMAT_LIST;
	filtered.numFormats = list->numFormats;
	filtered.formats = calloc(filtered.numFormats, sizeof(CLIPRDR_FORMAT));

	size_t wpos = 0;
	if ((mask & checkMask) == checkMask)
	{
		for (size_t x = 0; x < list->numFormats; x++)
		{
			const CLIPRDR_FORMAT* format = &list->formats[x];
			CLIPRDR_FORMAT* cur = &filtered.formats[x];
			cur->formatId = format->formatId;
			if (format->formatName)
				cur->formatName = _strdup(format->formatName);
			wpos++;
		}
	}
	else if ((mask & maskFiles) != 0)
	{
		for (size_t x = 0; x < list->numFormats; x++)
		{
			const CLIPRDR_FORMAT* format = &list->formats[x];
			CLIPRDR_FORMAT* cur = &filtered.formats[wpos];

			if (!format->formatName)
				continue;
			if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0 ||
			    strcmp(format->formatName, type_FileContents) == 0)
			{
				cur->formatId = format->formatId;
				cur->formatName = _strdup(format->formatName);
				wpos++;
			}
		}
	}
	else if ((mask & maskData) != 0)
	{
		for (size_t x = 0; x < list->numFormats; x++)
		{
			const CLIPRDR_FORMAT* format = &list->formats[x];
			CLIPRDR_FORMAT* cur = &filtered.formats[wpos];

			if (!format->formatName ||
			    (strcmp(format->formatName, type_FileGroupDescriptorW) != 0 &&
			     strcmp(format->formatName, type_FileContents) != 0))
			{
				cur->formatId = format->formatId;
				if (format->formatName)
					cur->formatName = _strdup(format->formatName);
				wpos++;
			}
		}
	}
	WINPR_ASSERT(wpos <= UINT32_MAX);
	filtered.numFormats = (UINT32)wpos;
	return filtered;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
                                 UINT16 msgFlags)
{
	CLIPRDR_FORMAT_LIST formatList = { 0 };
	CLIPRDR_FORMAT_LIST filteredFormatList = { 0 };
	CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
	UINT error = CHANNEL_RC_OK;

	formatList.common.msgType = CB_FORMAT_LIST;
	formatList.common.msgFlags = msgFlags;
	formatList.common.dataLen = dataLen;

	if ((error =
	         cliprdr_read_format_list(cliprdr->log, s, &formatList, cliprdr->useLongFormatNames)))
		goto error_out;

	const UINT32 mask =
	    freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
	filteredFormatList = cliprdr_filter_format_list(
	    &formatList, mask, CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
	if (filteredFormatList.numFormats == 0)
		goto error_out;

	const DWORD level = WLOG_DEBUG;
	if (WLog_IsLevelActive(cliprdr->log, level))
	{
		WLog_Print(cliprdr->log, level, "ServerFormatList: numFormats: %" PRIu32 "",
		           formatList.numFormats);
		for (size_t x = 0; x < formatList.numFormats; x++)
		{
			const CLIPRDR_FORMAT* format = &formatList.formats[x];
			WLog_Print(cliprdr->log, level, "[%" PRIuz "]: id=0x%08" PRIx32 " [%s|%s]", x,
			           format->formatId, ClipboardGetFormatIdString(format->formatId),
			           format->formatName);
		}

		WLog_Print(cliprdr->log, level, "ServerFormatList [filtered]: numFormats: %" PRIu32 "",
		           filteredFormatList.numFormats);
		for (size_t x = 0; x < filteredFormatList.numFormats; x++)
		{
			const CLIPRDR_FORMAT* format = &filteredFormatList.formats[x];
			WLog_Print(cliprdr->log, level, "[%" PRIuz "]: id=0x%08" PRIx32 " [%s|%s]", x,
			           format->formatId, ClipboardGetFormatIdString(format->formatId),
			           format->formatName);
		}
	}

	if (context->ServerFormatList)
	{
		if ((error = context->ServerFormatList(context, &filteredFormatList)))
			WLog_Print(cliprdr->log, WLOG_ERROR, "ServerFormatList failed with error %" PRIu32 "",
			           error);
	}

error_out:
	cliprdr_free_format_list(&filteredFormatList);
	cliprdr_free_format_list(&formatList);
	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, WINPR_ATTR_UNUSED wStream* s,
                                          UINT32 dataLen, UINT16 msgFlags)
{
	CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
	CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
	UINT error = CHANNEL_RC_OK;

	WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatListResponse");

	formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
	formatListResponse.common.msgFlags = msgFlags;
	formatListResponse.common.dataLen = dataLen;

	IFCALLRET(context->ServerFormatListResponse, error, context, &formatListResponse);
	if (error)
		WLog_Print(cliprdr->log, WLOG_ERROR,
		           "ServerFormatListResponse failed with error %" PRIu32 "!", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
                                         UINT16 msgFlags)
{
	CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = { 0 };
	CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
	UINT error = CHANNEL_RC_OK;

	formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST;
	formatDataRequest.common.msgFlags = msgFlags;
	formatDataRequest.common.dataLen = dataLen;

	if ((error = cliprdr_read_format_data_request(s, &formatDataRequest)))
		return error;

	WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataRequest (0x%08" PRIx32 " [%s])",
	           formatDataRequest.requestedFormatId,
	           ClipboardGetFormatIdString(formatDataRequest.requestedFormatId));

	const UINT32 mask =
	    freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
	if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0)
	{
		return cliprdr_send_error_response(cliprdr, CB_FORMAT_DATA_RESPONSE);
	}

	context->lastRequestedFormatId = formatDataRequest.requestedFormatId;
	IFCALLRET(context->ServerFormatDataRequest, error, context, &formatDataRequest);
	if (error)
		WLog_Print(cliprdr->log, WLOG_ERROR,
		           "ServerFormatDataRequest failed with error %" PRIu32 "!", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
                                          UINT16 msgFlags)
{
	CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = { 0 };
	CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
	UINT error = CHANNEL_RC_OK;

	WLog_Print(cliprdr->log, WLOG_DEBUG,
	           "ServerFormatDataResponse: msgFlags=0x%08" PRIx32 ", dataLen=%" PRIu32, msgFlags,
	           dataLen);

	formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE;
	formatDataResponse.common.msgFlags = msgFlags;
	formatDataResponse.common.dataLen = dataLen;

	if ((error = cliprdr_read_format_data_response(s, &formatDataResponse)))
		return error;

	const UINT32 mask =
	    freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
	if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0)
	{
		WLog_Print(cliprdr->log, WLOG_WARN,
		           "Received ServerFormatDataResponse but remote -> local clipboard is disabled");
		return CHANNEL_RC_OK;
	}

	IFCALLRET(context->ServerFormatDataResponse, error, context, &formatDataResponse);
	if (error)
		WLog_Print(cliprdr->log, WLOG_ERROR,
		           "ServerFormatDataResponse failed with error %" PRIu32 "!", error);

	return error;
}
