/****************************************************************************
**
** Copyright (C) 2012 Róbert Márki
**
** This file is part of Web Feeds.
**
** Web Feeds 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, either version 3 of the License, or
** (at your option) any later version.
**
** Web Feeds 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 Web Feeds.  If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/

#include "pageitemmodel.h"
#include "pageitemmodel_p.h"
#include <abstractfeedaggreagatorservice.h>
#include <QPluginLoader>
#include <QObject>
#include <QDeclarativeEngine>
#include <QDeclarativeContext>
#include <QApplication>
#include <QMetaProperty>

namespace WebFeeds
{
/*!
  \class PageItemModel
  \brief The PageItemModel class exposes the object tree to the QML UI.
*/
/*!
  \class PageItemModelPrivate
  \brief The PageItemModelPrivate class implements the mapping of the
  items in the object tree to a tree model.
*/
/*!
  \enum PageItemModelPrivate::Role
  This enum specifies the hard coded custom role types that are available
  in the model.
  \var PageItemModelPrivate::Role PageItemModelPrivate::ItemAddressRole
  The address of the item associated with an index.
  \var PageItemModelPrivate::Role PageItemModelPrivate::LastCustomRole
  The last hard coded custom role. The model assigns Role ids for item
  specific role names starting from this id.
*/

/*!
  Constructs a PageItemModelPrivate object with the given \a parent.
*/
PageItemModelPrivate::PageItemModelPrivate(QObject *parent) :
    QAbstractItemModel(parent)
{
}

/*!
  Constructs a PageItemModelPrivate object with the given \a rootItem
  and \a parent.
*/
PageItemModelPrivate::PageItemModelPrivate(PageItemPtr rootItem, QObject *parent) :
    QAbstractItemModel(parent),
    m_rootItem(rootItem)
{
    registerItem(m_rootItem);
}

/*!
  Destroys the PageItemModelPrivate object.
*/
PageItemModelPrivate::~PageItemModelPrivate()
{
    m_rootItem.clear();
}

/*!
  Reimplemented from QAbstractItemModel::index().
*/
QModelIndex PageItemModelPrivate::index(int row, int column, const QModelIndex &parent)
            const
{
    QModelIndex ret;
    PageItemPtr item;
    PageItemPtr parentItem;
    if (!parent.isValid())
        parentItem = m_rootItem;
    else
        parentItem = PageItemWeakRef(static_cast<PageItem*>(parent.internalPointer())).toStrongRef();
    if(parentItem)
    {
        PageItemPtr childItem = parentItem->childItem(row);
        if (childItem)
        {
            item = childItem;
            ret = createIndex(row, column, childItem.data());
        }
        else
        {
            item = m_rootItem;
        }
    }
    registerItem(item);
    return ret;
}

/*!
  Reimplemented from QAbstractItemModel::columnCount().
*/
int PageItemModelPrivate::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return 1;
}

/*!
  Reimplemented from QAbstractItemModel::rowCount().
*/
int PageItemModelPrivate::rowCount(const QModelIndex &parent) const
{
    int ret = 0;
    if (!parent.isValid())
    {
        ret = m_rootItem->childCount();
    }
    else
    {
        PageItemPtr parentItem = itemForIndex(parent);
        if(parentItem)
            ret = parentItem->childCount();
    }
    return ret;
}

/*!
  Reimplemented from QAbstractItemModel::data().
*/
QVariant PageItemModelPrivate::data(const QModelIndex &index, int role) const
{
    QVariant ret;
    PageItemPtr item = itemForIndex(index);
    if(role == PageItemModelPrivate::ItemAddressRole)
    {
        ret = QVariant::fromValue<PageItem*>(item.data());
    }
    else if(item)
    {
        if(roleNames().contains(role))
            ret = item->property(roleNames()[role]);
    }
    return ret;
}


/*!
  Reimplemented from QAbstractItemModel::flags().
*/
Qt::ItemFlags PageItemModelPrivate::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

/*!
  Returns the QModelIndex value for the given \a item as
  a QVariant value.
*/
QVariant PageItemModelPrivate::index(PageItem* item) const
{
    return QVariant::fromValue<QModelIndex>(getIndex(item));
}

/*!
  Reimplemented from QAbstractItemModel::parent().
*/
QModelIndex PageItemModelPrivate::parent(const QModelIndex &index) const
{
    QModelIndex ret;
    if(index.isValid())
    {
        PageItemPtr childItem = itemForIndex(index);
        PageItemPtr parentItem = childItem->parentItem();
        if(parentItem && (parentItem != m_rootItem))
            ret = createIndex(parentItem->row(), 0, parentItem.data());
    }
    return ret;
}

