//
// This file is part of Kepka,
// an unofficial desktop version of Telegram messaging app,
// see https://github.com/procxx/kepka
//
// Kepka is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// It is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// In addition, as a special exception, the copyright holders give permission
// to link the code of portions of this program with the OpenSSL library.
//
// Full license: https://github.com/procxx/kepka/blob/master/LICENSE
// Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
// Copyright (c) 2017- Kepka Contributors, https://github.com/procxx
//
#include "platform/win/notifications_manager_win.h"

#include "base/task_queue.h"
#include "mainwindow.h"
#include "platform/win/windows_app_user_model_id.h"
#include "platform/win/windows_dlls.h"
#include "platform/win/windows_event_filter.h"
#include "window/notifications_utilities.h"

#include <intsafe.h>
#include <strsafe.h>

#include <windows.foundation.collections.h>
#include <windows.ui.notifications.h>
#include <wrl/client.h>
#include <wrl/implements.h>

#include "app.h"
#include "facades.h"
#include "history/history.h"

HICON qt_pixmapToWinHICON(const QPixmap &);

using namespace Microsoft::WRL;
using namespace ABI::Windows::UI::Notifications;
using namespace ABI::Windows::Data::Xml::Dom;
using namespace Windows::Foundation;

namespace Platform {
namespace Notifications {
namespace {

class StringReferenceWrapper {
public:
	StringReferenceWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() {
		HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring);
		if (!SUCCEEDED(hr)) {
			RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
		}
	}

	~StringReferenceWrapper() {
		Dlls::WindowsDeleteString(_hstring);
	}

	template <size_t N> StringReferenceWrapper(_In_reads_(N) wchar_t const (&stringRef)[N]) throw() {
		UINT32 length = N - 1;
		HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring);
		if (!SUCCEEDED(hr)) {
			RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
		}
	}

	template <size_t _> StringReferenceWrapper(_In_reads_(_) wchar_t (&stringRef)[_]) throw() {
		UINT32 length;
		HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length);
		if (!SUCCEEDED(hr)) {
			RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
		}

		Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring);
	}

	HSTRING Get() const throw() {
		return _hstring;
	}

private:
	HSTRING _hstring;
	HSTRING_HEADER _header;
};

template <class T>
_Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T **factory) {
	return Dlls::RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory));
}

template <typename T>
inline HRESULT wrap_GetActivationFactory(_In_ HSTRING activatableClassId,
                                         _Inout_ Details::ComPtrRef<T> factory) throw() {
	return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf());
}

bool init() {
	if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS8) {
		return false;
	}
	if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr) || (Dlls::PropVariantToString == nullptr) ||
	    (Dlls::RoGetActivationFactory == nullptr) || (Dlls::WindowsCreateStringReference == nullptr) ||
	    (Dlls::WindowsDeleteString == nullptr)) {
		return false;
	}

	if (!AppUserModelId::validateShortcut()) {
		return false;
	}

	auto appUserModelId = AppUserModelId::getId();
	if (!SUCCEEDED(Dlls::SetCurrentProcessExplicitAppUserModelID(appUserModelId))) {
		return false;
	}
	return true;
}

HRESULT SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml) {
	ComPtr<IXmlText> inputText;

	HRESULT hr = xml->CreateTextNode(inputString, &inputText);
	if (!SUCCEEDED(hr)) return hr;
	ComPtr<IXmlNode> inputTextNode;

	hr = inputText.As(&inputTextNode);
	if (!SUCCEEDED(hr)) return hr;

	ComPtr<IXmlNode> pAppendedChild;
	return node->AppendChild(inputTextNode.Get(), &pAppendedChild);
}

HRESULT SetAudioSilent(_In_ IXmlDocument *toastXml) {
	ComPtr<IXmlNodeList> nodeList;
	HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList);
	if (!SUCCEEDED(hr)) return hr;

	ComPtr<IXmlNode> audioNode;
	hr = nodeList->Item(0, &audioNode);
	if (!SUCCEEDED(hr)) return hr;

	if (audioNode) {
		ComPtr<IXmlElement> audioElement;
		hr = audioNode.As(&audioElement);
		if (!SUCCEEDED(hr)) return hr;

		hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get());
		if (!SUCCEEDED(hr)) return hr;
	} else {
		ComPtr<IXmlElement> audioElement;
		hr = toastXml->CreateElement(StringReferenceWrapper(L"audio").Get(), &audioElement);
		if (!SUCCEEDED(hr)) return hr;

		hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get());
		if (!SUCCEEDED(hr)) return hr;

		ComPtr<IXmlNode> audioNode;
		hr = audioElement.As(&audioNode);
		if (!SUCCEEDED(hr)) return hr;

		ComPtr<IXmlNodeList> nodeList;
		hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &nodeList);
		if (!SUCCEEDED(hr)) return hr;

		ComPtr<IXmlNode> toastNode;
		hr = nodeList->Item(0, &toastNode);
		if (!SUCCEEDED(hr)) return hr;

		ComPtr<IXmlNode> appendedNode;
		hr = toastNode->AppendChild(audioNode.Get(), &appendedNode);
	}
	return hr;
}

