#include "SuspendHandler.h"

#include <QtGlobal>

#include <hyperion/HyperionIManager.h>
#include <iostream>

SuspendHandlerBase::SuspendHandlerBase()
	: _isSuspended(false)
	, _isIdle(false)
	, _isLocked (false)
{
	// Trigger suspend/resume/idle scenarios to be executed by Instance mMnager
	connect(this, &SuspendHandlerBase::suspendEvent, HyperionIManager::getInstance(), &HyperionIManager::suspend);
	connect(this, &SuspendHandlerBase::resumeEvent, HyperionIManager::getInstance(), &HyperionIManager::resume);
	connect(this, &SuspendHandlerBase::lockedEvent, HyperionIManager::getInstance(), &HyperionIManager::toggleIdle);
	connect(this, &SuspendHandlerBase::idleEvent, HyperionIManager::getInstance(), &HyperionIManager::toggleIdle);


	// Listen to suspend/resume/idle events received by the Instance Manager via API
	connect(HyperionIManager::getInstance(), &HyperionIManager::triggerSuspend, this, QOverload<bool>::of(&SuspendHandler::suspend));
	connect(HyperionIManager::getInstance(), &HyperionIManager::triggerToggleSuspend, this, &SuspendHandler::toggleSuspend);
	connect(HyperionIManager::getInstance(), &HyperionIManager::triggerIdle, this, &SuspendHandler::idle);
	connect(HyperionIManager::getInstance(), &HyperionIManager::triggerToggleIdle, this, &SuspendHandler::toggleIdle);
}

SuspendHandlerBase::~SuspendHandlerBase()
{
}

void SuspendHandlerBase::suspend()
{
	Debug(Logger::getInstance("DAEMON"), "");
	suspend(true);
}

void SuspendHandlerBase::suspend(bool sleep)
{
	if (sleep)
	{
		if (!_isSuspended)
		{
			_isSuspended = true;
			Info(Logger::getInstance("DAEMON"), "Suspend event received - Hyperion is going to sleep");
			emit suspendEvent();
		}
		else
		{
			Debug(Logger::getInstance("DAEMON"), "Suspend event ignored - already suspended");
		}
	}
	else
	{
		if (_isSuspended || _isIdle)
		{
			Info(Logger::getInstance("DAEMON"), "Resume event received - Hyperion is going into working mode");
			emit resumeEvent();
			_isSuspended = false;
			_isIdle = false;
		}
		else
		{
			Debug(Logger::getInstance("DAEMON"), "Resume event ignored - not in suspend nor idle mode");
		}
	}
}

void SuspendHandlerBase::resume()
{
	Debug(Logger::getInstance("DAEMON"), "");
	suspend(false);
}

void SuspendHandlerBase::toggleSuspend()
{
	Debug(Logger::getInstance("DAEMON"), "Toggle suspend event received");
	if (!_isSuspended)
	{
		suspend(true);
	}
	else
	{
		suspend(false);
	}
}

void SuspendHandlerBase::idle(bool isIdle)
{
	if (!_isSuspended)
	{
		if (isIdle)
		{
			if (!_isIdle)
			{
				_isIdle = true;
				Info(Logger::getInstance("DAEMON"), "Idle event received");
				emit idleEvent(isIdle);
			}
		}
		else
		{
			if (_isIdle)
			{
				Info(Logger::getInstance("DAEMON"), "Resume from idle event recevied");
				emit idleEvent(isIdle);
				_isIdle = false;
			}
		}
	}
	else
	{
		Debug(Logger::getInstance("DAEMON"), "Idle event ignored - Hyperion is suspended");
	}
}

void SuspendHandlerBase::toggleIdle()
{
	Debug(Logger::getInstance("DAEMON"), "Toggle idle event received");
	if (!_isIdle)
	{
		idle(true);
	}
	else
	{
		idle(false);
	}
}

void SuspendHandlerBase::lock(bool isLocked)
{
	if (!_isSuspended)
	{
		if (isLocked)
		{
			if (!_isLocked)
			{
				_isLocked = true;
				Info(Logger::getInstance("DAEMON"), "Screen lock event received");
				emit lockedEvent(isLocked);
			}
		}
		else
		{
			if (_isLocked)
			{
				Info(Logger::getInstance("DAEMON"), "Screen unlock event received");
				emit lockedEvent(isLocked);
				_isLocked = false;
			}
		}
	}
	else
	{
		Debug(Logger::getInstance("DAEMON"), "Screen lock event ignored - Hyperion is suspended");
	}
}

#if defined(_WIN32)
#include <QCoreApplication>
#include <QWidget>
#include <windows.h>
#include <wtsapi32.h>

#pragma comment( lib, "wtsapi32.lib" )

