/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * TestSyncAPC
 *
 * Copyright 2021 David Fort <contact@hardening-consulting.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 <winpr/wtypes.h>
#include <winpr/thread.h>
#include <winpr/synch.h>

typedef struct
{
	BOOL error;
	BOOL called;
} UserApcArg;

void CALLBACK userApc(ULONG_PTR arg)
{
	UserApcArg* userArg = (UserApcArg*)arg;
	userArg->called = TRUE;
}

static DWORD WINAPI uncleanThread(LPVOID lpThreadParameter)
{
	/* this thread post an APC that will never get executed */
	UserApcArg* userArg = (UserApcArg*)lpThreadParameter;
	if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)lpThreadParameter))
	{
		userArg->error = TRUE;
		return 1;
	}

	return 0;
}

static DWORD WINAPI cleanThread(LPVOID lpThreadParameter)
{
	Sleep(500);

	SleepEx(500, TRUE);
	return 0;
}

typedef struct
{
	HANDLE timer1;
	DWORD timer1Calls;
	HANDLE timer2;
	DWORD timer2Calls;
	BOOL endTest;
} UncleanCloseData;

static VOID CALLBACK Timer1APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
{
	UncleanCloseData* data = (UncleanCloseData*)lpArg;
	data->timer1Calls++;
	CloseHandle(data->timer2);
	data->endTest = TRUE;
}

static VOID CALLBACK Timer2APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
{
	UncleanCloseData* data = (UncleanCloseData*)lpArg;
	data->timer2Calls++;
}

static DWORD /*WINAPI*/ closeHandleTest(LPVOID lpThreadParameter)
{
	LARGE_INTEGER dueTime;
	UncleanCloseData* data = (UncleanCloseData*)lpThreadParameter;
	data->endTest = FALSE;

	dueTime.QuadPart = -500;
	if (!SetWaitableTimer(data->timer1, &dueTime, 0, Timer1APCProc, lpThreadParameter, FALSE))
		return 1;

	dueTime.QuadPart = -900;
	if (!SetWaitableTimer(data->timer2, &dueTime, 0, Timer2APCProc, lpThreadParameter, FALSE))
		return 1;

	while (!data->endTest)
	{
		SleepEx(100, TRUE);
	}
	return 0;
}

int TestSynchAPC(int argc, char* argv[])
{
	HANDLE thread = NULL;
	UserApcArg userApcArg;
	UncleanCloseData uncleanCloseData;

	userApcArg.error = FALSE;
	userApcArg.called = FALSE;

	WINPR_UNUSED(argc);
	WINPR_UNUSED(argv);

	/* first post an APC and check it is executed during a SleepEx */
	if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)&userApcArg))
		return 1;

	if (SleepEx(100, FALSE) != 0)
		return 2;

	if (SleepEx(100, TRUE) != WAIT_IO_COMPLETION)
		return 3;

	if (!userApcArg.called)
		return 4;

	userApcArg.called = FALSE;

	/* test that the APC is cleaned up even when not called */
	thread = CreateThread(NULL, 0, uncleanThread, &userApcArg, 0, NULL);
	if (!thread)
		return 10;
	WaitForSingleObject(thread, INFINITE);
	CloseHandle(thread);

	if (userApcArg.called || userApcArg.error)
		return 11;

	/* test a remote APC queuing */
	thread = CreateThread(NULL, 0, cleanThread, &userApcArg, 0, NULL);
	if (!thread)
		return 20;

	if (!QueueUserAPC((PAPCFUNC)userApc, thread, (ULONG_PTR)&userApcArg))
		return 21;

	WaitForSingleObject(thread, INFINITE);
	CloseHandle(thread);

	if (!userApcArg.called)
		return 22;

#if 0
	/* test cleanup of timer completions */
	memset(&uncleanCloseData, 0, sizeof(uncleanCloseData));
	uncleanCloseData.timer1 = CreateWaitableTimerA(NULL, FALSE, NULL);
	if (!uncleanCloseData.timer1)
		return 31;

	uncleanCloseData.timer2 = CreateWaitableTimerA(NULL, FALSE, NULL);
	if (!uncleanCloseData.timer2)
		return 32;

	thread = CreateThread(NULL, 0, closeHandleTest, &uncleanCloseData, 0, NULL);
	if (!thread)
		return 33;

	WaitForSingleObject(thread, INFINITE);
	CloseHandle(thread);

	if (uncleanCloseData.timer1Calls != 1 || uncleanCloseData.timer2Calls != 0)
		return 34;
	CloseHandle(uncleanCloseData.timer1);
#endif
	return 0;
}
