/****************************************************************************
 ** Copyright (C) 2001-2006 Klarälvdalens Datakonsult AB.  All rights reserved.
 **
 ** This file is part of the KD Gantt library.
 **
 ** This file may be distributed and/or modified under the terms of the
 ** GNU General Public License version 2 as published by the Free Software
 ** Foundation and appearing in the file LICENSE.GPL included in the
 ** packaging of this file.
 **
 ** Licensees holding valid commercial KD Gantt licenses may use this file in
 ** accordance with the KD Gantt Commercial License Agreement provided with
 ** the Software.
 **
 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 **
 ** See http://www.kdab.net/kdgantt for
 **   information about KD Gantt Commercial License Agreements.
 **
 ** Contact info@kdab.net if any conditions of this
 ** licensing are not clear to you.
 **
 **********************************************************************/
#include "kdganttsummaryhandlingproxymodel.h"
#include "kdganttsummaryhandlingproxymodel_p.h"

#include "kdgantt_debug.h"

#include <cassert>

using namespace KDGantt;

/*!\class KDGantt::SummaryHandlingProxyModel
 * \brief Proxy model that supports summary gantt items.
 *
 * This proxy model provides the functionality of summary items.
 * A summary item is an item with type KDGantt::TypeSummary and
 * zero or more children in the model that it summarizes.
 * GraphicsView itself does not dictate any policy for summary items,
 * instead the logic for making the summary items start and end points
 * span it's children is provided by this proxy.
 *
 * The start and end times of a summary is the min/max of the
 * start/end times of it's children.
 *
 * \see GraphicsView::setModel
 */

typedef ForwardingProxyModel BASE;

bool SummaryHandlingProxyModel::Private::cacheLookup(const QModelIndex &idx,
        QPair<QDateTime, QDateTime> *result) const
{
    //qCDebug(KDGANTT_LOG) << "cacheLookup("<<idx<<"), cache has " << cached_summary_items.count() << "items";
    QHash<QModelIndex, QPair<QDateTime, QDateTime> >::const_iterator it =
        cached_summary_items.constFind(idx);
    if (it != cached_summary_items.constEnd()) {
        *result = *it;
        return true;
    } else {
        return false;
    }
}

void SummaryHandlingProxyModel::Private::insertInCache(const SummaryHandlingProxyModel *model,
        const QModelIndex &sourceIdx) const
{
    QAbstractItemModel *sourceModel = model->sourceModel();
    const QModelIndex &mainIdx = sourceIdx;
    QDateTime st;
    QDateTime et;

    for (int r = 0; r < sourceModel->rowCount(mainIdx); ++r) {
        QModelIndex pdIdx = model->mapFromSource(sourceModel->index(r, 0, mainIdx));
        /* The probably results in recursive calls here */
        QVariant tmpsv = model->data(pdIdx, StartTimeRole);
        QVariant tmpev = model->data(pdIdx, EndTimeRole);
        if (!qVariantCanConvert<QDateTime>(tmpsv) ||
                !qVariantCanConvert<QDateTime>(tmpev)) {
            qCDebug(KDGANTT_LOG) << "Skipping item " << sourceIdx << " because it doesn't contain QDateTime";
            continue;
        }

        // We need to test for empty strings to
        // avoid a stupid Qt warning
        if (tmpsv.type() == QVariant::String && qVariantValue<QString>(tmpsv).isEmpty()) {
            continue;
        }
        if (tmpev.type() == QVariant::String && qVariantValue<QString>(tmpev).isEmpty()) {
            continue;
        }
        QDateTime tmpst = tmpsv.toDateTime();
        QDateTime tmpet = tmpev.toDateTime();
        if (st.isNull() || st > tmpst) {
            st = tmpst;
        }
        if (et.isNull() || et < tmpet) {
            et = tmpet;
        }
    }
    QVariant tmpssv = sourceModel->data(mainIdx, StartTimeRole);
    QVariant tmpsev = sourceModel->data(mainIdx, EndTimeRole);
    if (qVariantCanConvert<QDateTime>(tmpssv)
            && !(qVariantCanConvert<QString>(tmpssv) && qVariantValue<QString>(tmpssv).isEmpty())
            && qVariantValue<QDateTime>(tmpssv) != st) {
        sourceModel->setData(mainIdx, st, StartTimeRole);
    }
    if (qVariantCanConvert<QDateTime>(tmpsev)
            && !(qVariantCanConvert<QString>(tmpsev) && qVariantValue<QString>(tmpsev).isEmpty())
            && qVariantValue<QDateTime>(tmpsev) != et) {
        sourceModel->setData(mainIdx, et, EndTimeRole);
    }
    cached_summary_items[sourceIdx] = qMakePair(st, et);
}

