/*
Copyright 2016 Peter Repukat - FlatspotSoftware

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 "SteamTargetRenderer.h"

ULONG SteamTargetRenderer::ulTargetSerials[XUSER_MAX_COUNT];

SteamTargetRenderer::SteamTargetRenderer()
{
	iRealControllers = getRealControllers();

	getSteamOverlay();

	openUserWindow();
#ifndef NDEBUG
	bDrawDebugEdges = true;
#endif // NDEBUG

	QSettings settings(".\\TargetConfig.ini", QSettings::IniFormat);
	settings.beginGroup("BaseConf");
	const QStringList childKeys = settings.childKeys();
	for (auto &childkey : childKeys)
	{
		if (childkey == "bDrawDebugEdges")
		{
			bDrawDebugEdges = settings.value(childkey).toBool();
		} else if (childkey == "bShowDebugConsole") {
			bShowDebugConsole = settings.value(childkey).toBool();
		} else if (childkey == "bEnableOverlay") {
			bDrawOverlay = settings.value(childkey).toBool();
		} else if (childkey == "bEnableControllers") {
			bPauseControllers = !settings.value(childkey).toBool();
		} else if (childkey == "bEnableVsync") {
			bVsync = settings.value(childkey).toBool();
		} else if (childkey == "iRefreshRate") {
			iRefreshRate = settings.value(childkey).toInt();
		}
	}
	settings.endGroup();

	sfCshape = sf::CircleShape(100.f);
	sfCshape.setFillColor(sf::Color(128, 128, 128, 128));
	sfCshape.setOrigin(sf::Vector2f(100, 100));
	sf::VideoMode mode = sf::VideoMode::getDesktopMode();
	sfWindow.create(sf::VideoMode(mode.width-16, mode.height-32), "OverlayWindow"); //Window is too large ; always 16 and 32 pixels?  - sf::Style::None breaks transparency!
	sfWindow.setVerticalSyncEnabled(bVsync);
	if (!bVsync)
		sfWindow.setFramerateLimit(iRefreshRate);
	sfWindow.setPosition(sf::Vector2i(0, 0));
	makeSfWindowTransparent(sfWindow);

	sfWindow.setActive(false);
	consoleHwnd = GetConsoleWindow(); //We need a console for a dirty hack to make sure we stay in game bindings - Also useful for debugging

	LONG_PTR style = GetWindowLongPtr(consoleHwnd, GWL_STYLE); 
	SetWindowLongPtr(consoleHwnd, GWL_STYLE, style & ~WS_SYSMENU);

	if(!bShowDebugConsole) {
		ShowWindow(consoleHwnd, SW_HIDE); //Hide the console window; it just confuses the user;
	}

	if (!VIGEM_SUCCESS(vigem_init()))
	{
		std::cout << "Error initializing ViGem!" << std::endl;
		bRunLoop = false;
	}


	VIGEM_TARGET vtX360[XUSER_MAX_COUNT];
	for (int i = 0; i < XUSER_MAX_COUNT; i++)
	{
		VIGEM_TARGET_INIT(&vtX360[i]);
		SteamTargetRenderer::ulTargetSerials[i] = NULL;
	}

	QTimer::singleShot(2000, this, &SteamTargetRenderer::launchApp); // lets steam do its thing

}

SteamTargetRenderer::~SteamTargetRenderer()
{	
	bRunLoop = false;
	renderThread.join();
	for (int i = 0; i < XUSER_MAX_COUNT; i++)
	{
		vigem_target_unplug(&vtX360[i]);

	}
	vigem_shutdown();
	qpUserWindow->kill();
	delete qpUserWindow;
}

void SteamTargetRenderer::run()
{
	renderThread = std::thread(&SteamTargetRenderer::RunSfWindowLoop, this);
}

void SteamTargetRenderer::controllerCallback(VIGEM_TARGET Target, UCHAR LargeMotor, UCHAR SmallMotor, UCHAR LedNumber)
{
	std::cout << "Target Serial: " << Target.SerialNo
		<< "; LMotor: " << (unsigned int)(LargeMotor * 0xff) << "; "
		<< " SMotor: " << (unsigned int)(SmallMotor * 0xff) << "; " << std::endl;

	XINPUT_VIBRATION vibration;
	ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));
	vibration.wLeftMotorSpeed = LargeMotor * 0xff; 
	vibration.wRightMotorSpeed = SmallMotor * 0xff; 


	for (int i = 0; i < XUSER_MAX_COUNT; i++)
	{
		if (SteamTargetRenderer::ulTargetSerials[i] == Target.SerialNo)
		{
			XInputSetState(i, &vibration);
		}
	}
}

void SteamTargetRenderer::RunSfWindowLoop()
{
	if (!bRunLoop)
		return;
	sfWindow.setActive(true);
	DWORD result;
	bool focusSwitchNeeded = true;
	sf::Clock reCheckControllerTimer;

	if (!bDrawOverlay)
	{
		ShowWindow(consoleHwnd, SW_HIDE);
	}

	while (sfWindow.isOpen() && bRunLoop)
	{
		if (bDrawOverlay)
		{
			SetWindowPos(sfWindow.getSystemHandle(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
		}

		sf::Event event;
		while (sfWindow.pollEvent(event))
		{
			if (event.type == sf::Event::Closed)
				sfWindow.close();
		}

		sfWindow.clear(sf::Color::Transparent);

		if (bDrawDebugEdges)
			drawDebugEdges();

		sfWindow.display();

		if (!bPauseControllers)
		{

			if (reCheckControllerTimer.getElapsedTime().asSeconds() >= 1.f)
			{
				int totalbefore = iTotalControllers;
				iTotalControllers = 0;
				for (int i = 0; i < XUSER_MAX_COUNT; i++)
				{
					ZeroMemory(&xsState[i], sizeof(XINPUT_STATE));

					result = XInputGetState(i, &xsState[i]);

					if (result == ERROR_SUCCESS)
					{
						iTotalControllers++;
					}
					else {
						break;
					}
				}
				iTotalControllers -= iVirtualControllers;
				reCheckControllerTimer.restart();
			}

			for (int i = iRealControllers; i < iTotalControllers && i < XUSER_MAX_COUNT; i++)
			{
				////////
				ZeroMemory(&xsState[i], sizeof(XINPUT_STATE));


				result = XInputGetState(i, &xsState[i]);

				if (result == ERROR_SUCCESS)
				{

					if (VIGEM_SUCCESS(vigem_target_plugin(Xbox360Wired, &vtX360[i])))
					{
						iVirtualControllers++;

						std::cout << "Plugged in controller " << vtX360[i].SerialNo << std::endl;

						SteamTargetRenderer::ulTargetSerials[i] = vtX360[i].SerialNo;

						vigem_register_xusb_notification((PVIGEM_XUSB_NOTIFICATION)&SteamTargetRenderer::controllerCallback, vtX360[i]);
					}

					RtlCopyMemory(&xrReport[i], &xsState[i].Gamepad, sizeof(XUSB_REPORT));

					vigem_xusb_submit_report(vtX360[i], xrReport[i]);
				}
				else
				{
					if (VIGEM_SUCCESS(vigem_target_unplug(&vtX360[i])))
					{
						iVirtualControllers--;
						iTotalControllers = 0;
						for (int i = 0; i < XUSER_MAX_COUNT; i++)
						{
							ZeroMemory(&xsState[i], sizeof(XINPUT_STATE));

							result = XInputGetState(i, &xsState[i]);

							if (result == ERROR_SUCCESS)
							{
								iTotalControllers++;
							}
							else {
								break;
							}
						}
						iTotalControllers -= iVirtualControllers;
						std::cout << "Unplugged controller " << vtX360[i].SerialNo << std::endl;
						SteamTargetRenderer::ulTargetSerials[i] = NULL;
					}
				}
			}


			//This ensures that we stay in game binding, even if focused application changes! (Why does this work? Well, i dunno... ask Valve...)
			//Only works with a console window
			//Causes trouble as soon as there is more than the consoleWindow and the overlayWindow
			//This is trying to avoid hooking Steam.exe
			if (focusSwitchNeeded)
			{
				focusSwitchNeeded = false;
				SetFocus(consoleHwnd);
				SetForegroundWindow(consoleHwnd);
			}

			//Dirty hack to make the steamoverlay work properly and still keep Apps Controllerconfig when closing overlay.
			//This is trying to avoid hooking Steam.exe
			if (overlayPtr != NULL)
			{
				char overlayOpen = *(char*)overlayPtr;
				if (overlayOpen)
				{
					if (!bNeedFocusSwitch)
					{
						bNeedFocusSwitch = true;

						hwForeGroundWindow = GetForegroundWindow();

						std::cout << "ForegorundWindow HWND: " << hwForeGroundWindow << std::endl;

						SetFocus(consoleHwnd);
						SetForegroundWindow(consoleHwnd);

						SetFocus(sfWindow.getSystemHandle());
						SetForegroundWindow(sfWindow.getSystemHandle());

						SetWindowLong(sfWindow.getSystemHandle(), GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW);

						SetFocus(consoleHwnd);
						SetForegroundWindow(consoleHwnd);
					}
				}
				else {
					if (bNeedFocusSwitch)
					{

						SetFocus(sfWindow.getSystemHandle());
						SetForegroundWindow(sfWindow.getSystemHandle());

						SetWindowLong(sfWindow.getSystemHandle(), GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_TOOLWINDOW);

						SetFocus(consoleHwnd);
						SetForegroundWindow(consoleHwnd);

						SetFocus(hwForeGroundWindow);
						SetForegroundWindow(hwForeGroundWindow);

						bNeedFocusSwitch = false;
					}
				}
			}
		}
	}
}

void SteamTargetRenderer::getSteamOverlay()
{
	hmodGameOverlayRenderer = GetModuleHandle(L"Gameoverlayrenderer64.dll");

	if (hmodGameOverlayRenderer != NULL)
	{
		std::cout << "GameOverlayrenderer64.dll found;  Module at: 0x" << hmodGameOverlayRenderer << std::endl;
		overlayPtr = (uint64_t*)(uint64_t(hmodGameOverlayRenderer) + 0x1365e8);
		overlayPtr = (uint64_t*)(*overlayPtr + 0x40);
	}
}

int SteamTargetRenderer::getRealControllers()
{
	int realControllers = 0;
	UINT numDevices = NULL;

	GetRawInputDeviceList(NULL, &numDevices, sizeof(RAWINPUTDEVICELIST));

	PRAWINPUTDEVICELIST rawInputDeviceList;
	rawInputDeviceList = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * numDevices);
	GetRawInputDeviceList(rawInputDeviceList, &numDevices, sizeof(RAWINPUTDEVICELIST));

	for (unsigned int i = 0; i < numDevices; i++)
	{
		RID_DEVICE_INFO devInfo;
		devInfo.cbSize = sizeof(RID_DEVICE_INFO);
		GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &devInfo, (PUINT)&devInfo.cbSize);
		if (devInfo.hid.dwVendorId == 0x45e && devInfo.hid.dwProductId == 0x28e)
		{
			realControllers++;
		}

	}

	free(rawInputDeviceList);
	std::cout << "Detected " << realControllers << " real connected X360 Controllers" << std::endl;
	return realControllers;
}


void SteamTargetRenderer::makeSfWindowTransparent(sf::RenderWindow & window)
{
	HWND hwnd = window.getSystemHandle();
	SetWindowLong(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP &~WS_CAPTION);
	SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_TOOLWINDOW);

	MARGINS margins;
	margins.cxLeftWidth = -1;

	DwmExtendFrameIntoClientArea(hwnd, &margins);
	SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);

	window.clear(sf::Color::Transparent);
	window.display();
}

void SteamTargetRenderer::drawDebugEdges()
{
	sfCshape.setPosition(sf::Vector2f(-25, -25));
	sfWindow.draw(sfCshape);
	sfCshape.setPosition(sf::Vector2f(sfWindow.getSize().x + 25, -25));
	sfWindow.draw(sfCshape);
	sfCshape.setPosition(sf::Vector2f(-25, sfWindow.getSize().y));
	sfWindow.draw(sfCshape);
	sfCshape.setPosition(sf::Vector2f(sfWindow.getSize().x, sfWindow.getSize().y));
	sfWindow.draw(sfCshape);

}

void SteamTargetRenderer::openUserWindow()
{
	qpUserWindow = new QProcess(this);
	qpUserWindow->start("SteamTargetUserWindow.exe", QStringList(), QProcess::ReadWrite);
	qpUserWindow->waitForStarted();
	connect(qpUserWindow, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
		this, &SteamTargetRenderer::userWindowFinished);
	connect(qpUserWindow, SIGNAL(readyRead()) , this,SLOT(readChildProcess()));
}

void SteamTargetRenderer::userWindowFinished()
{
	delete qpUserWindow;
	bRunLoop = false;
	renderThread.join();
	for (int i = 0; i < XUSER_MAX_COUNT; i++)
	{
		vigem_target_unplug(&vtX360[i]);

	}
	vigem_shutdown();
	exit(0);
}

void SteamTargetRenderer::launchApp()
{

	bool launchGame = false;
	QString type = "Win32";
	QString path = "";
	QSettings settings(".\\TargetConfig.ini", QSettings::IniFormat);
	settings.beginGroup("LaunchGame");
	const QStringList childKeys = settings.childKeys();
	for (auto &childkey : childKeys)
	{
		if (childkey == "bLaunchGame")
		{
			launchGame = settings.value(childkey).toBool();
		}
		else if (childkey == "Type") {
			type = settings.value(childkey).toString();
		}
		else if (childkey == "Path") {
			path = settings.value(childkey).toString();
		}
	}
	settings.endGroup();

	if (launchGame)
	{
		QSharedMemory sharedMemInstance("GloSC_GameLauncher");
		if (!sharedMemInstance.create(1024) && sharedMemInstance.error() == QSharedMemory::AlreadyExists)
		{
			QStringList stringList;
			if (type == "Win32")
			{
				stringList << "LaunchWin32Game";
			} else if (type == "UWP") {
				stringList << "LaunchUWPGame";

				/*bool drawOverlayBefore = bDrawOverlay;
				bDrawOverlay = false;
				QTimer::singleShot(5000, [=] {
					bDrawOverlay = drawOverlayBefore;
				});*/

			}
			stringList << path;

			QBuffer buffer;
			buffer.open(QBuffer::ReadWrite);
			QDataStream out(&buffer);
			out << stringList;
			int size = buffer.size();

			sharedMemInstance.attach();
			char *to = (char*)sharedMemInstance.data();
			const char *from = buffer.data().data();
			memcpy(to, from, qMin(sharedMemInstance.size(), size));
			sharedMemInstance.unlock();
			sharedMemInstance.detach();
		}
	}
}

