/*
 * Copyright 2016-2020 Dmitry Ivanov
 *
 * This file is part of libquentier
 *
 * libquentier is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * libquentier 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libquentier. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef LIB_QUENTIER_NOTE_EDITOR_NOTE_EDITOR_PLUGIN_FACTORY_H
#define LIB_QUENTIER_NOTE_EDITOR_NOTE_EDITOR_PLUGIN_FACTORY_H

#include "INoteEditorResourcePlugin.h"

#include <QHash>
#include <QIcon>
#include <QMimeDatabase>
#include <QPointer>
#include <QVector>
#include <QWebPluginFactory>

QT_FORWARD_DECLARE_CLASS(QRegExp)

#define RESOURCE_PLUGIN_HTML_OBJECT_TYPE                                       \
    QStringLiteral("application/vnd.quentier.resource")

#define ENCRYPTED_AREA_PLUGIN_OBJECT_TYPE                                      \
    QStringLiteral("application/vnd.quentier.encrypt")

namespace quentier {

QT_FORWARD_DECLARE_CLASS(DecryptedTextManager)
QT_FORWARD_DECLARE_CLASS(EncryptionManager)
QT_FORWARD_DECLARE_CLASS(Note)
QT_FORWARD_DECLARE_CLASS(NoteEditorPrivate)

QT_FORWARD_DECLARE_CLASS(EncryptedAreaPlugin)
QT_FORWARD_DECLARE_CLASS(GenericResourceDisplayWidget)

/**
 * @brief The NoteEditorPluginFactory class allows one to install and uninstall
 * custom plugins to/from NoteEditor
 */
class Q_DECL_HIDDEN NoteEditorPluginFactory final : public QWebPluginFactory
{
    Q_OBJECT
public:
    explicit NoteEditorPluginFactory(
        NoteEditorPrivate & editor, QObject * parent = nullptr);

    virtual ~NoteEditorPluginFactory();

    /**
     * @brief noteEditor is the accessor providing the const reference to
     * INoteEditorBackend object owning the factory
     * @return const reference to INoteEditorBackend object
     */
    const NoteEditorPrivate & noteEditor() const;

    /**
     * @brief ResourcePluginIdentifier is the unique identifier of the plugin
     * assigned to it by the factory; any identifier larger than zero is
     * a "valid" one while zero is the same as "no identifier"
     */
    using ResourcePluginIdentifier = quint32;

    /**
     * @brief addResourcePlugin is the method allowing one to add new custom
     * plugins to NoteEditor
     *
     * @param plugin                    Plugin to be added to NoteEditor,
     *                                  subclass of INoteEditorResourcePlugin.
     *                                  Note: the ownership of the pointer
     *                                  object is transferred to the factory
     * @param errorDescription          Error description in case the plugin
     *                                  can't be installed
     * @param forceOverrideTypeKeys     When multiple plugins support multiple
     *                                  myme types and file extensions, they can
     *                                  potentially overlap. In such a case if
     *                                  this parameters if set to false (which
     *                                  is its default value), the method would
     *                                  refuse to install the plugin conflicting
     *                                  with other plugins by mime types and/or
     *                                  file extensions. However, if this
     *                                  parameter is set to true, the newly
     *                                  installed plugin would override the mime
     *                                  types and/or file extensions of
     *                                  previously installed plugins. It can be
     *                                  rolled back when the plugin is
     *                                  uninstalled.
     * @return                          Non-zero plugin identifier if the plugin
     *                                  was successfully installed, zero
     *                                  otherwise
     */
    ResourcePluginIdentifier addResourcePlugin(
        INoteEditorResourcePlugin * plugin, ErrorString & errorDescription,
        const bool forceOverrideTypeKeys = false);

    /**
     * @brief removeResourcePlugin is the method used to uninstall previously
     * installed plugin to NoteEditor
     *
     * @param id                        Identifier of the plugin to be
     *                                  uninstalled
     * @param errorDescription          Error description if the plugin couldn't
     *                                  be uninstalled
     * @return                          True if the plugin was successfully
     *                                  uninstalled, false otherwise
     */
    bool removeResourcePlugin(
        const ResourcePluginIdentifier id, ErrorString & errorDescription);

