/**
 * WinPR: Windows Portable Runtime
 * Clipboard Functions
 *
 * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <winpr/crt.h>
#include <winpr/collections.h>

#include <winpr/clipboard.h>

#include "clipboard.h"

/**
 * Clipboard (Windows):
 * msdn.microsoft.com/en-us/library/windows/desktop/ms648709/
 *
 * W3C Clipboard API and events:
 * http://www.w3.org/TR/clipboard-apis/
 */

const char* CF_STANDARD_STRINGS[CF_MAX] =
{
	"CF_RAW",		/* 0 */
	"CF_TEXT",		/* 1 */
	"CF_BITMAP",		/* 2 */
	"CF_METAFILEPICT",	/* 3 */
	"CF_SYLK",		/* 4 */
	"CF_DIF",		/* 5 */
	"CF_TIFF",		/* 6 */
	"CF_OEMTEXT",		/* 7 */
	"CF_DIB",		/* 8 */
	"CF_PALETTE",		/* 9 */
	"CF_PENDATA",		/* 10 */
	"CF_RIFF",		/* 11 */
	"CF_WAVE",		/* 12 */
	"CF_UNICODETEXT",	/* 13 */
	"CF_ENHMETAFILE",	/* 14 */
	"CF_HDROP",		/* 15 */
	"CF_LOCALE",		/* 16 */
	"CF_DIBV5"		/* 17 */
};

wClipboardFormat* ClipboardFindFormat(wClipboard* clipboard, UINT32 formatId, const char* name)
{
	UINT32 index;
	wClipboardFormat* format = NULL;

	if (!clipboard)
		return NULL;
	
	if (formatId)
	{
		for (index = 0; index < clipboard->numFormats; index++)
		{
			if (formatId == clipboard->formats[index].formatId)
			{
				format = &clipboard->formats[index];
				break;
			}
		}
	}
	else if (name)
	{
		for (index = 0; index < clipboard->numFormats; index++)
		{
			if (strcmp(name, clipboard->formats[index].formatName) == 0)
			{
				format = &clipboard->formats[index];
				break;
			}
		}
	}
	else
	{
		/* special "CF_RAW" case */

		if (clipboard->numFormats > 0)
		{
			format = &clipboard->formats[0];

			if (format->formatId)
				return NULL;

			if (!format->formatName || (strcmp(format->formatName, CF_STANDARD_STRINGS[0]) == 0))
				return format;
		}
	}

	return format;
}

wClipboardSynthesizer* ClipboardFindSynthesizer(wClipboardFormat* format, UINT32 formatId)
{
	UINT32 index;
	wClipboardSynthesizer* synthesizer;

	if (!format)
		return NULL;

	if (format->numSynthesizers < 1)
		return NULL;

	for (index = 0; index < format->numSynthesizers; index++)
	{
		synthesizer = &(format->synthesizers[index]);

		if (formatId == synthesizer->syntheticId)
			return synthesizer;
	}

	return NULL;
}

void ClipboardLock(wClipboard* clipboard)
{
	if (!clipboard)
		return;
	
	EnterCriticalSection(&(clipboard->lock));
}

void ClipboardUnlock(wClipboard* clipboard)
{
	if (!clipboard)
		return;
	
	LeaveCriticalSection(&(clipboard->lock));
}

BOOL ClipboardEmpty(wClipboard* clipboard)
{
	if (!clipboard)
		return FALSE;
	
	if (clipboard->data)
	{
		free((void*) clipboard->data);
		clipboard->data = NULL;
	}

	clipboard->size = 0;

	clipboard->formatId = 0;
	clipboard->sequenceNumber++;

	return TRUE;
}

UINT32 ClipboardCountRegisteredFormats(wClipboard* clipboard)
{
	if (!clipboard)
		return 0;
	
	return clipboard->numFormats;
}

UINT32 ClipboardGetRegisteredFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
{
	UINT32 index;
	UINT32* pFormatIds;
	wClipboardFormat* format;
	
	if (!clipboard)
		return 0;

	if (!ppFormatIds)
		return 0;

	pFormatIds = *ppFormatIds;

	if (!pFormatIds)
	{
		pFormatIds = malloc(clipboard->numFormats * sizeof(UINT32));

		if (!pFormatIds)
			return 0;

		*ppFormatIds = pFormatIds;
	}

	for (index = 0; index < clipboard->numFormats; index++)
	{
		format = &(clipboard->formats[index]);
		pFormatIds[index] = format->formatId;
	}

	return clipboard->numFormats;
}