HRESULT SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml) {
	wchar_t imageSrc[MAX_PATH] = L"file:///";
	HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath);
	if (!SUCCEEDED(hr)) return hr;

	ComPtr<IXmlNodeList> nodeList;
	hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList);
	if (!SUCCEEDED(hr)) return hr;

	ComPtr<IXmlNode> imageNode;
	hr = nodeList->Item(0, &imageNode);
	if (!SUCCEEDED(hr)) return hr;

	ComPtr<IXmlNamedNodeMap> attributes;
	hr = imageNode->get_Attributes(&attributes);
	if (!SUCCEEDED(hr)) return hr;

	ComPtr<IXmlNode> srcAttribute;
	hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute);
	if (!SUCCEEDED(hr)) return hr;

	return SetNodeValueString(StringReferenceWrapper(imageSrc).Get(), srcAttribute.Get(), toastXml);
}

typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification *, ::IInspectable *>
    DesktopToastActivatedEventHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification *, ToastDismissedEventArgs *>
    DesktopToastDismissedEventHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification *, ToastFailedEventArgs *>
    DesktopToastFailedEventHandler;

class ToastEventHandler : public Implements<DesktopToastActivatedEventHandler, DesktopToastDismissedEventHandler,
                                            DesktopToastFailedEventHandler> {
public:
	// We keep a weak pointer to a member field of native notifications manager.
	ToastEventHandler(const std::shared_ptr<Manager *> &guarded, const PeerId &peer, MsgId msg)
	    : _peerId(peer)
	    , _msgId(msg)
	    , _weak(guarded) {}
	~ToastEventHandler() = default;

	void performOnMainQueue(FnMut<void(Manager *manager)> task) {
		base::TaskQueue::Main().Put([weak = _weak, task = std::move(task)]() mutable {
			if (auto strong = weak.lock()) {
				task(*strong);
			}
		});
	}

	// DesktopToastActivatedEventHandler
	IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IInspectable *args) {
		performOnMainQueue(
		    [peerId = _peerId, msgId = _msgId](Manager *manager) { manager->notificationActivated(peerId, msgId); });
		return S_OK;
	}

	// DesktopToastDismissedEventHandler
	IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastDismissedEventArgs *e) {
		ToastDismissalReason tdr;
		if (SUCCEEDED(e->get_Reason(&tdr))) {
			switch (tdr) {
			case ToastDismissalReason_ApplicationHidden: break;
			case ToastDismissalReason_UserCanceled:
			case ToastDismissalReason_TimedOut:
			default:
				performOnMainQueue([peerId = _peerId, msgId = _msgId](Manager *manager) {
					manager->clearNotification(peerId, msgId);
				});
				break;
			}
		}
		return S_OK;
	}

	// DesktopToastFailedEventHandler
	IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastFailedEventArgs *e) {
		performOnMainQueue(
		    [peerId = _peerId, msgId = _msgId](Manager *manager) { manager->clearNotification(peerId, msgId); });
		return S_OK;
	}

	// IUnknown
	IFACEMETHODIMP_(ULONG) AddRef() {
		return InterlockedIncrement(&_refCount);
	}

	IFACEMETHODIMP_(ULONG) Release() {
		auto refCount = InterlockedDecrement(&_refCount);
		if (refCount == 0) {
			delete this;
		}
		return refCount;
	}

	IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) {
		if (IsEqualIID(riid, IID_IUnknown))
			*ppv = static_cast<IUnknown *>(static_cast<DesktopToastActivatedEventHandler *>(this));
		else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler)))
			*ppv = static_cast<DesktopToastActivatedEventHandler *>(this);
		else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler)))
			*ppv = static_cast<DesktopToastDismissedEventHandler *>(this);
		else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler)))
			*ppv = static_cast<DesktopToastFailedEventHandler *>(this);
		else
			*ppv = nullptr;

		if (*ppv) {
			reinterpret_cast<IUnknown *>(*ppv)->AddRef();
			return S_OK;
		}

		return E_NOINTERFACE;
	}