/*!
  Notifies the model that some items are going to be inserted into the
  \a parent item from the \a start to the \a end row.
*/
void PageItemModelPrivate::onAboutToInsertChildren(PageItemPtr parent, int start, int end)
{
    if(parent)
    {
        QModelIndex parentIndex = getIndex(parent.data());
        beginInsertRows(parentIndex, start, end);
    }
}

/*!
  Notifies the model that some items are going to be removed from the
  \a parent item from the \a start to the \a end row.
*/
void PageItemModelPrivate::onAboutToRemoveChildren(PageItemPtr parent, int start, int end)
{
    if(parent)
    {
        if((start >=0) && (end < parent->childCount()))
        {
            QModelIndex parentIndex = getIndex(parent.data());
            beginRemoveRows(parentIndex, start, end);
        }
    }
}

/*!
  Notifies the model that some items were inserted into the object tree.
*/
void PageItemModelPrivate::onChildrenInserted()
{
    endInsertRows();
}

/*!
  Notifies the model that some items were removed from the object tree.
*/
void PageItemModelPrivate::onChildrenRemoved()
{
    endRemoveRows();
}

/*!
  Reimplemented from QAbstractItemModel::canFetchMore().
*/
bool PageItemModelPrivate::canFetchMore(const QModelIndex &parent) const
{
    bool ret = false;
    if(parent.isValid())
    {
        PageItemPtr item = itemForIndex(parent);
        if(item)
            ret = item->canFetchMore();
    }
    return ret;
}

/*!
  Reimplemented from QAbstractItemModel::fetchMore().
*/
void PageItemModelPrivate::fetchMore(const QModelIndex &parent)
{
    PageItemPtr item = itemForIndex(parent);
    if(item)
        item->loadMoreChildren();
}

/*!
  Notifies the views attached to this model, that some of the given
  \a item's properties changed.
*/
void PageItemModelPrivate::onItemDataChanged(PageItemPtr item)
{
    if(item)
    {
        QModelIndex itemIndex = getIndex(item.data());
        emit dataChanged(itemIndex, itemIndex);
    }
}

/*!
  Notifies the views attached to this model, that some of the
  properties of the child items in \a parent has changed.
*/
void PageItemModelPrivate::onItemChildrenDataChanged(PageItemPtr parent)
{
    if(parent && parent->childCount())
    {
        QModelIndex firstIndex = getIndex(parent->childItem(0).data());
        QModelIndex lastIndex = getIndex(parent->childItem(parent->childCount() - 1).data());
        emit dataChanged(firstIndex, lastIndex);
    }
}

/*!
  Return the number of child items in the given \a parent item.
  The \a parent should be a QModelIndex value.
*/
int PageItemModelPrivate::rowCount(QVariant parent)
{
    return rowCount(parent.value<QModelIndex>());
}

/*!
  Registers the \a customPropertyNames as role names in the model.
*/
void PageItemModelPrivate::setupModelData(const QSet<QString>& customPropertyNames)
{
    QSet<QString> propertyNames = customPropertyNames;
    QHash<int, QByteArray> roles;
    const QMetaObject metaPageItem = PageItem::staticMetaObject;
    for(int i = metaPageItem.propertyOffset(); i < metaPageItem.propertyCount(); ++i)
    {
        propertyNames << metaPageItem.property(i).name();
    }
    QStringList propertyNameList = propertyNames.toList();
    for(int i = 0; i < propertyNameList.count(); ++i)
    {
        roles.insert(PageItemModelPrivate::LastCustomRole + i, qPrintable(propertyNameList.at(i)));
    }
    setRoleNames(roles);
}

/*!
  Returns the PageItem object accociated with the given \a index.
  The \a index should be a QModelIndex value.
*/
PageItem* PageItemModelPrivate::item(const QVariant &index)
{
    PageItem* ret = 0;
    QModelIndex modelIndex = index.value<QModelIndex>();
    PageItemPtr item = itemForIndex(modelIndex);
    if(item)
    {
        ret = item.data();
        QDeclarativeEngine::setObjectOwnership(ret, QDeclarativeEngine::CppOwnership);
    }
    return ret;
}

/*!
  Returns the QModelIndex value associated with the given \a item.
*/
QModelIndex PageItemModelPrivate::getIndex(PageItem *item) const
{
    QModelIndex ret;
    QModelIndexList indexes = match(index(0, 0), PageItemModelPrivate::ItemAddressRole
                                    ,QVariant::fromValue<PageItem*>(item), 1
                                    ,Qt::MatchExactly | Qt::MatchRecursive);
    if(indexes.count() > 0)
        ret = indexes.first();
    return ret;
}