UINT32 ClipboardRegisterFormat(wClipboard* clipboard, const char* name)
{
	wClipboardFormat* format;
	
	if (!clipboard)
		return 0;

	format = ClipboardFindFormat(clipboard, 0, name);

	if (format)
		return format->formatId;

	if ((clipboard->numFormats + 1) >= clipboard->maxFormats)
	{
		UINT32 numFormats = clipboard->maxFormats * 2;
		wClipboardFormat *tmpFormat;

		tmpFormat = (wClipboardFormat*) realloc(clipboard->formats,
				numFormats * sizeof(wClipboardFormat));

		if (!tmpFormat)
			return 0;

		clipboard->formats  = tmpFormat;
		clipboard->maxFormats = numFormats;
	}

	format = &(clipboard->formats[clipboard->numFormats]);
	ZeroMemory(format, sizeof(wClipboardFormat));

	if (name)
	{
		format->formatName = _strdup(name);

		if (!format->formatName)
			return 0;
	}

	format->formatId = clipboard->nextFormatId++;
	clipboard->numFormats++;

	return format->formatId;
}

BOOL ClipboardRegisterSynthesizer(wClipboard* clipboard, UINT32 formatId,
		UINT32 syntheticId, CLIPBOARD_SYNTHESIZE_FN pfnSynthesize)
{
	UINT32 index;
	wClipboardFormat* format;
	wClipboardSynthesizer* synthesizer;
	
	if (!clipboard)
		return FALSE;

	format = ClipboardFindFormat(clipboard, formatId, NULL);

	if (!format)
		return FALSE;

	if (format->formatId == syntheticId)
		return FALSE;

	synthesizer = ClipboardFindSynthesizer(format, formatId);

	if (!synthesizer)
	{
		wClipboardSynthesizer *tmpSynthesizer;
		UINT32 numSynthesizers = format->numSynthesizers + 1;

		tmpSynthesizer = (wClipboardSynthesizer*) realloc(format->synthesizers,
				numSynthesizers * sizeof(wClipboardSynthesizer));

		if (!tmpSynthesizer)
			return FALSE;

		format->synthesizers  = tmpSynthesizer;
		format->numSynthesizers = numSynthesizers;
		index = numSynthesizers - 1;
		synthesizer = &(format->synthesizers[index]);
	}

	ZeroMemory(synthesizer, sizeof(wClipboardSynthesizer));

	synthesizer->syntheticId = syntheticId;
	synthesizer->pfnSynthesize = pfnSynthesize;

	return TRUE;
}

UINT32 ClipboardCountFormats(wClipboard* clipboard)
{
	UINT32 count;
	wClipboardFormat* format;
	
	if (!clipboard)
		return 0;

	format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);

	if (!format)
		return 0;

	count = 1 + format->numSynthesizers;

	return count;
}

UINT32 ClipboardGetFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
{
	UINT32 index;
	UINT32 count;
	UINT32* pFormatIds;
	wClipboardFormat* format;
	wClipboardSynthesizer* synthesizer;
	
	if (!clipboard)
		return 0;

	format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);

	if (!format)
		return 0;

	count = 1 + format->numSynthesizers;

	if (!ppFormatIds)
		return 0;

	pFormatIds = *ppFormatIds;

	if (!pFormatIds)
	{
		pFormatIds = malloc(count * sizeof(UINT32));

		if (!pFormatIds)
			return 0;

		*ppFormatIds = pFormatIds;
	}

	pFormatIds[0] = format->formatId;

	for (index = 1; index < count; index++)
	{
		synthesizer = &(format->synthesizers[index - 1]);
		pFormatIds[index] = synthesizer->syntheticId;
	}

	return count;
}

