/*
 * Copyright (C) 2010 Canonical, Ltd.
 *
 * Authors:
 *  Florian Boucault <florian.boucault@canonical.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QtCore/QHash>
#include <QtCore/QByteArray>

#include <dee.h>
#include <glib-object.h>

#include "deelistmodel.h"

static QVariant
QVariantFromGVariant(GVariant *value)
{
    switch (g_variant_classify(value)) {
        case G_VARIANT_CLASS_BOOLEAN:
            return QVariant((bool) g_variant_get_boolean(value));
        case G_VARIANT_CLASS_BYTE:
            return QVariant((uchar) g_variant_get_byte(value));
        case G_VARIANT_CLASS_INT16:
            return QVariant((qint16) g_variant_get_int16(value));
        case G_VARIANT_CLASS_UINT16:
            return QVariant((quint16) g_variant_get_uint16(value));
        case G_VARIANT_CLASS_INT32:
            return QVariant((qint32) g_variant_get_int32(value));
        case G_VARIANT_CLASS_UINT32:
            return QVariant((quint32) g_variant_get_uint32(value));
        case G_VARIANT_CLASS_INT64:
            return QVariant((qint64) g_variant_get_int64(value));
        case G_VARIANT_CLASS_UINT64:
            return QVariant((quint64) g_variant_get_uint64(value));
        case G_VARIANT_CLASS_DOUBLE:
            return QVariant(g_variant_get_double(value));
        case G_VARIANT_CLASS_STRING:
            return QVariant(QString::fromUtf8(g_variant_get_string(value, NULL)));
        default:
            /* Fallback on an empty QVariant.
               FIXME: Missing conversion of following GVariant types:
                - G_VARIANT_CLASS_HANDLE
                - G_VARIANT_CLASS_OBJECT_PATH
                - G_VARIANT_CLASS_SIGNATURE
                - G_VARIANT_CLASS_VARIANT
                - G_VARIANT_CLASS_MAYBE
                - G_VARIANT_CLASS_ARRAY
                - G_VARIANT_CLASS_TUPLE
                - G_VARIANT_CLASS_DICT_ENTRY
            */
            return QVariant();
    }
}

class DeeListModelPrivate {
public:
    DeeListModelPrivate(DeeListModel* parent);
    ~DeeListModelPrivate();

    void connectToDeeModel();
    void connectToDeeModel(DeeModel *model);
    void disconnectFromDeeModel();
    void createRoles();

    /* GObject signal handlers for m_deeModel */
    static void onSynchronizedChanged(GObject* emitter, GParamSpec *pspec, DeeListModel* model);
    static void onRowAdded(GObject* emitter, DeeModelIter* iter, DeeListModel* model);
    static void onRowRemoved(GObject* emitter, DeeModelIter* iter, DeeListModel* model);
    static void onRowChanged(GObject* emitter, DeeModelIter* iter, DeeListModel* model);

    DeeListModel* m_parent;
    DeeModel* m_deeModel;
    QString m_name;
    int m_count;
    bool m_isLocal;
};

DeeListModelPrivate::DeeListModelPrivate(DeeListModel* parent) : m_parent(parent), m_deeModel(NULL), m_count(0), m_isLocal(false)
{
}

DeeListModelPrivate::~DeeListModelPrivate()
{
    disconnectFromDeeModel();
}

void
DeeListModelPrivate::disconnectFromDeeModel()
{
    if (m_deeModel != NULL) {
        /* Disconnect from all GObject properties */
        g_object_disconnect(m_deeModel, "any_signal", G_CALLBACK(onSynchronizedChanged), m_parent, NULL);
        g_object_disconnect(m_deeModel, "any_signal", G_CALLBACK(onRowAdded), m_parent, NULL);
        g_object_disconnect(m_deeModel, "any_signal", G_CALLBACK(onRowRemoved), m_parent, NULL);
        g_object_disconnect(m_deeModel, "any_signal", G_CALLBACK(onRowChanged), m_parent, NULL);

        g_object_unref(m_deeModel);
        m_deeModel = NULL;
        Q_EMIT m_parent->synchronizedChanged(false);
    }
}

void
DeeListModelPrivate::connectToDeeModel()
{
    disconnectFromDeeModel();

    if (!m_name.isEmpty())
    {
        m_deeModel = dee_shared_model_new(m_name.toUtf8().data());
        g_signal_connect(m_deeModel, "notify::synchronized", G_CALLBACK(onSynchronizedChanged), m_parent);
        g_signal_connect(m_deeModel, "row-added", G_CALLBACK(onRowAdded), m_parent);
        g_signal_connect(m_deeModel, "row-removed", G_CALLBACK(onRowRemoved), m_parent);
        g_signal_connect(m_deeModel, "row-changed", G_CALLBACK(onRowChanged), m_parent);
    }
    m_isLocal = false;
}

void
DeeListModelPrivate::connectToDeeModel(DeeModel *model)
{
    disconnectFromDeeModel();

    m_deeModel = (DeeModel*)g_object_ref (model);
    g_signal_connect(m_deeModel, "row-added", G_CALLBACK(onRowAdded), m_parent);
    g_signal_connect(m_deeModel, "row-removed", G_CALLBACK(onRowRemoved), m_parent);
    g_signal_connect(m_deeModel, "row-changed", G_CALLBACK(onRowChanged), m_parent);
    m_isLocal = true;
    createRoles();
    m_count = dee_model_get_n_rows(m_deeModel);
    m_parent->reset();
    Q_EMIT m_parent->countChanged();

}

