/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 *
 * 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/print.h>
#include <freerdp/log.h>

#include "win_rdp.h"

#define TAG SERVER_TAG("shadow.win")

void shw_OnChannelConnectedEventHandler(rdpContext* context, ChannelConnectedEventArgs* e)
{
	shwContext* shw = (shwContext*) context;

	WLog_INFO(TAG, "OnChannelConnected: %s", e->name);
}

void shw_OnChannelDisconnectedEventHandler(rdpContext* context, ChannelDisconnectedEventArgs* e)
{
	shwContext* shw = (shwContext*) context;

	WLog_INFO(TAG, "OnChannelDisconnected: %s", e->name);
}

BOOL shw_begin_paint(rdpContext* context)
{
	shwContext* shw;
	rdpGdi* gdi = context->gdi;

	shw = (shwContext*) context;

	gdi->primary->hdc->hwnd->invalid->null = 1;
	gdi->primary->hdc->hwnd->ninvalid = 0;
	return TRUE;
}

BOOL shw_end_paint(rdpContext* context)
{
	int index;
	int ninvalid;
	HGDI_RGN cinvalid;
	RECTANGLE_16 invalidRect;
	rdpGdi* gdi = context->gdi;
	shwContext* shw = (shwContext*) context;
	winShadowSubsystem* subsystem = shw->subsystem;

	ninvalid = gdi->primary->hdc->hwnd->ninvalid;
	cinvalid = gdi->primary->hdc->hwnd->cinvalid;

	for (index = 0; index < ninvalid; index++)
	{
		invalidRect.left = cinvalid[index].x;
		invalidRect.top = cinvalid[index].y;
		invalidRect.right = cinvalid[index].x + cinvalid[index].w;
		invalidRect.bottom = cinvalid[index].y + cinvalid[index].h;

		region16_union_rect(&(subsystem->invalidRegion), &(subsystem->invalidRegion), &invalidRect);
	}

	SetEvent(subsystem->RdpUpdateEnterEvent);
	WaitForSingleObject(subsystem->RdpUpdateLeaveEvent, INFINITE);
	ResetEvent(subsystem->RdpUpdateLeaveEvent);
	return TRUE;
}

BOOL shw_desktop_resize(rdpContext* context)
{
	return TRUE;
}

BOOL shw_surface_frame_marker(rdpContext* context, SURFACE_FRAME_MARKER* surfaceFrameMarker)
{
	shwContext* shw = (shwContext*) context;
	return TRUE;
}

BOOL shw_authenticate(freerdp* instance, char** username, char** password, char** domain)
{
	return TRUE;
}

BOOL shw_verify_certificate(freerdp* instance, char* subject, char* issuer, char* fingerprint)
{
	return TRUE;
}

int shw_verify_x509_certificate(freerdp* instance, BYTE* data, int length, const char* hostname, int port, DWORD flags)
{
	return 1;
}

void shw_OnConnectionResultEventHandler(rdpContext* context, ConnectionResultEventArgs* e)
{
	shwContext* shw = (shwContext*) context;
	WLog_INFO(TAG, "OnConnectionResult: %d", e->result);
}

BOOL shw_pre_connect(freerdp* instance)
{
	shwContext* shw;
	rdpContext* context = instance->context;

	shw = (shwContext*) context;

	PubSub_SubscribeConnectionResult(context->pubSub,
			(pConnectionResultEventHandler) shw_OnConnectionResultEventHandler);

	PubSub_SubscribeChannelConnected(context->pubSub,
			(pChannelConnectedEventHandler) shw_OnChannelConnectedEventHandler);

	PubSub_SubscribeChannelDisconnected(context->pubSub,
			(pChannelDisconnectedEventHandler) shw_OnChannelDisconnectedEventHandler);

	freerdp_client_load_addins(context->channels, instance->settings);

	freerdp_channels_pre_connect(context->channels, instance);

	return TRUE;
}

