/*
 * liveexample.cpp
 * Copyright (C) 2011 Adrian Perez <aperez@igalia.com>
 *
 * Distributed under terms of the MIT license.
 */

#include <QAbstractItemModel>
#include <QSparqlConnection>
#include <TrackerLiveQuery>
#include <QCoreApplication>
#include <QStringList>
#include <QString>
#include <cstdio>


static const QString DRIVER(
  "QTRACKER_DIRECT"
);

/*
 * This is a regular SPARQL query that will fetch the URLs of the photos
 * indexed by Tracker. Note that we add a column to the result that contains
 * the Tracker unique identifier: it will be used by LiveQuery to merge the
 * updates in the model. For live updates to work, it is mandatory to have
 * the identifier in one of the columns.
 *
 * By the way, for a real-world application queries would be more complex,
 * but the main point here is to get a taste of how live queries work.
 */
static const QString QUERY(
  "SELECT\n"
  "  tracker:id(?urn) AS ?trackerid\n"
  "  nie:url(?urn) AS ?url\n"
  "WHERE {\n"
  "  ?urn rdf:type nmm:Photo .\n"
  "}\n"
  "ORDER BY ?url"
);

/*
 * This is the SPARQL query for getting the actual data of updated items.
 * It must contain the same result columns and in the same order as the
 * above query. The "%FILTER" string will be replaced by an expression like
 * the following:
 *
 *    FILTER(<something> IN (<id1>, <id2>, ... , <idN>))
 *
 * Where "<something>" is an expression that results in a Tracker identifier
 * whilst "<idX>" will be the Tracker identifiers of the updated items. How
 * the "<something>" part looks like is to be defined.
 *
 * Note that order of the results is not relevant for the update query.
 */
static const QString QUERY_UPDATE(
  "SELECT\n"
  "  tracker:id(?urn) AS ?trackerid\n"
  "  nie:url(?urn) AS ?url\n"
  "WHERE {\n"
  "  ?urn rdf:type nmm:Photo .\n"
  "  %FILTER\n"
  "}\n"
  "ORDER BY ?url"
);


class LiveExample: public QObject
{
    Q_OBJECT

private:
    QSparqlConnection m_connection;
    TrackerLiveQuery  m_liveQuery;

public:
    /*
     * Initialize the LiveQuery object. Apart from the query used to get the
     * initial data set, we pass the number of columns in the result and the
     * connection to use. Note that the total number of columns must be
     * passed, the column that contains the Tracker identifier is accounted
     * for, too.
     */
    LiveExample():
        QObject(),
        m_connection(DRIVER),
        m_liveQuery(QUERY, 2, m_connection)
    {
        /*
         * TrackerLiveQuery::model() returns a subclass of QAbstractItemModel,
         * we can connect to its modification signals:
         *
         *   - rowsInserted() is emitted when a new item is added.
         *   - rowsAboutToBeRemoved() is emitted just before an item is
         *     removed.
         *
         * We could listen to more signals, e.g. dataChanged().
         */
        connect(m_liveQuery.model(),
                SIGNAL(rowsInserted(const QModelIndex&, int, int)),
                SLOT(onRowsInserted(const QModelIndex&, int, int)));

        connect(m_liveQuery.model(),
                SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
                SLOT(onRowsAboutToBeRemoved(const QModelIndex&, int, int)));

        /*
         * Instantiate a TrackerPartialUpdater with the update query. Then
         * ask it for listening to changes using the ::watchClass() method:
         *
         *    - First argument is the name of the class (nmm:Photo) to
         *      listen to. Note live updates only work with classes that
         *      have the "tracker:notify true" attribute in its ontology.
         *    - Next we pass a list of attributes to watch for changes.
         *      The default behavior, passsing an empty list, will get
         *      notifications for all properties.
         *    - Remember about the "<something>" snippet used to construct
         *      the replacement for "%FILTER" in the update query? Here we
         *      pass the "<something>" part.
         *    - ??
         *    - Finally, we specify which column contains the Tracker unique
         *      identifier.
         */
        TrackerPartialUpdater updater(QUERY_UPDATE);
        updater.watchClass("nmm:Photo",
                           QStringList(),
                           "tracker:id(?urn) in %LIST",
                           TrackerPartialUpdater::Subject,
                           0);
        m_liveQuery.addUpdater(updater);

        /*
         * TrackerLiveQuery can maintain the model sorted when processing
         * updates. We need to specify a list of columns that are to be used
         * for sorting. In this case, the URL column is used.
         */
        m_liveQuery.setCollationColumns(QList<TrackerLiveQuery::CollationColumn>()
            << TrackerLiveQuery::CollationColumn(1, QVariant::String, Qt::AscendingOrder));

        /*
         * We also need to tell LiveQuery which column in the main SPARQL
         * query contains the Tracker unique identifier.
         */
        m_liveQuery.setIdentityColumns(QList<int>() << 0);

        /*
         * Bxplicitly enable processing updates. This is the default so in
         * reality this call is not needed, but it serves to let you know
         * that updates can be temporarily paused.
         */
        m_liveQuery.setUpdatesEnabled(true);

        /*
         * Finally, use TrackerLiveQuery::start() to let it make its magic.
         * This will make an initial query to get information about the
         * watched classes, then the main query to get the initial data set,
         * and finally it will connect to the D-Bus signal used by Tracker
         * to notify about updates.
         */
        m_liveQuery.start();
    }

public Q_SLOTS:
    /*
     * This slot will be called when new elements are added to the model.
     * It just prints a plus sign and the URL of the item.
     */
    void onRowsInserted(const QModelIndex&, int start, int end)
    {
        QAbstractItemModel *model = m_liveQuery.model();
        for (int row = start; row <= end; row++) {
            const QModelIndex idx(model->index(row, 1));
            std::printf("+ %s\n", qPrintable(model->data(idx).toString()));
        }
    }

    /*
     * This slot will be called just before elements are removed from the
     * model. It just pinrts a minus sign and the URL of the item.
     */
    void onRowsAboutToBeRemoved(const QModelIndex&, int start, int end)
    {
        QAbstractItemModel *model = m_liveQuery.model();
        for (int row = start; row <= end; row++) {
            const QModelIndex idx(model->index(row, 1));
            std::printf("- %s\n", qPrintable(model->data(idx).toString()));
        }
    }
};


int main(int argc, char *argv[])
{
    /*
     * Having a QCoreApplication or a QApplication is needed to have an
     * event loop and using D-Bus. LiveQuery uses the later to get
     * update notifications from Tracker.
     */
    QCoreApplication application(argc, argv);
    LiveExample liveExample;
    return application.exec();
}


#include "moc_liveexample.cpp"