    /**
     * @brief hasResourcePlugin is the method allowing one to find out whether
     * the plugin with certain identifier is currently installed or not
     *
     * @param id                        Identifier of the plugin to be checked
     *                                  for being installed
     * @return                          True if the plugin is installed,
     *                                  false otherwise
     */
    bool hasResourcePlugin(const ResourcePluginIdentifier id) const;

    /**
     * @brief hasResourcePluginForMimeType is the method allowing one to find
     * out whether the resource display plugin corresponding to certain mime
     * type is currently installed
     *
     * @param mimeType                  Mime type for which the presence of
     *                                  the install plugin is checked
     * @return                          True if the plugin for specified mime
     *                                  type is installed, false otherwise
     */
    bool hasResourcePluginForMimeType(const QString & mimeType) const;

    /**
     * @brief hasResourcePluginForMimeType is the method allowing one to find
     * out whether the resource display plugin corresponding to any mime type
     * matching the specified regex is currently installed
     *
     * @param mimeTypeRegex             The regex for mime type the matching to
     *                                  which for any installed plugin is
     *                                  checked
     * @return                          True if the plugin for mime type
     *                                  matching the specified regex is
     *                                  installed, false otherwise
     */
    bool hasResourcePluginForMimeType(const QRegExp & mimeTypeRegex) const;

    /**
     * @brief setNote is the method specifying the note for the plugin factory.
     *
     * The plugin factory needs to access certain information from the resources
     * of current note for which the plugins are created. For that the factory
     * needs to have a const reference to current note. This method provides
     * such a reference.
     *
     * @param note                      Current note to be displayed by note
     *                                  editor with plugins from note editor
     *                                  plugin factory
     */
    void setNote(const Note & note);

    /**
     * @brief setFallbackResourceIcon is the method specifying the icon which
     * note editor plugin factory would create for "generic" resource display
     * plugin if it finds no "real" plugin installed for given mime type.
     *
     * This "generic" plugin would try to figure out the best matching icon for
     * given mime type but it can fail to do so. In such a case the icon
     * specified by this method would be displayed for the resource with such
     * unidentified mime type
     *
     * @param icon                      Icon to be used as a last resort for
     *                                  resources of unidentified mime types
     */
    void setFallbackResourceIcon(const QIcon & icon);

    void setInactive();
    void setActive();

    void updateResource(const Resource & resource);

private:
    // QWebPluginFactory interface
    virtual QObject * create(
        const QString & mimeType, const QUrl & url,
        const QStringList & argumentNames,
        const QStringList & argumentValues) const;

    virtual QList<QWebPluginFactory::Plugin> plugins() const;

private:
    QObject * createResourcePlugin(
        const QStringList & argumentNames,
        const QStringList & argumentValues) const;

    QObject * createEncryptedAreaPlugin(
        const QStringList & argumentNames,
        const QStringList & argumentValues) const;

    QIcon getIconForMimeType(const QString & mimeTypeName) const;

    QStringList getFileSuffixesForMimeType(const QString & mimeType) const;

    QString getFilterStringForMimeType(const QString & mimeType) const;

private:
    class GenericResourceDisplayWidgetFinder
    {
    public:
        GenericResourceDisplayWidgetFinder(const Resource & resource);

        bool operator()(
            const QPointer<GenericResourceDisplayWidget> & ptr) const;

    private:
        QString m_resourceLocalUid;
    };

private:
    NoteEditorPrivate & m_noteEditor;

    using ResourcePluginsHash =
        QHash<ResourcePluginIdentifier, INoteEditorResourcePlugin *>;

    ResourcePluginsHash m_resourcePlugins;
    QVector<INoteEditorResourcePlugin *> m_resourcePluginsInAdditionOrder;
    ResourcePluginIdentifier m_lastFreeResourcePluginId = 1;

    const Note * m_pCurrentNote = nullptr;

    QIcon m_fallbackResourceIcon;

    QMimeDatabase m_mimeDatabase;

    mutable QHash<QString, QIcon> m_resourceIconCache;
    mutable QHash<QString, QStringList> m_fileSuffixesCache;

    mutable QVector<QPointer<GenericResourceDisplayWidget>>
        m_genericResourceDisplayWidgetPlugins;
    mutable QVector<QPointer<EncryptedAreaPlugin>> m_encryptedAreaPlugins;
};

} // namespace quentier

#endif // LIB_QUENTIER_NOTE_EDITOR_NOTE_EDITOR_PLUGIN_FACTORY_H