void SummaryHandlingProxyModel::Private::removeFromCache(const QModelIndex &idx) const
{
    cached_summary_items.remove(idx);
}

void SummaryHandlingProxyModel::Private::clearCache() const
{
    cached_summary_items.clear();
}

/*! Constructor. Creates a new SummaryHandlingProxyModel with
 * parent \a parent
 */
SummaryHandlingProxyModel::SummaryHandlingProxyModel(QObject *parent)
    : BASE(parent), _d(new Private)
{
    init();
}

#define d d_func()
SummaryHandlingProxyModel::~SummaryHandlingProxyModel()
{
}

void SummaryHandlingProxyModel::init()
{
}

namespace
{

// Think this is ugly? Well, it's not from me, it comes from QProxyModel
struct KDPrivateModelIndex {
    int r, c;
    void *p;
    const QAbstractItemModel *m;
};
}

/*! Sets the model to be used as the source model for this proxy.
 * The proxy does not take ownership of the model.
 * \see QAbstractProxyModel::setSourceModel
 */
void SummaryHandlingProxyModel::setSourceModel(QAbstractItemModel *model)
{
    BASE::setSourceModel(model);
    d->clearCache();
}

void SummaryHandlingProxyModel::sourceModelReset()
{
    d->clearCache();
    BASE::sourceModelReset();
}

void SummaryHandlingProxyModel::sourceLayoutChanged()
{
    d->clearCache();
    BASE::sourceLayoutChanged();
}

void SummaryHandlingProxyModel::sourceDataChanged(const QModelIndex &from, const QModelIndex &to)
{
    QAbstractItemModel *model = sourceModel();
    QModelIndex parentIdx = from;
    do {
        const QModelIndex &dataIdx = parentIdx;
        if (model->data(dataIdx, ItemTypeRole) == TypeSummary) {
            //qCDebug(KDGANTT_LOG) << "removing " << parentIdx << "from cache";
            d->removeFromCache(dataIdx);
            QModelIndex proxyDataIdx = mapFromSource(dataIdx);
            Q_EMIT dataChanged(proxyDataIdx, proxyDataIdx);
        }
    } while ((parentIdx = model->parent(parentIdx)) != QModelIndex());

    BASE::sourceDataChanged(from, to);
}

void SummaryHandlingProxyModel::sourceColumnsAboutToBeInserted(const QModelIndex &parentIdx,
        int start,
        int end)
{
    BASE::sourceColumnsAboutToBeInserted(parentIdx, start, end);
    d->clearCache();
}

void SummaryHandlingProxyModel::sourceColumnsAboutToBeRemoved(const QModelIndex &parentIdx,
        int start,
        int end)
{
    BASE::sourceColumnsAboutToBeRemoved(parentIdx, start, end);
    d->clearCache();
}

void SummaryHandlingProxyModel::sourceRowsAboutToBeInserted(const QModelIndex &parentIdx, int start, int end)
{
    BASE::sourceRowsAboutToBeInserted(parentIdx, start, end);
    d->clearCache();
}

void SummaryHandlingProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex &parentIdx, int start, int end)
{
    BASE::sourceRowsAboutToBeRemoved(parentIdx, start, end);
    d->clearCache();
}

/*! \see QAbstractItemModel::flags */
Qt::ItemFlags SummaryHandlingProxyModel::flags(const QModelIndex &idx) const
{
    const QModelIndex sidx = mapToSource(idx);
    const QAbstractItemModel *model = sourceModel();
    Qt::ItemFlags f = model->flags(sidx);
    if (d->isSummary(sidx)) {
        f &= !Qt::ItemIsEditable;
    }
    return f;
}