BOOL shw_post_connect(freerdp* instance)
{
	rdpGdi* gdi;
	shwContext* shw;
	rdpSettings* settings;

	shw = (shwContext*) instance->context;
	settings = instance->settings;

	if (!gdi_init(instance, CLRBUF_32BPP, NULL))
		return FALSE;

	gdi = instance->context->gdi;

	instance->update->BeginPaint = shw_begin_paint;
	instance->update->EndPaint = shw_end_paint;
	instance->update->DesktopResize = shw_desktop_resize;
	instance->update->SurfaceFrameMarker = shw_surface_frame_marker;

	freerdp_channels_post_connect(instance->context->channels, instance);

	return TRUE;
}

void* shw_client_thread(void* arg)
{
	int index;
	int rcount;
	int wcount;
	BOOL bSuccess;
	void* rfds[32];
	void* wfds[32];
	int fds_count;
	HANDLE fds[64];
	shwContext* shw;
	rdpContext* context;
	rdpChannels* channels;
	freerdp* instance = (freerdp*) arg;

	ZeroMemory(rfds, sizeof(rfds));
	ZeroMemory(wfds, sizeof(wfds));

	context = (rdpContext*) instance->context;
	shw = (shwContext*) context;

	bSuccess = freerdp_connect(instance);

	WLog_INFO(TAG, "freerdp_connect: %d", bSuccess);

	if (!bSuccess)
	{
		ExitThread(0);
		return NULL;
	}

	channels = instance->context->channels;

	while (1)
	{
		rcount = 0;
		wcount = 0;

		if (!freerdp_get_fds(instance, rfds, &rcount, wfds, &wcount))
		{
			WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
			break;
		}

		if (!freerdp_channels_get_fds(channels, instance, rfds, &rcount, wfds, &wcount))
		{
			WLog_ERR(TAG, "Failed to get channels file descriptor");
			break;
		}

		fds_count = 0;
		
		for (index = 0; index < rcount; index++)
			fds[fds_count++] = rfds[index];

		for (index = 0; index < wcount; index++)
			fds[fds_count++] = wfds[index];

		if (MsgWaitForMultipleObjects(fds_count, fds, FALSE, 1000, QS_ALLINPUT) == WAIT_FAILED)
		{
			WLog_ERR(TAG, "MsgWaitForMultipleObjects failure: 0x%08X", GetLastError());
			break;
		}

		if (!freerdp_check_fds(instance))
		{
			WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
			break;
		}

		if (freerdp_shall_disconnect(instance))	
		{
			break;
		}

		if (!freerdp_channels_check_fds(channels, instance))
		{
			WLog_ERR(TAG, "Failed to check channels file descriptor");
			break;
		}
	}

	freerdp_free(instance);

	ExitThread(0);
	return NULL;
}

/**
 * Client Interface
 */

BOOL shw_freerdp_client_global_init(void)
{
	return TRUE;
}

void shw_freerdp_client_global_uninit(void)
{

}

int shw_freerdp_client_start(rdpContext* context)
{
	shwContext* shw;
	freerdp* instance = context->instance;

	shw = (shwContext*) context;

	if (!(shw->thread = CreateThread(NULL, 0,
			(LPTHREAD_START_ROUTINE) shw_client_thread,
			instance, 0, NULL)))
	{
		WLog_ERR(TAG, "Failed to create thread");
		return -1;
	}

	return 0;
}

int shw_freerdp_client_stop(rdpContext* context)
{
	shwContext* shw = (shwContext*) context;

	SetEvent(shw->StopEvent);

	return 0;
}

