/*
    SPDX-License-Identifier: LGPL-2.1-or-later OR MIT
    SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr <cordlandwehr@kde.org>
*/

#ifndef JOURNALDVIEWMODEL_H
#define JOURNALDVIEWMODEL_H

#include "filter.h"
#include "kjournald_export.h"
#include <QAbstractItemModel>
#include <QQmlEngine>
#include <ijournalprovider.h>
#include <memory>

class JournaldViewModelPrivate;

/**
 * @brief Item model class that provides convienence access to journald database
 *
 * This QAbstractItemModel derived class provides a model/view abstraction for the journald API
 * with the goal to ease integration in Qt based applications.
 */
class KJOURNALD_EXPORT JournaldViewModel : public QAbstractItemModel
{
    Q_OBJECT
    Q_PROPERTY(IJournalProvider *journalProvider READ journalProvider WRITE setJournalProvider NOTIFY journalProviderChanged)
    /**
     * indicates if journal could be loaded from configured provider
     */
    Q_PROPERTY(bool available READ isAvailable NOTIFY availableChanged)
    /**
     * Configure filter for view model
     **/
    Q_PROPERTY(Filter filter WRITE setFilter READ filter RESET resetFilter NOTIFY filterChanged)
    /**
     * if set to true, templated systemd services are grouped together
     **/
    Q_PROPERTY(bool enableSystemdUnitTemplateGrouping READ groupTemplatedSystemdUnits WRITE setGroupTemplatedSystemdUnits NOTIFY
                   groupTemplatedSystemdUnitsChanged FINAL)

    QML_ELEMENT

public:
    enum Roles {
        MESSAGE = Qt::DisplayRole, //!< journal entry's message text
        MESSAGE_ID = Qt::UserRole + 1, //!< ID of log entry in journald DB (might not exist for non systemd services)
        ENTRY, //!< gadget with basic log information
        DATE, //!< date of journal entry
        DATETIME, //!< date and time of journal entry
        MONOTONIC_TIMESTAMP, //!< monotonic timestamp in miliseconds for journal entry
        PRIORITY, //!< priority of journal entry
        SYSTEMD_UNIT, //!< systemd unit name of journal entry
        SYSTEMD_UNIT_CHANGED_SUBSTRING, //!< changed part of systemd unit string when compared to previous line
        BOOT_ID, //!< boot ID of journal entry
        SYSTEMD_UNIT_COLOR_BACKGROUND, //!< convenience rainbow color that is hashed for systemd unit, lighter variant
        SYSTEMD_UNIT_COLOR_FOREGROUND, //!< convenience rainbow color that is hashed for systemd unit, darker variant
        EXE_COLOR_BACKGROUND, //!< convenience rainbow color that is hashed for the process, lighter variant
        EXE_COLOR_FOREGROUND, //!< convenience rainbow color that is hashed for the process, darker variant
        EXE, //!< executable path, when available; field "_EXE"
        EXE_CHANGED_SUBSTRING, //!< changed part of EXE string when compared to previous line
        CURSOR, //!< journald internal unique identifier for a log entry
    };
    Q_ENUM(Roles);

    enum Direction {
        FORWARD, //!< interaction with log view leads to browsing
        BACKWARD, //!< interaction with log view leads to text selection
    };
    Q_ENUM(Direction);

    /**
     * @brief Construct model from the default local journald database
     *
     * @param parent the QObject parent
     */
    explicit JournaldViewModel(QObject *parent = nullptr);

    /**
     * Destroys JournaldViewModel
     */
    ~JournaldViewModel() override;

    /**
     * Reset model by using given journal object
     *
     * @param journal The journald access wrapper
     */
    void setJournalProvider(IJournalProvider *provider);

    IJournalProvider *journalProvider() const;

    bool isAvailable() const;

    /**
     * @copydoc QAbstractItemModel::rolesNames()
     */
    QHash<int, QByteArray> roleNames() const override;

    /**
     * @copydoc QAbstractItemModel::headerData()
     *
     * @note NOT IMPLEMENTED YET
     */
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

    /**
     * @copydoc QAbstractItemModel::index()
     */
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;

    /**
     * @copydoc QAbstractItemModel::parent()
     */
    QModelIndex parent(const QModelIndex &index) const override;

    /**
     * @copydoc QAbstractItemModel::rowCount()
     */
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;

    /**
     * @copydoc QAbstractItemModel::columnCount()
     */
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

    /**
     * @copydoc QAbstractItemModel::data()
     */
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    /**
     * @brief Convenience method that returns date for a given model row
     * @param indexRow the index row
     * @return the QDateItem object for the entry found
     */
    Q_INVOKABLE QDateTime datetime(int indexRow) const;

    /**
     * @copydoc QAbstractItemModel::canFetchMore()
     */
    bool canFetchMore(const QModelIndex &parent) const override;

    /**
     * @copydoc QAbstractItemModel::fetchMore()
     */
    void fetchMore(const QModelIndex &parent) override;

    /**
     * Configure the filter that is applied to view model
     */
    void setFilter(const Filter &filter);

    /**
     * @return currently set filter
     */
    Filter filter() const;

    /**
     * @brief Discard all filter values
     */
    void resetFilter();

    /**
     * @return row index of searched string
     */
    Q_INVOKABLE int search(const QString &searchString, int startRow, bool caseSensitive, JournaldViewModel::Direction direction = FORWARD);

    /**
     * @brief Reset model and start reading from head
     *
     * This method can be used to position the log at the beginning and clearing all existing
     * data in the log cache.
     */
    Q_INVOKABLE void seekHead();

    /**
     * @brief Reset model and start reading from tail
     *
     * This method can be used to position the log at the end and clearing all existing
     * data in the log cache.
     */
    Q_INVOKABLE void seekTail();

    /**
     * @brief Return closest index row for given date
     *
     * This method always returns a valid index then the model is not empty. If the model is empty,
     * return value is -1;
     *
     * @return row index for closest entry
     */
    Q_INVOKABLE int closestIndexForData(const QDateTime &datetime);

    /**
     * @brief Set how many log entries shall be read on each request of read-mode.
     * @param size
     * @note the initial value is 500 and changing this value only affects future reads.
     */
    void setFetchMoreChunkSize(quint32 size);

    /**
     * @return true of service grouping is enabled
     */
    bool groupTemplatedSystemdUnits();

    /**
     * set systemd service unit grouping behavior to @p enabled
     */
    void setGroupTemplatedSystemdUnits(bool enabled);

private Q_SLOTS:
    /**
     * Decoupled fetching for log entries that can enforce sequence of fetching calls.
     * @return pair of fetched entries, first are entries at head, second are entries at tail
     */
    std::pair<int, int> fetchMoreLogEntries();

Q_SIGNALS:
    /**
     * Signal is emitted when filter is changed
     */
    void filterChanged();
    void journalProviderChanged();
    void availableChanged();
    void groupTemplatedSystemdUnitsChanged();

protected:
    void guardedBeginResetModel();
    void guardedEndResetModel();

private:
    std::unique_ptr<JournaldViewModelPrivate> d;
};

#endif // JOURNALDVIEWMODEL_H