private:
	ULONG _refCount = 0;
	PeerId _peerId = 0;
	MsgId _msgId = 0;
	std::weak_ptr<Manager *> _weak;
};

auto Checked = false;
auto InitSucceeded = false;

void Check() {
	InitSucceeded = init();
}

} // namespace

bool Supported() {
	if (!Checked) {
		Checked = true;
		Check();
	}
	return InitSucceeded;
}

std::unique_ptr<Window::Notifications::Manager> Create(Window::Notifications::System *system) {
	if (Global::NativeNotifications() && Supported()) {
		auto result = std::make_unique<Manager>(system);
		if (result->init()) {
			return std::move(result);
		}
	}
	return nullptr;
}

void FlashBounce() {
	auto window = App::wnd();
	if (!window || GetForegroundWindow() == window->psHwnd()) {
		return;
	}

	FLASHWINFO info;
	info.cbSize = sizeof(info);
	info.hwnd = window->psHwnd();
	info.dwFlags = FLASHW_ALL;
	info.dwTimeout = 0;
	info.uCount = 1;
	FlashWindowEx(&info);
}

class Manager::Private {
public:
	using Type = Window::Notifications::CachedUserpics::Type;

	explicit Private(Manager *instance, Type type);
	bool init();

	bool showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle,
	                      const QString &msg, bool hideNameAndPhoto, bool hideReplyButton);
	void clearAll();
	void clearFromHistory(History *history);
	void beforeNotificationActivated(PeerId peerId, MsgId msgId);
	void afterNotificationActivated(PeerId peerId, MsgId msgId);
	void clearNotification(PeerId peerId, MsgId msgId);

	~Private();

private:
	Window::Notifications::CachedUserpics _cachedUserpics;

	std::shared_ptr<Manager *> _guarded;

	ComPtr<IToastNotificationManagerStatics> _notificationManager;
	ComPtr<IToastNotifier> _notifier;
	ComPtr<IToastNotificationFactory> _notificationFactory;

	struct NotificationPtr {
		NotificationPtr() {}
		NotificationPtr(const ComPtr<IToastNotification> &ptr)
		    : p(ptr) {}

		ComPtr<IToastNotification> p;
	};
	QMap<PeerId, QMap<MsgId, NotificationPtr>> _notifications;
};

Manager::Private::Private(Manager *instance, Type type)
    : _guarded(std::make_shared<Manager *>(instance))
    , _cachedUserpics(type) {}

bool Manager::Private::init() {
	if (!SUCCEEDED(wrap_GetActivationFactory(
	        StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(),
	        &_notificationManager))) {
		return false;
	}

	auto appUserModelId = AppUserModelId::getId();
	if (!SUCCEEDED(_notificationManager->CreateToastNotifierWithId(
	        StringReferenceWrapper(appUserModelId, wcslen(appUserModelId)).Get(), &_notifier))) {
		return false;
	}

	if (!SUCCEEDED(wrap_GetActivationFactory(
	        StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(),
	        &_notificationFactory))) {
		return false;
	}
	return true;
}

Manager::Private::~Private() {
	clearAll();

	_notifications.clear();
	if (_notificationManager) _notificationManager.Reset();
	if (_notifier) _notifier.Reset();
	if (_notificationFactory) _notificationFactory.Reset();
}

void Manager::Private::clearAll() {
	if (!_notifier) return;

	auto temp = base::take(_notifications);
	for_const (auto &notifications, temp) {
		for_const (auto &notification, notifications) { _notifier->Hide(notification.p.Get()); }
	}
}

void Manager::Private::clearFromHistory(History *history) {
	if (!_notifier) return;

	auto i = _notifications.find(history->peer->id);
	if (i != _notifications.cend()) {
		auto temp = base::take(i.value());
		_notifications.erase(i);

		for_const (auto &notification, temp) { _notifier->Hide(notification.p.Get()); }
	}
}

void Manager::Private::beforeNotificationActivated(PeerId peerId, MsgId msgId) {
	clearNotification(peerId, msgId);
}

void Manager::Private::afterNotificationActivated(PeerId peerId, MsgId msgId) {
	if (auto window = App::wnd()) {
		SetForegroundWindow(window->psHwnd());
	}
}