void
DeeListModelPrivate::createRoles()
{
    if (m_deeModel == NULL) {
        return;
    }

    QHash<int, QByteArray> roles;
    QString column;
    guint n_columns = dee_model_get_n_columns(m_deeModel);

    for (unsigned int index=0; index<n_columns; index++)
    {
        column = QString("column_%1").arg(index);
        roles[index] = column.toAscii();
    }

    m_parent->setRoleNames(roles);
    Q_EMIT m_parent->roleNamesChanged(roles);
}

void
DeeListModelPrivate::onSynchronizedChanged(GObject* emitter __attribute__ ((unused)),
                                           GParamSpec *pspec,
                                           DeeListModel *model)
{
    model->d->createRoles();
    model->d->m_count = dee_model_get_n_rows(model->d->m_deeModel);
    model->synchronizedChanged(model->synchronized());
    model->reset();
    Q_EMIT model->countChanged();
}

void
DeeListModelPrivate::onRowAdded(GObject *emitter __attribute__ ((unused)),
                                DeeModelIter* iter,
                                DeeListModel* model)
{
    if(!model->synchronized()) {
        return;
    }

    model->d->m_count++;
    gint position = dee_model_get_position(model->d->m_deeModel, iter);
    /* Force emission of QAbstractItemModel::rowsInserted by calling
       beginInsertRows and endInsertRows. Necessary because according to the
       documentation:
       "It can only be emitted by the QAbstractItemModel implementation, and
        cannot be explicitly emitted in subclass code."
    */
    model->beginInsertRows(QModelIndex(), position, position);
    model->endInsertRows();
    Q_EMIT model->countChanged();
}

void
DeeListModelPrivate::onRowRemoved(GObject *emitter __attribute__ ((unused)),
                                  DeeModelIter* iter,
                                  DeeListModel* model)
{
    if(!model->synchronized()) {
        return;
    }

    /* Note that at this point the row is still present and valid in the DeeModel.
       Therefore the value returned by dee_model_get_n_columns() might not be
       what one would expect.
       See Dee's dee_sequence_model_remove() method.
    */
    model->d->m_count--;
    gint position = dee_model_get_position(model->d->m_deeModel, iter);
    /* Force emission of QAbstractItemModel::rowsRemoved by calling
       beginRemoveRows and endRemoveRows. Necessary because according to the
       documentation:
       "It can only be emitted by the QAbstractItemModel implementation, and
        cannot be explicitly emitted in subclass code."
    */
    model->beginRemoveRows(QModelIndex(), position, position);
    model->endRemoveRows();
    Q_EMIT model->countChanged();
}

void
DeeListModelPrivate::onRowChanged(GObject *emitter __attribute__ ((unused)),
                                  DeeModelIter* iter,
                                  DeeListModel* model)
{
    if(!model->synchronized()) {
        return;
    }

    gint position = dee_model_get_position(model->d->m_deeModel, iter);
    QModelIndex index = model->index(position);
    Q_EMIT model->dataChanged(index, index);
}



DeeListModel::DeeListModel(QObject *parent) :
    QAbstractListModel(parent), d(new DeeListModelPrivate(this))
{
    g_type_init();
}

DeeListModel::~DeeListModel()
{
    delete d;
}

QString
DeeListModel::name() const
{
    return d->m_name;
}

void
DeeListModel::setName(const QString& name)
{
    if (name != d->m_name) {
        d->m_name = name;
        Q_EMIT nameChanged(d->m_name);
        d->connectToDeeModel();
    }
}

void
DeeListModel::setModel(DeeModel *model)
{
    if (model != NULL) {
        d->connectToDeeModel(model);
    } else {
        d->disconnectFromDeeModel();
    }
}


bool
DeeListModel::synchronized() const
{
    if (d->m_deeModel == NULL) {
        return false;
    } else {
        if (d->m_isLocal) {
            return true;
        } else {
            return dee_shared_model_is_synchronized(DEE_SHARED_MODEL(d->m_deeModel));
        }
    }
}

int
DeeListModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)

    if (d->m_deeModel == NULL || !synchronized()) {
        return 0;
    }

    return d->m_count;
}

QVariant
DeeListModel::data(const QModelIndex &index, int role) const
{
    if (d->m_deeModel == NULL || !synchronized()) {
        return QVariant();
    }

    if (!index.isValid())
        return QVariant();

    /* Assume role is always a positive integer */
    if ((unsigned int)role >= dee_model_get_n_columns(d->m_deeModel)) {
        return QVariant();
    }

    GVariant* gvariant;
    DeeModelIter* iter;

    iter = dee_model_get_iter_at_row(d->m_deeModel, index.row());
    gvariant = dee_model_get_value(d->m_deeModel, iter, role);
    QVariant qvariant = QVariantFromGVariant(gvariant);
    g_variant_unref(gvariant);

    return qvariant;
}

QVariantMap
DeeListModel::get(int row)
{
    if (d->m_deeModel == NULL || !synchronized()) {
        return QVariantMap();
    }

    QVariantMap result;
    QHashIterator<int, QByteArray> i(roleNames());
    while (i.hasNext()) {
        i.next();
        QModelIndex modelIndex = index(row);
        QVariant data = modelIndex.data(i.key());
        result[i.value()] = data;
     }
     return result;
}

int
DeeListModel::count()
{
    return rowCount();
}