/*!
  Registers the given \a item with the model. Connects all the necessary
  signals and slots to be able to interact with the object tree through the
  \a item.
*/
void PageItemModelPrivate::registerItem(PageItemPtr item) const
{
    if(item)
    {
        connect(item.data(), SIGNAL(aboutToInsertChildren(PageItemPtr,int,int)),
                this, SLOT(onAboutToInsertChildren(PageItemPtr,int,int)), Qt::UniqueConnection);
        connect(item.data(), SIGNAL(childrenInserted(PageItemPtr)),
                this, SLOT(onChildrenInserted()), Qt::UniqueConnection);
        connect(item.data(), SIGNAL(dataChanged(PageItemPtr)),
                this, SLOT(onItemDataChanged(PageItemPtr)), Qt::UniqueConnection);
        connect(item.data(), SIGNAL(aboutToRemoveChildren(PageItemPtr,int,int)),
                this, SLOT(onAboutToRemoveChildren(PageItemPtr,int,int)), Qt::UniqueConnection);
        connect(item.data(), SIGNAL(childrenRemoved(PageItemPtr)),
                this, SLOT(onChildrenRemoved()), Qt::UniqueConnection);
        connect(item.data(), SIGNAL(childrenDataChanged(PageItemPtr)),
                this, SLOT(onItemChildrenDataChanged(PageItemPtr)), Qt::UniqueConnection);
    }
}

/*!
  Returns the PageItemPtr value associated with the given \a index.
  The internalPointer value stored in \a index is casted into a PageItemPtr
  value.
*/
PageItemPtr PageItemModelPrivate::itemForIndex(const QModelIndex &index) const
{
    return PageItemWeakRef(static_cast<PageItem*>(index.internalPointer())).toStrongRef();
}

/*!
  Constructs a PageItemModel object with the given \a parent.
*/
PageItemModel::PageItemModel(QObject *parent)
    : QSortFilterProxyModel(parent)
{
    d = new PageItemModelPrivate(parent);
}

/*!
  Constructs a PageItemModel object with the given \a rootIte
  and \a parent.
*/
PageItemModel::PageItemModel(PageItemPtr rootItem, QObject *parent)
    : QSortFilterProxyModel(parent)
{
    d = new PageItemModelPrivate(rootItem, parent);
}

/*!
  Destroys the PageItemModel object.
*/
PageItemModel::~PageItemModel()
{
    delete d;
}

/*!
  Initializes the model, and registers the the \a customProperties as
  role names.
*/
void PageItemModel::setupModelData(const QSet<QString>& customProperties)
{
    d->setupModelData(customProperties);
    setSourceModel(d);
}

/*!
  Returns the QModelIndex value associated with the given
  \a item as a QVariant value.
*/
QVariant PageItemModel::index(PageItem *item) const
{
    QModelIndex sourceIndex = d->index(item).value<QModelIndex>();
    QModelIndex index = mapFromSource(sourceIndex);
    return QVariant::fromValue<QModelIndex>(index);
}

/*!
  Returns the row index of the given \a item in it's parent item.
*/
int PageItemModel::row(PageItem *item) const
{
    QModelIndex index = mapFromSource(d->getIndex(item));
    if(index.isValid())
        return index.row();
    else
        return 0;
}

/*!
  Returns the the PageItem object for the given \a index, which should be
  a QModelIndex value.
*/
PageItem *PageItemModel::item(const QVariant &index)
{
    QModelIndex sourceIndex = mapToSource(index.value<QModelIndex>());
    return d->item(QVariant::fromValue<QModelIndex>(sourceIndex));
}

/*!
  Invalidates the filter, and refilteres the items.
*/
void PageItemModel::onFilterChanged()
{
    invalidateFilter();
}

/*!
  Returns true if the current filter accepts the child item of \a source_parent at the
  given \a source_row. The acceptability of the given item is determined by the service
  associated with the item.
*/
bool PageItemModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
    bool ret = true;
    QModelIndex rowIndex = d->index(source_row, 0, source_parent);
    PageItemPtr item = d->itemForIndex(rowIndex);
    if(item)
    {
        AbstractFeedAggreagatorService *service = item->service();
        if(service)
        {
            ret = service->isItemAcceptable(item);
        }
    }
    return ret;
}
} // namespace WebFeeds
