/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2012-05-22
 * Description : Qt model for camera entries
 *
 * SPDX-FileCopyrightText: 2012 by Islam Wazery <wazery at ubuntu dot com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * ============================================================ */

#pragma once

// Qt includes

#include <QAbstractListModel>
#include <QUrl>

// Local includes

#include "dragdropimplementations.h"
#include "camerathumbsctrl.h"
#include "camiteminfo.h"

namespace Digikam
{
class AbstractItemDragDropHandler;

class ImportItemModel : public QAbstractListModel, public DragDropModelImplementation
{
    Q_OBJECT

public:

    enum ImportItemModelRoles
    {
        /**
         * An ImportItemModel* pointer to this model
         */
        ImportItemModelPointerRole = Qt::UserRole,
        ImportItemModelInternalId  = Qt::UserRole + 1,

        /**
         * Returns a thumbnail pixmap. May be implemented by subclasses.
         * Returns either a valid pixmap or a null QVariant.
         */
        ThumbnailRole               = Qt::UserRole + 2,
        /**
         * Return (optional) extraData field
         */
        ExtraDataRole               = Qt::UserRole + 3,

        /**
         * Returns the number of duplicate indexes for the same image id
         */
        ExtraDataDuplicateCount     = Qt::UserRole + 6,

        FilterModelRoles            = Qt::UserRole + 100
    };

public:

    explicit ImportItemModel(QObject* const parent = nullptr);
    ~ImportItemModel()                                                     override;

    /**
     * Used to set the camera controller, and connect with it.
     */
    virtual void setCameraThumbsController(CameraThumbsCtrl* const controller);

    /**
     * If a cache is kept, lookup by file path is fast,
     * without a cache it is O(n). Default is false.
     */
    void setKeepsFileUrlCache(bool keepCache);
    bool keepsFileUrlCache() const;

    /**
     * Returns the CamItemInfo object, reference from the underlying data pointed to by the index.
     * For camItemInfo and camItemInfoId If the index is not valid they will return a null CamItemInfo, and 0
     * respectively, camItemInfoRef must not be called with an invalid index as it will crash.
     */
    CamItemInfo      camItemInfo(const QModelIndex& index)           const;
    CamItemInfo&     camItemInfoRef(const QModelIndex& index)        const;
    qlonglong        camItemId(const QModelIndex& index)             const;
    CamItemInfoList  camItemInfos(const QList<QModelIndex>& indexes) const;
    QList<qlonglong> camItemIds(const QList<QModelIndex>& indexes)   const;

    /**
     * Returns the CamItemInfo object, reference from the underlying data of
     * the given row (parent is the invalid QModelIndex, column is 0).
     * Note that camItemInfoRef must not be called with an invalid index as it will crash.
     */
    CamItemInfo  camItemInfo(int row)                                const;
    CamItemInfo& camItemInfoRef(int row)                             const;
    qlonglong    camItemId(int row)                                  const;

    /**
     * Return the index of a given CamItemInfo, if it exists in the model.
     */
    QModelIndex        indexForCamItemInfo(const CamItemInfo& info)   const;
    QList<QModelIndex> indexesForCamItemInfo(const CamItemInfo& info) const;
    QModelIndex        indexForCamItemId(qlonglong id)                const;
    QList<QModelIndex> indexesForCamItemId(qlonglong id)              const;

    /**
     * Returns the index or CamItemInfo object from the underlying data for
     * the given file url. In case of multiple occurrences of the same file, the simpler
     * overrides returns any one found first, use the QList methods to retrieve all occurrences.
     */
    QModelIndex        indexForUrl(const QUrl& fileUrl)               const;
    QList<QModelIndex> indexesForUrl(const QUrl& fileUrl)             const;
    CamItemInfo        camItemInfo(const QUrl& fileUrl)               const;
    QList<CamItemInfo> camItemInfos(const QUrl& fileUrl)              const;

    /**
     * Clears the CamItemInfos and resets the model.
     */
    void clearCamItemInfos();

    /**
     * addCamItemInfo() is asynchronous if a prepocessor is set.
     * This method first adds the info, synchronously.
     * Only afterwards, the preprocessor will have the opportunity to process it.
     * This method also bypasses any incremental updates.
     */
    void addCamItemInfoSynchronously(const CamItemInfo& info);
    void addCamItemInfosSynchronously(const Digikam::CamItemInfoList& infos);

    /**
     * Clears and adds infos.
     */
    void setCamItemInfos(const CamItemInfoList& infos);

    QList<CamItemInfo> camItemInfos()       const;
    QList<qlonglong>   camItemIds()         const;
    QList<CamItemInfo> uniqueCamItemInfos() const;

    bool hasImage(qlonglong id)             const;
    bool hasImage(const CamItemInfo& info)  const;

    bool isEmpty()                          const;

    /**
     * Remove the given infos or indexes directly from the model.
     */
    void removeIndex(const QModelIndex& index);
    void removeIndexs(const QList<QModelIndex>& indexes);
    void removeCamItemInfo(const CamItemInfo& info);
    void removeCamItemInfos(const QList<CamItemInfo>& infos);