void Manager::Private::clearNotification(PeerId peerId, MsgId msgId) {
	auto i = _notifications.find(peerId);
	if (i != _notifications.cend()) {
		i.value().remove(msgId);
		if (i.value().isEmpty()) {
			_notifications.erase(i);
		}
	}
}

bool Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle,
                                        const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
	if (!_notificationManager || !_notifier || !_notificationFactory) return false;

	ComPtr<IXmlDocument> toastXml;
	bool withSubtitle = !subtitle.isEmpty();

	HRESULT hr = _notificationManager->GetTemplateContent(
	    withSubtitle ? ToastTemplateType_ToastImageAndText04 : ToastTemplateType_ToastImageAndText02, &toastXml);
	if (!SUCCEEDED(hr)) return false;

	hr = SetAudioSilent(toastXml.Get());
	if (!SUCCEEDED(hr)) return false;

	StorageKey key;
	if (hideNameAndPhoto) {
		key = StorageKey(0, 0);
	} else {
		key = peer->userpicUniqueKey();
	}
	auto userpicPath = _cachedUserpics.get(key, peer);
	auto userpicPathWide = QDir::toNativeSeparators(userpicPath).toStdWString();

	hr = SetImageSrc(userpicPathWide.c_str(), toastXml.Get());
	if (!SUCCEEDED(hr)) return false;

	ComPtr<IXmlNodeList> nodeList;
	hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList);
	if (!SUCCEEDED(hr)) return false;

	UINT32 nodeListLength;
	hr = nodeList->get_Length(&nodeListLength);
	if (!SUCCEEDED(hr)) return false;

	if (nodeListLength < (withSubtitle ? 3U : 2U)) return false;

	{
		ComPtr<IXmlNode> textNode;
		hr = nodeList->Item(0, &textNode);
		if (!SUCCEEDED(hr)) return false;

		std::wstring wtitle = title.toStdWString();
		hr = SetNodeValueString(StringReferenceWrapper(wtitle.data(), wtitle.size()).Get(), textNode.Get(),
		                        toastXml.Get());
		if (!SUCCEEDED(hr)) return false;
	}
	if (withSubtitle) {
		ComPtr<IXmlNode> textNode;
		hr = nodeList->Item(1, &textNode);
		if (!SUCCEEDED(hr)) return false;

		std::wstring wsubtitle = subtitle.toStdWString();
		hr = SetNodeValueString(StringReferenceWrapper(wsubtitle.data(), wsubtitle.size()).Get(), textNode.Get(),
		                        toastXml.Get());
		if (!SUCCEEDED(hr)) return false;
	}
	{
		ComPtr<IXmlNode> textNode;
		hr = nodeList->Item(withSubtitle ? 2 : 1, &textNode);
		if (!SUCCEEDED(hr)) return false;

		std::wstring wmsg = msg.toStdWString();
		hr = SetNodeValueString(StringReferenceWrapper(wmsg.data(), wmsg.size()).Get(), textNode.Get(), toastXml.Get());
		if (!SUCCEEDED(hr)) return false;
	}

	ComPtr<IToastNotification> toast;
	hr = _notificationFactory->CreateToastNotification(toastXml.Get(), &toast);
	if (!SUCCEEDED(hr)) return false;

	EventRegistrationToken activatedToken, dismissedToken, failedToken;
	ComPtr<ToastEventHandler> eventHandler(new ToastEventHandler(_guarded, peer->id, msgId));

	hr = toast->add_Activated(eventHandler.Get(), &activatedToken);
	if (!SUCCEEDED(hr)) return false;

	hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken);
	if (!SUCCEEDED(hr)) return false;

	hr = toast->add_Failed(eventHandler.Get(), &failedToken);
	if (!SUCCEEDED(hr)) return false;

	auto i = _notifications.find(peer->id);
	if (i != _notifications.cend()) {
		auto j = i->find(msgId);
		if (j != i->cend()) {
			ComPtr<IToastNotification> notify = j->p;
			i->erase(j);
			_notifier->Hide(notify.Get());
			i = _notifications.find(peer->id);
		}
	}
	if (i == _notifications.cend()) {
		i = _notifications.insert(peer->id, QMap<MsgId, NotificationPtr>());
	}
	hr = _notifier->Show(toast.Get());
	if (!SUCCEEDED(hr)) {
		i = _notifications.find(peer->id);
		if (i != _notifications.cend() && i->isEmpty()) _notifications.erase(i);
		return false;
	}
	_notifications[peer->id].insert(msgId, toast);

	return true;
}

