﻿/** 
 * MIT License
 *
 * Copyright (C) 2016-2023 WinToast v1.3.0 - Mohammed Boujemaoui <mohabouje@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef WINTOASTLIB_H
#define WINTOASTLIB_H

#include <Windows.h>
#include <sdkddkver.h>
#include <WinUser.h>
#include <ShObjIdl.h>
#include <wrl/implements.h>
#include <wrl/event.h>
#include <windows.ui.notifications.h>
#include <strsafe.h>
#include <Psapi.h>
#include <ShlObj.h>
#include <roapi.h>
#include <propvarutil.h>
#include <functiondiscoverykeys.h>
#include <iostream>
#include <winstring.h>
#include <string.h>
#include <vector>
#include <map>
#include <memory>

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

namespace WinToastLib {

    class IWinToastHandler {
    public:
        enum WinToastDismissalReason {
            UserCanceled      = ToastDismissalReason::ToastDismissalReason_UserCanceled,
            ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
            TimedOut          = ToastDismissalReason::ToastDismissalReason_TimedOut
        };

        virtual ~IWinToastHandler()                                      = default;
        virtual void toastActivated() const                              = 0;
        virtual void toastActivated(int actionIndex) const               = 0;
        virtual void toastDismissed(WinToastDismissalReason state) const = 0;
        virtual void toastFailed() const                                 = 0;
    };

    class WinToastTemplate {
    public:
        enum class Scenario { Default, Alarm, IncomingCall, Reminder };
        enum Duration { System, Short, Long };
        enum AudioOption { Default = 0, Silent, Loop };
        enum TextField { FirstLine = 0, SecondLine, ThirdLine };

        enum WinToastTemplateType {
            ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
            ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
            ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
            ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
            Text01         = ToastTemplateType::ToastTemplateType_ToastText01,
            Text02         = ToastTemplateType::ToastTemplateType_ToastText02,
            Text03         = ToastTemplateType::ToastTemplateType_ToastText03,
            Text04         = ToastTemplateType::ToastTemplateType_ToastText04
        };

        enum AudioSystemFile {
            DefaultSound,
            IM,
            Mail,
            Reminder,
            SMS,
            Alarm,
            Alarm2,
            Alarm3,
            Alarm4,
            Alarm5,
            Alarm6,
            Alarm7,
            Alarm8,
            Alarm9,
            Alarm10,
            Call,
            Call1,
            Call2,
            Call3,
            Call4,
            Call5,
            Call6,
            Call7,
            Call8,
            Call9,
            Call10,
        };

        enum CropHint {
            Square,
            Circle,
        };

        WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
        ~WinToastTemplate();

        void setFirstLine(_In_ std::wstring const& text);
        void setSecondLine(_In_ std::wstring const& text);
        void setThirdLine(_In_ std::wstring const& text);
        void setTextField(_In_ std::wstring const& txt, _In_ TextField pos);
        void setAttributionText(_In_ std::wstring const& attributionText);
        void setImagePath(_In_ std::wstring const& imgPath, _In_ CropHint cropHint = CropHint::Square);
        void setHeroImagePath(_In_ std::wstring const& imgPath, _In_ bool inlineImage = false);
        void setAudioPath(_In_ WinToastTemplate::AudioSystemFile audio);
        void setAudioPath(_In_ std::wstring const& audioPath);
        void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
        void setDuration(_In_ Duration duration);
        void setExpiration(_In_ INT64 millisecondsFromNow);
        void setScenario(_In_ Scenario scenario);
        void addAction(_In_ std::wstring const& label);

        std::size_t textFieldsCount() const;
        std::size_t actionsCount() const;
        bool hasImage() const;
        bool hasHeroImage() const;
        std::vector<std::wstring> const& textFields() const;
        std::wstring const& textField(_In_ TextField pos) const;
        std::wstring const& actionLabel(_In_ std::size_t pos) const;
        std::wstring const& imagePath() const;
        std::wstring const& heroImagePath() const;
        std::wstring const& audioPath() const;
        std::wstring const& attributionText() const;
        std::wstring const& scenario() const;
        INT64 expiration() const;
        WinToastTemplateType type() const;
        WinToastTemplate::AudioOption audioOption() const;
        Duration duration() const;
        bool isToastGeneric() const;
        bool isInlineHeroImage() const;
        bool isCropHintCircle() const;

    private:
        std::vector<std::wstring> _textFields{};
        std::vector<std::wstring> _actions{};
        std::wstring _imagePath{};
        std::wstring _heroImagePath{};
        bool _inlineHeroImage{false};
        std::wstring _audioPath{};
        std::wstring _attributionText{};
        std::wstring _scenario{L"Default"};
        INT64 _expiration{0};
        AudioOption _audioOption{WinToastTemplate::AudioOption::Default};
        WinToastTemplateType _type{WinToastTemplateType::Text01};
        Duration _duration{Duration::System};
        CropHint _cropHint{CropHint::Square};
    };

    class WinToast {
    public:
        enum WinToastError {
            NoError = 0,
            NotInitialized,
            SystemNotSupported,
            ShellLinkNotCreated,
            InvalidAppUserModelID,
            InvalidParameters,
            InvalidHandler,
            NotDisplayed,
            UnknownError
        };

        enum ShortcutResult {
            SHORTCUT_UNCHANGED   = 0,
            SHORTCUT_WAS_CHANGED = 1,
            SHORTCUT_WAS_CREATED = 2,

            SHORTCUT_MISSING_PARAMETERS = -1,
            SHORTCUT_INCOMPATIBLE_OS    = -2,
            SHORTCUT_COM_INIT_FAILURE   = -3,
            SHORTCUT_CREATE_FAILED      = -4
        };

        enum ShortcutPolicy {
            /* Don't check, create, or modify a shortcut. */
            SHORTCUT_POLICY_IGNORE = 0,
            /* Require a shortcut with matching AUMI, don't create or modify an existing one. */
            SHORTCUT_POLICY_REQUIRE_NO_CREATE = 1,
            /* Require a shortcut with matching AUMI, create if missing, modify if not matching. This is the default. */
            SHORTCUT_POLICY_REQUIRE_CREATE = 2,
        };

        WinToast(void);
        virtual ~WinToast();
        static WinToast* instance();
        static bool isCompatible();
        static bool isSupportingModernFeatures();
        static bool isWin10AnniversaryOrHigher();
        static std::wstring configureAUMI(_In_ std::wstring const& companyName, _In_ std::wstring const& productName,
                                          _In_ std::wstring const& subProduct         = std::wstring(),
                                          _In_ std::wstring const& versionInformation = std::wstring());
        static std::wstring const& strerror(_In_ WinToastError error);
        virtual bool initialize(_Out_opt_ WinToastError* error = nullptr);
        virtual bool isInitialized() const;
        virtual bool hideToast(_In_ INT64 id);
        virtual INT64 showToast(_In_ WinToastTemplate const& toast, _In_ IWinToastHandler* eventHandler,
                                _Out_opt_ WinToastError* error = nullptr);
        virtual void clear();
        virtual enum ShortcutResult createShortcut();

        std::wstring const& appName() const;
        std::wstring const& appUserModelId() const;
        void setAppUserModelId(_In_ std::wstring const& aumi);
        void setAppName(_In_ std::wstring const& appName);
        void setShortcutPolicy(_In_ ShortcutPolicy policy);

    protected:
        struct NotifyData {
            NotifyData(){};
            NotifyData(_In_ ComPtr<IToastNotification> notify, _In_ EventRegistrationToken activatedToken,
                       _In_ EventRegistrationToken dismissedToken, _In_ EventRegistrationToken failedToken) :
                _notify(notify), _activatedToken(activatedToken), _dismissedToken(dismissedToken), _failedToken(failedToken) {}

            ~NotifyData() {
                RemoveTokens();
            }

            void RemoveTokens() {
                if (!_readyForDeletion) {
                    return;
                }

                if (_previouslyTokenRemoved) {
                    return;
                }

                if (!_notify.Get()) {
                    return;
                }

                _notify->remove_Activated(_activatedToken);
                _notify->remove_Dismissed(_dismissedToken);
                _notify->remove_Failed(_failedToken);
                _previouslyTokenRemoved = true;
            }

            void markAsReadyForDeletion() {
                _readyForDeletion = true;
            }

            bool isReadyForDeletion() const {
                return _readyForDeletion;
            }

            IToastNotification* notification() {
                return _notify.Get();
            }

        private:
            ComPtr<IToastNotification> _notify{nullptr};
            EventRegistrationToken _activatedToken{};
            EventRegistrationToken _dismissedToken{};
            EventRegistrationToken _failedToken{};
            bool _readyForDeletion{false};
            bool _previouslyTokenRemoved{false};
        };

        bool _isInitialized{false};
        bool _hasCoInitialized{false};
        ShortcutPolicy _shortcutPolicy{SHORTCUT_POLICY_REQUIRE_CREATE};
        std::wstring _appName{};
        std::wstring _aumi{};
        std::map<INT64, NotifyData> _buffer{};

        void markAsReadyForDeletion(_In_ INT64 id);
        HRESULT validateShellLinkHelper(_Out_ bool& wasChanged);
        HRESULT createShellLinkHelper();
        HRESULT setImageFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isToastGeneric, bool isCropHintCircle);
        HRESULT setHeroImageHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isInlineImage);
        HRESULT setBindToastGenericHelper(_In_ IXmlDocument* xml);
        HRESULT
        setAudioFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path,
                            _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
        HRESULT setTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text, _In_ UINT32 pos);
        HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text);
        HRESULT addActionHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& action, _In_ std::wstring const& arguments);
        HRESULT addDurationHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& duration);
        HRESULT addScenarioHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& scenario);
        ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
        void setError(_Out_opt_ WinToastError* error, _In_ WinToastError value);
    };
} // namespace WinToastLib
#endif // WINTOASTLIB_H