    int numberOfIndexesForCamItemInfo(const CamItemInfo& info) const;
    int numberOfIndexesForCamItemId(qlonglong id)              const;

    /**
     * Retrieve the CamItemInfo object from the data() function of the given index
     * The index may be from a QSortFilterProxyModel as long as an ImportItemModel is at the end.
     */
    static CamItemInfo retrieveCamItemInfo(const QModelIndex& index);
    static qlonglong   retrieveCamItemId(const QModelIndex& index);

    /// QAbstractListModel implementation
    int           rowCount(const QModelIndex& parent)                            const override;
    QVariant      data(const QModelIndex& index, int role)                       const override;
    QVariant      headerData(int section, Qt::Orientation orientation, int role) const override;
    Qt::ItemFlags flags(const QModelIndex& index)                                const override;
    QModelIndex   index(int row, int column, const QModelIndex& parent)          const override;

    /// DragDrop methods
    DECLARE_MODEL_DRAG_DROP_METHODS

    /**
     * Enable sending of itemInfosAboutToBeRemoved and itemsInfosRemoved signals.
     * Default: false
     */
    void setSendRemovalSignals(bool send);

    /**
     * Returns true if this model is currently refreshing.
     * For a preprocessor this means that, although the preprocessor may currently have
     * processed all it got, more batches are to be expected.
     */
    bool isRefreshing() const;

Q_SIGNALS:

    /**
     * Informs that ItemInfos will be added to the model.
     * This signal is sent before the model data is changed and views are informed.
     */
    void itemInfosAboutToBeAdded(const QList<CamItemInfo>& infos);

    /**
     * Informs that ItemInfos have been added to the model.
     * This signal is sent after the model data is changed and views are informed.
     */
    void itemInfosAdded(const QList<CamItemInfo>& infos);

    /**
     * Informs that CamItemInfos will be removed from the model.
     * This signal is sent before the model data is changed and views are informed.
     * Note: You need to explicitly enable sending of this signal. It is not sent
     * in clearCamItemInfos().
     */
    void itemInfosAboutToBeRemoved(const QList<CamItemInfo>& infos);

    /**
     * Informs that CamItemInfos have been removed from the model.
     * This signal is sent after the model data is changed and views are informed.
     * Note: You need to explicitly enable sending of this signal. It is not sent
     * in clearCamItemInfos().
     */
    void itemInfosRemoved(const QList<CamItemInfo>& infos);

    /**
     * Connect to this signal only if you are the current preprocessor.
     */
    void preprocess(const QList<CamItemInfo>& infos);
    void processAdded(const QList<CamItemInfo>& infos);

    /**
     * Signals that the model is right now ready to start an incremental refresh.
     * This is guaranteed only for the scope of emitting this signal.
     */
    void readyForIncrementalRefresh();

    /**
     * Signals that the model has finished currently with all scheduled
     * refreshing, full or incremental, and all preprocessing.
     * The model is in polished, clean situation right now.
     */
    void allRefreshingFinished();

public Q_SLOTS:

    void reAddCamItemInfos(const CamItemInfoList& infos);
    void reAddingFinished();
    void slotFileDeleted(const QString& folder, const QString& file, bool status);
    void slotFileUploaded(const CamItemInfo& info);
    void addCamItemInfo(const CamItemInfo& info);
    void addCamItemInfos(const CamItemInfoList& infos);

protected:

    /**
     * Subclasses that add CamItemInfos in batches shall call startRefresh()
     * when they start sending batches and finishRefresh() when they have finished.
     * No incremental refreshes will be started while listing.
     * A clearCamItemInfos() always stops listing, calling finishRefresh() is then not necessary.
     */
    void startRefresh();
    void finishRefresh();

    /**
     * As soon as the model is ready to start an incremental refresh, the signal
     * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline
     * if the model is ready right now.
     */
    void requestIncrementalRefresh();
    bool hasIncrementalRefreshPending() const;

    /**
     * Starts an incremental refresh operation. You shall only call this method from a slot
     * connected to readyForIncrementalRefresh(). To initiate an incremental refresh,
     * call requestIncrementalRefresh().
     */
    void startIncrementalRefresh();
    void finishIncrementalRefresh();

    void emitDataChangedForAll();
    void emitDataChangedForSelections(const QItemSelection& selection);

    /**
     * Called when the internal storage is cleared.
     */
    virtual void camItemInfosCleared() {};

    /**
     * Called before rowsAboutToBeRemoved
     */
    virtual void prepareItemInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {};

private:

    void appendInfos(const CamItemInfoList& infos);
    void publiciseInfos(const CamItemInfoList& infos);
    void cleanSituationChecks();
    void removeRowPairs(const QList<QPair<int, int> >& toRemove);
    void removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove);

public:

    // NOTE: Declared public because it's used in ItemModelIncrementalUpdater class
    class Private;

private:

    // Disable
    ImportItemModel(const ImportItemModel&)            = delete;
    ImportItemModel& operator=(const ImportItemModel&) = delete;

private:

    Private* const d = nullptr;
};

} // namespace Digikam

Q_DECLARE_METATYPE(Digikam::ImportItemModel*)
