/**
 * FreeRDP: A Remote Desktop Protocol Client
 * FreeRDP Windows Server
 *
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
 * Copyright 2012 Corey Clayton <can.of.tuna@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.
 */

#include <freerdp/config.h>

#include <stdio.h>

#include <winpr/windows.h>

#include <freerdp/freerdp.h>
#include <freerdp/listener.h>

#include "wf_peer.h"
#include "wf_info.h"
#include "wf_mirage.h"

#include "wf_update.h"

#include <freerdp/log.h>
#define TAG SERVER_TAG("windows")

DWORD WINAPI wf_update_thread(LPVOID lpParam)
{
	DWORD fps;
	wfInfo* wfi;
	DWORD beg, end;
	DWORD diff, rate;
	wfi = (wfInfo*)lpParam;
	fps = wfi->framesPerSecond;
	rate = 1000 / fps;

	while (1)
	{
		beg = GetTickCount();

		if (wf_info_lock(wfi) > 0)
		{
			if (wfi->activePeerCount > 0)
			{
				wf_info_update_changes(wfi);

				if (wf_info_have_updates(wfi))
				{
					wf_update_encode(wfi);
					// WLog_DBG(TAG, "Start of parallel sending");
					int index = 0;

					for (int peerindex = 0; peerindex < wfi->peerCount; peerindex++)
					{
						for (; index < FREERDP_SERVER_WIN_INFO_MAXPEERS; index++)
						{
							if (wfi->peers[index] && wfi->peers[index]->activated)
							{
								// WLog_DBG(TAG, "Setting event for %d of %d", index + 1,
								// wfi->activePeerCount);
								(void)SetEvent(
								    ((wfPeerContext*)wfi->peers[index]->context)->updateEvent);
							}
						}
					}

					for (int index = 0; index < wfi->activePeerCount; index++)
					{
						// WLog_DBG(TAG, "Waiting for %d of %d", index + 1, wfi->activePeerCount);
						// WaitForSingleObject(wfi->updateSemaphore, INFINITE);
						(void)WaitForSingleObject(wfi->updateSemaphore, 1000);
					}

					// WLog_DBG(TAG, "End of parallel sending");
					wf_info_clear_invalid_region(wfi);
				}
			}

			wf_info_unlock(wfi);
		}

		end = GetTickCount();
		diff = end - beg;

		if (diff < rate)
		{
			Sleep(rate - diff);
		}
	}

	// WLog_DBG(TAG, "Exiting Update Thread");
	return 0;
}

void wf_update_encode(wfInfo* wfi)
{
	RFX_RECT rect;
	long height, width;
	BYTE* pDataBits = nullptr;
	int stride;
	SURFACE_BITS_COMMAND* cmd;
	wf_info_find_invalid_region(wfi);
	cmd = &wfi->cmd;
	Stream_ResetPosition(wfi->s);
	wf_info_getScreenData(wfi, &width, &height, &pDataBits, &stride);
	rect.x = 0;
	rect.y = 0;
	rect.width = (UINT16)width;
	rect.height = (UINT16)height;
	// WLog_DBG(TAG, "x:%"PRId32" y:%"PRId32" w:%ld h:%ld", wfi->invalid.left, wfi->invalid.top,
	// width, height);
	Stream_Clear(wfi->s);

	if (!(rfx_compose_message(wfi->rfx_context, wfi->s, &rect, 1, pDataBits, width, height,
	                          stride)))
	{
		return;
	}

	wfi->frame_idx = rfx_context_get_frame_idx(wfi->rfx_context);
	cmd->destLeft = wfi->invalid.left;
	cmd->destTop = wfi->invalid.top;
	cmd->destRight = wfi->invalid.left + width;
	cmd->destBottom = wfi->invalid.top + height;
	cmd->bmp.bpp = 32;
	cmd->bmp.codecID = 3;
	cmd->bmp.width = width;
	cmd->bmp.height = height;
	cmd->bmp.bitmapDataLength = Stream_GetPosition(wfi->s);
	cmd->bmp.bitmapData = Stream_Buffer(wfi->s);
}

void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context)
{
	freerdp_peer* client;

	WINPR_ASSERT(wfi);
	WINPR_ASSERT(context);

	client = ((rdpContext*)context)->peer;
	WINPR_ASSERT(client);

	/* This happens when the RemoteFX encoder state is reset */

	if (wfi->frame_idx == 1)
		context->frame_idx = 0;

	/*
	 * When a new client connects, it is possible that old frames from
	 * from a previous encoding state remain. Those frames should be discarded
	 * as they will cause an error condition in mstsc.
	 */

	if ((context->frame_idx + 1) != wfi->frame_idx)
	{
		/* This frame is meant to be discarded */
		if (context->frame_idx == 0)
			return;

		/* This is an unexpected error condition */
		WLog_DBG(TAG, "Unexpected Frame Index: Actual: %d Expected: %d", wfi->frame_idx,
		         context->frame_idx + 1);
	}

	WINPR_ASSERT(client->context);
	WINPR_ASSERT(client->context->settings);
	WINPR_ASSERT(client->context->update);
	WINPR_ASSERT(client->context->update->SurfaceBits);

	wfi->cmd.bmp.codecID =
	    freerdp_settings_get_uint32(client->context->settings, FreeRDP_RemoteFxCodecId);
	client->context->update->SurfaceBits(client->context, &wfi->cmd);
	context->frame_idx++;
}

void wf_update_encoder_reset(wfInfo* wfi)
{
	if (wf_info_lock(wfi) > 0)
	{
		WLog_DBG(TAG, "Resetting encoder");

		if (wfi->rfx_context)
		{
			rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height);
		}
		else
		{
			/* TODO: pass ThreadingFlags somehow */
			wfi->rfx_context = rfx_context_new(TRUE);
			rfx_context_set_mode(wfi->rfx_context, RLGR3);
			rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height);
			rfx_context_set_pixel_format(wfi->rfx_context, PIXEL_FORMAT_BGRA32);
			wfi->s = Stream_New(nullptr, 0xFFFF);
		}

		wf_info_invalidate_full_screen(wfi);
		wf_info_unlock(wfi);
	}
}

void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context)
{
	if (wf_info_lock(wfi) > 0)
	{
		if (wfi->activePeerCount < 1)
		{
#ifndef WITH_DXGI_1_2
			wf_mirror_driver_activate(wfi);
#endif
			ResumeThread(wfi->updateThread);
		}

		wf_update_encoder_reset(wfi);
		wfi->activePeerCount++;
		WLog_DBG(TAG, "Activating Peer Updates: %d", wfi->activePeerCount);
		wf_info_unlock(wfi);
	}
}

void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context)
{
	if (wf_info_lock(wfi) > 0)
	{
		freerdp_peer* client = ((rdpContext*)context)->peer;

		if (client->activated)
		{
			if (wfi->activePeerCount <= 1)
			{
				wf_mirror_driver_deactivate(wfi);
			}

			client->activated = FALSE;
			wfi->activePeerCount--;
			WLog_DBG(TAG, "Deactivating Peer Updates: %d", wfi->activePeerCount);
		}

		wf_info_unlock(wfi);
	}
}