SuspendHandlerWindows::SuspendHandlerWindows()
{
	auto handle = reinterpret_cast<HWND> (_widget.winId());

	_notifyHandle = RegisterSuspendResumeNotification(handle, DEVICE_NOTIFY_WINDOW_HANDLE);
	if (_notifyHandle != NULL)
	{
		QCoreApplication::instance()->installNativeEventFilter(this);
	}
	else
	{
		Error(Logger::getInstance("DAEMON"), "Could not register for suspend/resume events!");
	}

	if (!WTSRegisterSessionNotification(handle, NOTIFY_FOR_THIS_SESSION))
	{
		Error(Logger::getInstance("DAEMON"), "Could not register for lock/unlock events!");
	}
}

SuspendHandlerWindows::~SuspendHandlerWindows()
{
	if (_notifyHandle != NULL)
	{
		QCoreApplication::instance()->removeNativeEventFilter(this);

		UnregisterSuspendResumeNotification(_notifyHandle);

		auto handle = reinterpret_cast<HWND> (_widget.winId());
		WTSUnRegisterSessionNotification(handle);
	}
	_notifyHandle = NULL;
}

#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
bool SuspendHandlerWindows::nativeEventFilter(const QByteArray& eventType, void* message, qintptr* /*result*/)
#else
bool SuspendHandlerWindows::nativeEventFilter(const QByteArray& eventType, void* message, long int* /*result*/)
#endif
{
	MSG* msg = static_cast<MSG*>(message);

	switch (msg->message)
	{
	case WM_WTSSESSION_CHANGE:
		switch (msg->wParam)
		{
		case WTS_SESSION_LOCK:
			emit lock(true);
			return true;
			break;
		case WTS_SESSION_UNLOCK:
			emit lock(false);
			return true;
			break;
		}
		break;

	case WM_POWERBROADCAST:
		switch (msg->wParam)
		{
		case PBT_APMRESUMESUSPEND:
			emit suspend(false);
			return true;
			break;
		case PBT_APMSUSPEND:
			emit suspend(true);
			return true;
			break;
		}
		break;
	}
	return false;
}

#elif defined(__linux__) && defined(HYPERION_HAS_DBUS)
#include <QDBusConnection>

struct dBusSignals
{
	QString service;
	QString path;
	QString interface;
	QString name;
};

typedef QMultiMap<QString, dBusSignals> DbusSignalsMap;

// Constants
namespace {
const DbusSignalsMap dbusSignals = {
	//system signals
	{"Suspend", {"org.freedesktop.login1","/org/freedesktop/login1","org.freedesktop.login1.Manager","PrepareForSleep"}},

	//Session signals
	{"ScreenSaver", {"org.freedesktop.ScreenSaver","/org/freedesktop/ScreenSaver","org.freedesktop.ScreenSaver","ActiveChanged"}},
	{"ScreenSaver", {"org.gnome.ScreenSaver","/org/gnome/ScreenSaver","org.gnome.ScreenSaver","ActiveChanged"}},
};
} //End of constants

SuspendHandlerLinux::SuspendHandlerLinux()
{
	QDBusConnection systemBus = QDBusConnection::systemBus();
	if (!systemBus.isConnected())
	{
		Info(Logger::getInstance("DAEMON"), "The suspend/resume feature is not supported by your system configuration");
	}
	else
	{
		QString service = dbusSignals.find("Suspend").value().service;
		if (!systemBus.connect(service,
							   dbusSignals.find("Suspend").value().path,
							   dbusSignals.find("Suspend").value().interface,
							   dbusSignals.find("Suspend").value().name,
							   this, SLOT(suspend(bool))))
			Error(Logger::getInstance("DAEMON"), "Could not register for suspend/resume events [%s]!", QSTRING_CSTR(service));
		else
		{
			Debug(Logger::getInstance("DAEMON"), "Registered for suspend/resume events [%s].", QSTRING_CSTR(service));
		}
	}

	QDBusConnection sessionBus = QDBusConnection::sessionBus();
	if (!sessionBus.isConnected())
	{
		Info(Logger::getInstance("DAEMON"), "The lock/unlock feature is not supported by your system configuration");
	}
	else
	{
		DbusSignalsMap::const_iterator iter = dbusSignals.find("ScreenSaver");
		while (iter != dbusSignals.end() && iter.key() == "ScreenSaver") {
			QString service = iter.value().service;
			if (!sessionBus.connect(service,
									iter.value().path,
									iter.value().interface,
									iter.value().name,
									this, SLOT(lock(bool))))
				Error(Logger::getInstance("DAEMON"), "Could not register for lock/unlock events [%s]!", QSTRING_CSTR(service));
			else
			{
				Debug(Logger::getInstance("DAEMON"), "Registered for lock/unlock events [%s].", QSTRING_CSTR(service));
			}
			++iter;
		}
	}
}
#endif