BOOL shw_freerdp_client_new(freerdp* instance, rdpContext* context)
{
	shwContext* shw;
	rdpSettings* settings;

	shw = (shwContext*) instance->context;

	if (!(shw->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
		return FALSE;

	if (!(context->channels = freerdp_channels_new()))
	{
		CloseHandle(shw->StopEvent);
		shw->StopEvent = NULL;
		return FALSE;
	}

	instance->PreConnect = shw_pre_connect;
	instance->PostConnect = shw_post_connect;
	instance->Authenticate = shw_authenticate;
	instance->VerifyCertificate = shw_verify_certificate;
	instance->VerifyX509Certificate = shw_verify_x509_certificate;

	settings = instance->settings;
	shw->settings = instance->context->settings;

	settings->AsyncTransport = FALSE;
	settings->AsyncChannels = FALSE;
	settings->AsyncUpdate = FALSE;
	settings->AsyncInput = FALSE;

	settings->IgnoreCertificate = TRUE;
	settings->ExternalCertificateManagement = TRUE;

	settings->RdpSecurity = TRUE;
	settings->TlsSecurity = TRUE;
	settings->NlaSecurity = FALSE;

	settings->BitmapCacheEnabled = FALSE;
	settings->BitmapCacheV3Enabled = FALSE;
	settings->OffscreenSupportLevel = FALSE;
	settings->GlyphSupportLevel = GLYPH_SUPPORT_NONE;
	settings->BrushSupportLevel = FALSE;

	ZeroMemory(settings->OrderSupport, 32);

	settings->FrameMarkerCommandEnabled = TRUE;
	settings->SurfaceFrameMarkerEnabled = TRUE;
	settings->AltSecFrameMarkerSupport = TRUE;

	settings->ColorDepth = 32;
	settings->NSCodec = TRUE;
	settings->RemoteFxCodec = TRUE;
	settings->FastPathInput = TRUE;
	settings->FastPathOutput = TRUE;
	settings->LargePointerFlag = TRUE;

	settings->CompressionEnabled = FALSE;

	settings->AutoReconnectionEnabled = FALSE;
	settings->NetworkAutoDetect = FALSE;
	settings->SupportHeartbeatPdu = FALSE;
	settings->SupportMultitransport = FALSE;
	settings->ConnectionType = CONNECTION_TYPE_LAN;

	settings->AllowFontSmoothing = TRUE;
	settings->AllowDesktopComposition = TRUE;
	settings->DisableWallpaper = FALSE;
	settings->DisableFullWindowDrag = TRUE;
	settings->DisableMenuAnims = TRUE;
	settings->DisableThemes = FALSE;

	settings->DeviceRedirection = TRUE;
	settings->RedirectClipboard = TRUE;
	settings->SupportDynamicChannels = TRUE;

	return TRUE;
}

void shw_freerdp_client_free(freerdp* instance, rdpContext* context)
{
	shwContext* shw = (shwContext*) instance->context;
}

int shw_RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
{
	pEntryPoints->Version = 1;
	pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);

	pEntryPoints->settings = NULL;

	pEntryPoints->ContextSize = sizeof(shwContext);
	pEntryPoints->GlobalInit = shw_freerdp_client_global_init;
	pEntryPoints->GlobalUninit = shw_freerdp_client_global_uninit;
	pEntryPoints->ClientNew = shw_freerdp_client_new;
	pEntryPoints->ClientFree = shw_freerdp_client_free;
	pEntryPoints->ClientStart = shw_freerdp_client_start;
	pEntryPoints->ClientStop = shw_freerdp_client_stop;

	return 0;
}

int win_shadow_rdp_init(winShadowSubsystem* subsystem)
{
	rdpContext* context;
	RDP_CLIENT_ENTRY_POINTS clientEntryPoints;

	ZeroMemory(&clientEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
	clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS);
	clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION;

	shw_RdpClientEntry(&clientEntryPoints);

	if (!(subsystem->RdpUpdateEnterEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
		goto fail_enter_event;

	if (!(subsystem->RdpUpdateLeaveEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
		goto fail_leave_event;

	if (!(context = freerdp_client_context_new(&clientEntryPoints)))
		goto fail_context;

	subsystem->shw = (shwContext*) context;
	subsystem->shw->settings = context->settings;
	subsystem->shw->subsystem = subsystem;

	return 1;

fail_context:
	CloseHandle(subsystem->RdpUpdateLeaveEvent);
fail_leave_event:
	CloseHandle(subsystem->RdpUpdateEnterEvent);
fail_enter_event:

	return -1;
}

int win_shadow_rdp_start(winShadowSubsystem* subsystem)
{
	int status;
	shwContext* shw = subsystem->shw;
	rdpContext* context = (rdpContext*) shw;

	status = freerdp_client_start(context);

	return status;
}

int win_shadow_rdp_stop(winShadowSubsystem* subsystem)
{
	int status;
	shwContext* shw = subsystem->shw;
	rdpContext* context = (rdpContext*) shw;

	status = freerdp_client_stop(context);

	return status;
}

int win_shadow_rdp_uninit(winShadowSubsystem* subsystem)
{
	win_shadow_rdp_stop(subsystem);

	return 1;
}