BOOL ClipboardInitFormats(wClipboard* clipboard)
{
	UINT32 formatId = 0;
	wClipboardFormat* format;
	
	if (!clipboard)
		return FALSE;

	for (formatId = 0; formatId < CF_MAX; formatId++)
	{
		format = &(clipboard->formats[clipboard->numFormats++]);
		ZeroMemory(format, sizeof(wClipboardFormat));

		format->formatId = formatId;
		format->formatName = _strdup(CF_STANDARD_STRINGS[formatId]);

		if (!format->formatName)
			return FALSE;
	}

	ClipboardInitSynthesizers(clipboard);

	return TRUE;
}

UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name)
{
	wClipboardFormat* format;

	if (!clipboard)
		return 0;
	
	format = ClipboardFindFormat(clipboard, 0, name);

	if (!format)
		return 0;

	return format->formatId;
}

const char* ClipboardGetFormatName(wClipboard* clipboard, UINT32 formatId)
{
	wClipboardFormat* format;
	
	if (!clipboard)
		return NULL;

	format = ClipboardFindFormat(clipboard, formatId, NULL);

	if (!format)
		return NULL;

	return format->formatName;
}

void* ClipboardGetData(wClipboard* clipboard, UINT32 formatId, UINT32* pSize)
{
	UINT32 SrcSize = 0;
	UINT32 DstSize = 0;
	void* pSrcData = NULL;
	void* pDstData = NULL;
	wClipboardFormat* format;
	wClipboardSynthesizer* synthesizer;
	
	if (!clipboard)
		return NULL;

	if (!pSize)
		return NULL;

	format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);

	if (!format)
		return NULL;

	SrcSize = clipboard->size;
	pSrcData = (void*) clipboard->data;

	if (formatId == format->formatId)
	{
		DstSize = SrcSize;

		pDstData = malloc(DstSize);

		if (!pDstData)
			return NULL;

		CopyMemory(pDstData, pSrcData, SrcSize);
		*pSize = DstSize;
	}
	else
	{
		synthesizer = ClipboardFindSynthesizer(format, formatId);

		if (!synthesizer || !synthesizer->pfnSynthesize)
			return NULL;

		DstSize = SrcSize;
		pDstData = synthesizer->pfnSynthesize(clipboard, format->formatId, pSrcData, &DstSize);

		*pSize = DstSize;
	}

	return pDstData;
}

BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const void* data, UINT32 size)
{
	wClipboardFormat* format;
	
	if (!clipboard)
		return FALSE;

	format = ClipboardFindFormat(clipboard, formatId, NULL);

	if (!format)
		return FALSE;

	free((void*) clipboard->data);
	clipboard->data = data;
	clipboard->size = size;

	clipboard->formatId = formatId;
	clipboard->sequenceNumber++;

	return TRUE;
}

UINT64 ClipboardGetOwner(wClipboard* clipboard)
{
	if (!clipboard)
		return 0;
	
	return clipboard->ownerId;
}

void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId)
{
	if (!clipboard)
		return;
	
	clipboard->ownerId = ownerId;
}

wClipboard* ClipboardCreate()
{
	wClipboard* clipboard;

	clipboard = (wClipboard*) calloc(1, sizeof(wClipboard));

	if (clipboard)
	{
		clipboard->nextFormatId = 0xC000;
		clipboard->sequenceNumber = 0;

		InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000);

		clipboard->numFormats = 0;
		clipboard->maxFormats = 64;
		clipboard->formats = (wClipboardFormat*) malloc(clipboard->maxFormats * sizeof(wClipboardFormat));

		if (!clipboard->formats)
		{
			free(clipboard);
			return NULL;
		}

		ClipboardInitFormats(clipboard);
	}

	return clipboard;
}

void ClipboardDestroy(wClipboard* clipboard)
{
	UINT32 index;
	wClipboardFormat* format;

	if (!clipboard)
		return;

	for (index = 0; index < clipboard->numFormats; index++)
	{
		format = &(clipboard->formats[index]);
		free((void*) format->formatName);

		if (format->synthesizers)
		{
			free(format->synthesizers);
			format->synthesizers = NULL;
			format->numSynthesizers = 0;
		}
	}

	free((void*) clipboard->data);
	clipboard->data = NULL;
	clipboard->size = 0;

	clipboard->numFormats = 0;
	free(clipboard->formats);

	DeleteCriticalSection(&(clipboard->lock));

	free(clipboard);
}
