/*
 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
 *
 * This file is part of the KGantt library.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kganttview.h"
#include "kganttview_p.h"

#include "kganttgraphicsitem.h"
#include "kganttitemdelegate.h"
#include "kganttsummaryhandlingproxymodel.h"

#include <QAbstractItemModel>
#include <QGraphicsItem>
#include <QGraphicsRectItem>
#include <QHeaderView>
#include <QPaintEvent>
#include <QScrollBar>
#include <QVBoxLayout>

#include <QDebug>

#include <cassert>

#if defined KDAB_EVAL
#include "../evaldialog/evaldialog.h"
#endif

using namespace KGantt;

namespace
{
class HeaderView : public QHeaderView
{
public:
    explicit HeaderView(QWidget *parent = nullptr)
        : QHeaderView(Qt::Horizontal, parent)
    {
    }

    QSize sizeHint() const override
    {
        QSize s = QHeaderView::sizeHint();
        s.rheight() *= 2;
        return s;
    }
};
}

KGanttTreeView::KGanttTreeView(QAbstractProxyModel *proxy, QWidget *parent)
    : QTreeView(parent)
    , m_controller(this, proxy)
{
    setHeader(new HeaderView);
}

KGanttTreeView::~KGanttTreeView()
{
}

void KGanttTreeView::expandAll(QModelIndex index)
{
    for (int i = 0; i < model()->rowCount(index); i++) {
        QModelIndex indexAt = model()->index(i, 0, index);
        if (model()->hasChildren(indexAt))
            expandAll(indexAt);
        if (isExpanded(indexAt))
            continue;
        expand(indexAt);
    }
}

void KGanttTreeView::collapseAll(QModelIndex index)
{
    for (int i = 0; i < model()->rowCount(index); i++) {
        QModelIndex indexAt = model()->index(i, 0, index);
        if (model()->hasChildren(indexAt))
            collapseAll(indexAt);
        if (!isExpanded(indexAt))
            continue;
        collapse(indexAt);
    }
}

View::Private::Private(View *v)
    : q(v)
    , splitter(v)
    , rowController(nullptr)
    , gfxview(new GraphicsView(&splitter))
    , model(nullptr)
{
    // init();
}

View::Private::~Private()
{
    delete gfxview;
}

void View::Private::init()
{
    KGanttTreeView *tw = new KGanttTreeView(&ganttProxyModel, &splitter);
    tw->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    tw->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);

    q->setLeftView(tw);
    q->setRowController(tw->rowController());

    // gfxview.setRenderHints( QPainter::Antialiasing );

    tw->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    QVBoxLayout *layout = new QVBoxLayout(q);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->addWidget(&splitter);
    q->setLayout(layout);

    constraintProxy.setProxyModel(&ganttProxyModel);
    constraintProxy.setDestinationModel(&mappedConstraintModel);
    setupGraphicsView();
}

void View::Private::setupGraphicsView()
{
    gfxview->setParent(&splitter);
    gfxview->setAlignment(Qt::AlignTop | Qt::AlignLeft);
    gfxview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    gfxview->setSelectionModel(leftWidget->selectionModel());
    gfxview->setConstraintModel(&mappedConstraintModel);
    q->setLeftView(leftWidget);
    q->setRowController(rowController);
    updateScene();
}

void View::Private::updateScene()
{
    gfxview->clearItems();
    if (!model)
        return;

    if (QTreeView *tw = qobject_cast<QTreeView *>(leftWidget)) {
        QModelIndex idx = ganttProxyModel.mapFromSource(model->index(0, 0, leftWidget->rootIndex()));
        do {
            gfxview->updateRow(idx);
        } while ((idx = tw->indexBelow(idx)) != QModelIndex() && gfxview->rowController()->isRowVisible(idx));
        gfxview->updateSceneRect();
    } else {
        const QModelIndex rootidx = ganttProxyModel.mapFromSource(leftWidget->rootIndex());
        for (int r = 0; r < ganttProxyModel.rowCount(rootidx); ++r) {
            gfxview->updateRow(ganttProxyModel.index(r, 0, rootidx));
        }
    }
}

void View::Private::slotCollapsed(const QModelIndex &_idx)
{
    QTreeView *tw = qobject_cast<QTreeView *>(leftWidget);
    if (!tw)
        return;

    bool blocked = gfxview->blockSignals(true);

    QModelIndex idx(_idx);
    const QAbstractItemModel *model = leftWidget->model();
    const QModelIndex pidx = ganttProxyModel.mapFromSource(idx);
    bool isMulti = false;
    for (QModelIndex treewalkidx = pidx; treewalkidx.isValid(); treewalkidx = treewalkidx.parent()) {
        if (treewalkidx.data(ItemTypeRole).toInt() == TypeMulti && !gfxview->rowController()->isRowExpanded(treewalkidx)) {
            isMulti = true;
            break;
        }
    }

    if (!isMulti) {
        for (int i = 0; i < model->rowCount(idx); ++i) {
            gfxview->deleteSubtree(ganttProxyModel.index(i, 0, pidx));
        }
    } else {
        gfxview->updateRow(pidx);
    }
    // qDebug() << "Looking to update from " << idx;
    while ((idx = tw->indexBelow(idx)) != QModelIndex() && gfxview->rowController()->isRowVisible(ganttProxyModel.mapFromSource(idx))) {
        const QModelIndex proxyidx(ganttProxyModel.mapFromSource(idx));
        gfxview->updateRow(proxyidx);
    }
    gfxview->blockSignals(blocked);
    gfxview->updateSceneRect();
}

void View::Private::slotExpanded(const QModelIndex &_idx)
{
    QModelIndex idx(ganttProxyModel.mapFromSource(_idx));
    do {
        // qDebug() << "Updating row" << idx << idx.data( Qt::DisplayRole ).toString();
        gfxview->updateRow(idx);
    } while ((idx = gfxview->rowController()->indexBelow(idx)) != QModelIndex() && gfxview->rowController()->isRowVisible(idx));
    gfxview->updateSceneRect();
}

void View::Private::slotVerticalScrollValueChanged(int val)
{
#if 0
    qDebug() << "View::Private::slotVerticalScrollValueChanged("<<val<<")="
             << val/gfxview->verticalScrollBar()->singleStep();
#endif
    leftWidget->verticalScrollBar()->setValue(val / gfxview->verticalScrollBar()->singleStep());
}

void View::Private::slotLeftWidgetVerticalRangeChanged(int min, int max)
{
    // qDebug() << "View::Private::slotLeftWidgetVerticalRangeChanged("<<min<<max<<")";
    //  In some cases the gfxview has already been deleted when this signal arrive
    if (!gfxview.isNull()) {
        gfxview->verticalScrollBar()->setRange(min, max);
        gfxview->updateSceneRect();
    }
}

void View::Private::slotGfxViewVerticalRangeChanged(int min, int max)
{
    // qDebug() << "View::Private::slotGfxViewVerticalRangeChanged("<<min<<max<<")";
    if (!leftWidget.isNull() && !gfxview.isNull()) {
        int leftMin = leftWidget->verticalScrollBar()->minimum();
        int leftMax = leftWidget->verticalScrollBar()->maximum();
        bool blocked = gfxview->verticalScrollBar()->blockSignals(true);
        gfxview->verticalScrollBar()->setRange(qMax(min, leftMin), qMax(max, leftMax));
        gfxview->verticalScrollBar()->blockSignals(blocked);
    }
}

View::View(QWidget *parent)
    : QWidget(parent)
    , _d(new Private(this))
{
#if defined KDAB_EVAL
    EvalDialog::checkEvalLicense("KD Gantt");
#endif
    _d->init();
}

View::~View()
{
    delete _d;
}

#define d d_func()

void View::setLeftView(QAbstractItemView *aiv)
{
    assert(aiv);
    if (aiv == d->leftWidget)
        return;
    if (!d->leftWidget.isNull()) {
        d->leftWidget->disconnect(this);
        d->leftWidget->hide();
        d->leftWidget->verticalScrollBar()->disconnect(d->gfxview->verticalScrollBar());
        d->gfxview->verticalScrollBar()->disconnect(d->leftWidget->verticalScrollBar());
    }

    d->leftWidget = aiv;
    d->splitter.insertWidget(0, d->leftWidget);

    if (qobject_cast<QTreeView *>(d->leftWidget)) {
        connect(d->leftWidget, SIGNAL(collapsed(QModelIndex)), this, SLOT(slotCollapsed(QModelIndex)));
        connect(d->leftWidget, SIGNAL(expanded(QModelIndex)), this, SLOT(slotExpanded(QModelIndex)));
    }

    connect(d->gfxview->verticalScrollBar(), SIGNAL(valueChanged(int)), d->leftWidget->verticalScrollBar(), SLOT(setValue(int)));
    connect(d->leftWidget->verticalScrollBar(), SIGNAL(valueChanged(int)), d->gfxview->verticalScrollBar(), SLOT(setValue(int)));
    connect(d->leftWidget->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(slotLeftWidgetVerticalRangeChanged(int, int)));
    connect(d->gfxview->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(slotGfxViewVerticalRangeChanged(int, int)));
}

void View::setRowController(AbstractRowController *ctrl)
{
    if (ctrl == d->rowController && d->gfxview->rowController() == ctrl)
        return;
    d->rowController = ctrl;
    d->gfxview->setRowController(d->rowController);
}

AbstractRowController *View::rowController()
{
    return d->rowController;
}

const AbstractRowController *View::rowController() const
{
    return d->rowController;
}

const QAbstractItemView *View::leftView() const
{
    return d->leftWidget;
}

QAbstractItemView *View::leftView()
{
    return d->leftWidget;
}

void View::setGraphicsView(GraphicsView *gv)
{
    if (gv != d->gfxview) {
        GraphicsView *old = d->gfxview;
        AbstractGrid *grid = old->takeGrid();
        d->gfxview = gv;
        d->gfxview->setModel(old->model()); // use the old ForwardingProxyModel
        d->setupGraphicsView();
        d->gfxview->setGrid(grid);
        delete old;
    }
}

const GraphicsView *View::graphicsView() const
{
    return d->gfxview;
}

GraphicsView *View::graphicsView()
{
    return d->gfxview;
}

const QSplitter *View::splitter() const
{
    return &d->splitter;
}

QSplitter *View::splitter()
{
    return &d->splitter;
}

QAbstractItemModel *View::model() const
{
    return leftView()->model();
}

void View::setModel(QAbstractItemModel *model)
{
    leftView()->setModel(model);
    d->ganttProxyModel.setSourceModel(model);
    d->gfxview->setModel(&d->ganttProxyModel);
}

QItemSelectionModel *View::selectionModel() const
{
    return leftView()->selectionModel();
}

void View::setSelectionModel(QItemSelectionModel *smodel)
{
    leftView()->setSelectionModel(smodel);
    d->gfxview->setSelectionModel(new QItemSelectionModel(&(d->ganttProxyModel), this));
}

void View::setGrid(AbstractGrid *grid)
{
    d->gfxview->setGrid(grid);
}

void View::expandAll(QModelIndex index)
{
    // FIXME:
    // It is legal to call setLeftView() with any QAbstractItemView,
    // so expandAll should be reimplemented to work with that.
    KGanttTreeView *tw = qobject_cast<KGanttTreeView *>(leftView());
    if (tw) {
        tw->expandAll(index);
    }
}

void View::collapseAll(QModelIndex index)
{
    // FIXME:
    // It is legal to call setLeftView() with any QAbstractItemView,
    // so expandAll should be reimplemented to work with that.
    KGanttTreeView *tw = qobject_cast<KGanttTreeView *>(leftView());
    if (tw) {
        tw->collapseAll(index);
    }
}

AbstractGrid *View::grid() const
{
    return d->gfxview->grid();
}

QModelIndex View::rootIndex() const
{
    return leftView()->rootIndex();
}

void View::setRootIndex(const QModelIndex &idx)
{
    leftView()->setRootIndex(idx);
    d->gfxview->setRootIndex(idx);
}

ItemDelegate *View::itemDelegate() const
{
    return d->gfxview->itemDelegate();
}

void View::setItemDelegate(ItemDelegate *delegate)
{
    leftView()->setItemDelegate(delegate);
    d->gfxview->setItemDelegate(delegate);
}

void View::setConstraintModel(ConstraintModel *cm)
{
    d->constraintProxy.setSourceModel(cm);
    d->gfxview->setConstraintModel(&d->mappedConstraintModel);
}

ConstraintModel *View::constraintModel() const
{
    return d->constraintProxy.sourceModel();
}

const QAbstractProxyModel *View::ganttProxyModel() const
{
    return &(d->ganttProxyModel);
}

QAbstractProxyModel *View::ganttProxyModel()
{
    return &(d->ganttProxyModel);
}

void View::ensureVisible(const QModelIndex &index)
{
    QGraphicsView *view = graphicsView();
    KGantt::GraphicsScene *scene = static_cast<KGantt::GraphicsScene *>(view->scene());
    if (!scene)
        return;

    KGantt::SummaryHandlingProxyModel *model = static_cast<KGantt::SummaryHandlingProxyModel *>(scene->summaryHandlingModel());

    const QModelIndex pidx = d->ganttProxyModel.mapFromSource(index);
    const QModelIndex idx = model->mapFromSource(pidx);
    QGraphicsItem *item = scene->findItem(idx);
    view->ensureVisible(item);
}

void View::resizeEvent(QResizeEvent *ev)
{
    QWidget::resizeEvent(ev);
}

QModelIndex View::indexAt(const QPoint &pos) const
{
    return d->gfxview->indexAt(pos);
}

void View::print(QPrinter *printer, bool drawRowLabels, bool drawColumnLabels)
{
    graphicsView()->print(printer, drawRowLabels, drawColumnLabels);
}

void View::print(QPrinter *printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels)
{
    graphicsView()->print(printer, start, end, drawRowLabels, drawColumnLabels);
}

void View::print(QPainter *painter, const QRectF &target, bool drawRowLabels, bool drawColumnLabels)
{
    d->gfxview->print(painter, target, drawRowLabels, drawColumnLabels);
}

void View::print(QPainter *painter, qreal start, qreal end, const QRectF &target, bool drawRowLabels, bool drawColumnLabels)
{
    d->gfxview->print(painter, start, end, target, drawRowLabels, drawColumnLabels);
}

void View::printDiagram(QPrinter *printer, const PrintingContext &context)
{
    graphicsView()->printDiagram(printer, context);
}

#include "moc_kganttview.cpp"

#ifndef KDAB_NO_UNIT_TESTS
#include "unittest/test.h"

#include "kganttlistviewrowcontroller.h"
#include <QApplication>
#include <QListView>
#include <QPixmap>
#include <QTimer>

KDAB_SCOPED_UNITTEST_SIMPLE(KGantt, View, "test")
{
    View view(nullptr);
#if 0 // GUI tests do not work well on the server
    QTimer::singleShot( 1000, qApp, SLOT(quit()) );
    view.show();

    qApp->exec();
    QPixmap screenshot1 = QPixmap::grabWidget( &view );

    QTreeView* tv = new QTreeView;
    view.setLeftView( tv );
    view.setRowController( new TreeViewRowController(tv,view.ganttProxyModel()) );

    QTimer::singleShot( 1000, qApp, SLOT(quit()) );

    qApp->exec();
    QPixmap screenshot2 = QPixmap::grabWidget( &view );

    assertEqual( screenshot1.toImage(),  screenshot2.toImage() );

    QListView* lv = new QListView;
    view.setLeftView(lv);
    view.setRowController( new ListViewRowController(lv,view.ganttProxyModel()));
    view.show();
    QTimer::singleShot( 1000, qApp, SLOT(quit()) );
    qApp->exec();
#endif
}
#endif /* KDAB_NO_UNIT_TESTS */