void SteamTargetRenderer::readChildProcess()
{
	QString message(qpUserWindow->readLine());
	if (message.contains("ResetControllers"))
	{
		bPauseControllers = true;
		for (int i = 0; i < XUSER_MAX_COUNT; i++)
		{
			vigem_target_unplug(&vtX360[i]);
			SteamTargetRenderer::ulTargetSerials[i] = NULL;
		}
		Sleep(1000); //give the driver time to unplug before redetecting
		iRealControllers = 0;
		iTotalControllers = 0;
		iVirtualControllers = 0;
		iRealControllers = getRealControllers();
		bPauseControllers = false;
	} else if (message.contains("ShowConsole")) {
		message.chop(1);
		message.remove("ShowConsole ");
		int showConsole = message.toInt();
		if (showConsole > 0)
		{
			bShowDebugConsole = true;
			ShowWindow(consoleHwnd, SW_SHOW);
			SetFocus(consoleHwnd);
			SetForegroundWindow(consoleHwnd);
		} else {
			bShowDebugConsole = false;
			ShowWindow(consoleHwnd, SW_HIDE);
			SetFocus(consoleHwnd);
			SetForegroundWindow(consoleHwnd);
		}
	} else if (message.contains("ShowOverlay")) {
		message.chop(1);
		message.remove("ShowOverlay ");
		int showOverlay = message.toInt();
		if (showOverlay > 0)
		{
			ShowWindow(sfWindow.getSystemHandle(), SW_SHOW);
			SetFocus(consoleHwnd);
			SetForegroundWindow(consoleHwnd);
		} else {
			ShowWindow(sfWindow.getSystemHandle(), SW_HIDE);
			SetFocus(consoleHwnd);
			SetForegroundWindow(consoleHwnd);
		}
	} else if (message.contains("EnableControllers")) {
		message.chop(1);
		message.remove("EnableControllers ");
		int enableControllers = message.toInt();
		if (enableControllers > 0)
		{
			for (int i = 0; i < XUSER_MAX_COUNT; i++)
			{
				vigem_target_unplug(&vtX360[i]);
				SteamTargetRenderer::ulTargetSerials[i] = NULL;
			}
			Sleep(1000); //give the driver time to unplug before redetecting
			iRealControllers = 0;
			iTotalControllers = 0;
			iVirtualControllers = 0;
			iRealControllers = getRealControllers();
			bPauseControllers = false;
		} else {
			bPauseControllers = true;
			for (int i = 0; i < XUSER_MAX_COUNT; i++)
			{
				vigem_target_unplug(&vtX360[i]);
				SteamTargetRenderer::ulTargetSerials[i] = NULL;
			}
			Sleep(1000); //give the driver time to unplug before redetecting
			iRealControllers = 0;
			iVirtualControllers = 0;
			iRealControllers = getRealControllers();
		}
	}
}