Manager::Manager(Window::Notifications::System *system)
    : NativeManager(system)
    , _private(std::make_unique<Private>(this, Private::Type::Rounded)) {}

bool Manager::init() {
	return _private->init();
}

void Manager::clearNotification(PeerId peerId, MsgId msgId) {
	_private->clearNotification(peerId, msgId);
}

Manager::~Manager() = default;

void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle,
                                       const QString &msg, bool hideNameAndPhoto, bool hideReplyButton) {
	_private->showNotification(peer, msgId, title, subtitle, msg, hideNameAndPhoto, hideReplyButton);
}

void Manager::doClearAllFast() {
	_private->clearAll();
}

void Manager::doClearFromHistory(History *history) {
	_private->clearFromHistory(history);
}

void Manager::onBeforeNotificationActivated(PeerId peerId, MsgId msgId) {
	_private->beforeNotificationActivated(peerId, msgId);
}

void Manager::onAfterNotificationActivated(PeerId peerId, MsgId msgId) {
	_private->afterNotificationActivated(peerId, msgId);
}

namespace {

bool QuietHoursEnabled = false;
DWORD QuietHoursValue = 0;

// Thanks https://stackoverflow.com/questions/35600128/get-windows-quiet-hours-from-win32-or-c-sharp-api
void queryQuietHours() {
	if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS10) {
		// There are quiet hours in Windows starting from Windows 8.1
		// But there were several reports about the notifications being shut
		// down according to the registry while no quiet hours were enabled.
		// So we try this method only starting with Windows 10.
		return;
	}

	LPCWSTR lpKeyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Notifications\\Settings";
	LPCWSTR lpValueName = L"NOC_GLOBAL_SETTING_TOASTS_ENABLED";
	HKEY key;
	auto result = RegOpenKeyEx(HKEY_CURRENT_USER, lpKeyName, 0, KEY_READ, &key);
	if (result != ERROR_SUCCESS) {
		return;
	}

	DWORD value = 0, type = 0, size = sizeof(value);
	result = RegQueryValueEx(key, lpValueName, 0, &type, (LPBYTE)&value, &size);
	RegCloseKey(key);

	auto quietHoursEnabled = (result == ERROR_SUCCESS) && (value == 0);
	if (QuietHoursEnabled != quietHoursEnabled) {
		QuietHoursEnabled = quietHoursEnabled;
		QuietHoursValue = value;
		LOG(("Quiet hours changed, entry value: %1").arg(value));
	} else if (QuietHoursValue != value) {
		QuietHoursValue = value;
		LOG(("Quiet hours value changed, was value: %1, entry value: %2").arg(QuietHoursValue).arg(value));
	}
}

QUERY_USER_NOTIFICATION_STATE UserNotificationState = QUNS_ACCEPTS_NOTIFICATIONS;

void queryUserNotificationState() {
	if (Dlls::SHQueryUserNotificationState != nullptr) {
		QUERY_USER_NOTIFICATION_STATE state;
		if (SUCCEEDED(Dlls::SHQueryUserNotificationState(&state))) {
			UserNotificationState = state;
		}
	}
}

static constexpr auto kQuerySettingsEachMs = 1000;
TimeMs LastSettingsQueryMs = 0;

void querySystemNotificationSettings() {
	auto ms = getms(true);
	if (LastSettingsQueryMs > 0 && ms <= LastSettingsQueryMs + kQuerySettingsEachMs) {
		return;
	}
	LastSettingsQueryMs = ms;
	queryQuietHours();
	queryUserNotificationState();
}

} // namespace

bool SkipAudio() {
	querySystemNotificationSettings();

	if (UserNotificationState == QUNS_NOT_PRESENT || UserNotificationState == QUNS_PRESENTATION_MODE) {
		return true;
	}
	if (QuietHoursEnabled) {
		return true;
	}
	if (EventFilter::getInstance()->sessionLoggedOff()) {
		return true;
	}
	return false;
}

bool SkipToast() {
	querySystemNotificationSettings();

	if (UserNotificationState == QUNS_PRESENTATION_MODE ||
	    UserNotificationState == QUNS_RUNNING_D3D_FULL_SCREEN /* || UserNotificationState == QUNS_BUSY*/) {
		return true;
	}
	if (QuietHoursEnabled) {
		return true;
	}
	return false;
}

} // namespace Notifications
} // namespace Platform