/*! \see QAbstractItemModel::data */
QVariant SummaryHandlingProxyModel::data(const QModelIndex &proxyIndex, int role) const
{
    //qCDebug(KDGANTT_LOG) << "SummaryHandlingProxyModel::data("<<proxyIndex<<role<<")";
    const QModelIndex sidx = mapToSource(proxyIndex);
    const QAbstractItemModel *model = sourceModel();
    if (d->isSummary(sidx) && (role == StartTimeRole || role == EndTimeRole)) {
        //qCDebug(KDGANTT_LOG) << "requested summary";
        QPair<QDateTime, QDateTime> result;
        if (d->cacheLookup(sidx, &result)) {
            //qCDebug(KDGANTT_LOG) << "SummaryHandlingProxyModel::data(): Looking up summary for " << proxyIndex << role;
            switch (role) {
            case StartTimeRole: return result.first;
            case EndTimeRole: return result.second;
            default: /* fall thru */;
            }
        } else {
            d->insertInCache(this, sidx);
            return data(proxyIndex, role);   /* TODO: Optimise */
        }
    }
    return model->data(sidx, role);
}

/*! \see QAbstractItemModel::setData */
bool SummaryHandlingProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    QAbstractItemModel *model = sourceModel();
    if (role == StartTimeRole || role == EndTimeRole) {
        QModelIndex parentIdx = mapToSource(index);
        do {
            if (d->isSummary(parentIdx)) {
                //qCDebug(KDGANTT_LOG) << "removing " << parentIdx << "from cache";
                d->removeFromCache(parentIdx);
                QModelIndex proxyParentIdx = mapFromSource(parentIdx);
                Q_EMIT dataChanged(proxyParentIdx, proxyParentIdx);
            }
        } while ((parentIdx = model->parent(parentIdx)) != QModelIndex());
    }
    return BASE::setData(index, value, role);
}

#undef d

#ifndef KDAB_NO_UNIT_TESTS

#include "unittest/test.h"

#include <QStandardItemModel>

namespace
{
std::ostream &operator<<(std::ostream &os, const QDateTime &dt)
{
#ifdef QT_NO_STL
    os << dt.toString().toLatin1().constData();
#else
    os << dt.toString().toStdString();
#endif
    return os;
}
}

KDAB_SCOPED_UNITTEST_SIMPLE(KDGantt, SummaryHandlingProxyModel, "test")
{
    SummaryHandlingProxyModel model;
    QStandardItemModel sourceModel;

    model.setSourceModel(&sourceModel);

    QStandardItem *topitem = new QStandardItem(QStringLiteral("Summary"));
    topitem->setData(KDGantt::TypeSummary,  KDGantt::ItemTypeRole);
    sourceModel.appendRow(topitem);

    QStandardItem *task1 = new QStandardItem(QStringLiteral("Task1"));
    task1->setData(KDGantt::TypeTask, KDGantt::ItemTypeRole);
    QStandardItem *task2 = new QStandardItem(QStringLiteral("Task2"));
    task2->setData(KDGantt::TypeTask, KDGantt::ItemTypeRole);
    topitem->appendRow(task1);
    topitem->appendRow(task2);

    QDateTime startdt = QDateTime::currentDateTime();
    QDateTime enddt = startdt.addDays(1);

    task1->setData(startdt, KDGantt::StartTimeRole);
    task1->setData(enddt, KDGantt::EndTimeRole);
    task2->setData(startdt, KDGantt::StartTimeRole);
    task2->setData(enddt, KDGantt::EndTimeRole);

    const QModelIndex topidx = model.index(0, 0, QModelIndex());

    assertEqual(model.data(topidx, KDGantt::ItemTypeRole).toInt(), KDGantt::TypeSummary);
    assertEqual(model.data(model.index(0, 0, topidx), KDGantt::ItemTypeRole).toInt(), KDGantt::TypeTask);

    QDateTime task1startdt = model.data(model.index(0, 0, topidx), KDGantt::StartTimeRole).toDateTime();
    assertEqual(task1startdt, startdt);

    QDateTime summarystartdt = model.data(topidx, KDGantt::StartTimeRole).toDateTime();
    assertEqual(summarystartdt, startdt);
    assertTrue(model.flags(model.index(0, 0, topidx)) & Qt::ItemIsEditable);
    assertFalse(model.flags(topidx) & Qt::ItemIsEditable);
}

#endif /* KDAB_NO_UNIT_TESTS */

