/**
 * 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>
#include <mutex>

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() { ForceRemoveTokens(); }

        // Unregisters event tokens unconditionally.  Must be called before
        // the IToastNotification refcount reaches zero; wpnapps.dll aborts
        // if any token remains registered when the object is destroyed.
        void
        ForceRemoveTokens() {
            if (_previouslyTokenRemoved) { return; }

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

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

        // Like ForceRemoveTokens() but only runs once the notification has
        // completed its lifecycle (activated / dismissed / failed).  Used by
        // the markAsReadyForDeletion flush loop so that mid-flight
        // notifications are not prematurely deregistered.
        void
        RemoveTokens() {
            if (!_readyForDeletion) { return; }
            ForceRemoveTokens();
        }

        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{};
    std::mutex _bufferMutex{};

    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
