/*
 * Copyright (c) 2014-2016 Isode Limited.
 * All rights reserved.
 * See the COPYING file for more information.
 */

#include <Swift/QtUI/Trellis/QtDynamicGridLayout.h>

#include <cassert>

#include <QApplication>
#include <QEvent>
#include <QGridLayout>
#include <QLayoutItem>
#include <QtDebug>

#include <Swiften/Base/Log.h>

#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/QtTabWidget.h>
#include <Swift/QtUI/QtTabbable.h>
#include <Swift/QtUI/Trellis/QtDNDTabBar.h>

namespace Swift {

QtDynamicGridLayout::QtDynamicGridLayout(QWidget* parent, bool enableDND) : QWidget(parent), dndEnabled_(enableDND), movingTab_(nullptr) {
    gridLayout_ = new QGridLayout(this);
    setContentsMargins(0,0,0,0);
    setDimensions(QSize(1,1));
    connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*,QWidget*)));
}

QtDynamicGridLayout::~QtDynamicGridLayout() {
}

int QtDynamicGridLayout::addTab(QtTabbable* tab, const QString& title) {
    assert(gridLayout_->rowCount() > 0 && gridLayout_->columnCount() > 0);

    QPoint lastPos(0,0);
    if (tabPositions_.contains(P2QSTRING(tab->getID()))) {
        lastPos = tabPositions_[P2QSTRING(tab->getID())];
    }

    lastPos = QPoint(qMin(lastPos.x(), gridLayout_->columnCount() - 1), qMin(lastPos.y(), gridLayout_->rowCount() - 1));

    QLayoutItem* item = gridLayout_->itemAtPosition(lastPos.y(), lastPos.x());
    QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(item ? item->widget() : nullptr);
    if (tabWidget) {
        tabWidget->addTab(tab, title);
    }
    tab->setEmphasiseFocus(getDimension().width() > 1 || getDimension().height() > 1);
    return tabWidget ? indexOf(tab) : -1;
}

int QtDynamicGridLayout::count() const {
    int count = 0;
    for (int y = 0; y < gridLayout_->rowCount(); y++) {
        for (int x = 0; x < gridLayout_->columnCount(); x++) {
            QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
            QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
            if (tabWidget) {
                count += tabWidget->count();
            }
        }
    }
    return count;
}

QWidget* QtDynamicGridLayout::widget(int index) const {
    QWidget* widgetAtIndex = nullptr;
    for (int y = 0; y < gridLayout_->rowCount(); y++) {
        for (int x = 0; x < gridLayout_->columnCount(); x++) {
            QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
            QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem ? layoutItem->widget() : nullptr);
            if (tabWidget) {
                if (index < tabWidget->count()) {
                    widgetAtIndex = tabWidget->widget(index);
                    return widgetAtIndex;
                }
                else {
                    index -= tabWidget->count();
                }
            }
        }
    }
    return widgetAtIndex;
}

int QtDynamicGridLayout::indexOf(const QWidget* widget) const {
    int index = 0;
    if (widget) {
        for (int y = 0; y < gridLayout_->rowCount(); y++) {
            for (int x = 0; x < gridLayout_->columnCount(); x++) {
                QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
                QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
                if (tabWidget) {
                    for (int n = 0; n < tabWidget->count(); n++) {
                        QWidget* nthWidget = tabWidget->widget(n);
                        if (nthWidget == widget) {
                            return index;
                        }
                        index++;
                    }
                }
            }
        }
    }
    return -1;
}

void QtDynamicGridLayout::handleApplicationFocusChanged(QWidget*, QWidget* newFocus) {
    if (movingTab_ || resizing_) {
        return;
    }

    if (newFocus) {
        if (isAncestorOf(newFocus)) {
            QtTabbable *newTab = dynamic_cast<QtTabbable*>(newFocus->parentWidget());
            if (newTab) {
                onCurrentIndexChanged(currentIndex());
            }
        }
    }
}

int QtDynamicGridLayout::currentIndex() const {
    return indexOf(currentWidget());
}

void QtDynamicGridLayout::setCurrentIndex(int index) {
    int tabIndex = -1;
    QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
    if (tabIndex >= 0) {
        tabWidget->setCurrentIndex(tabIndex);
        if (!tabWidget->hasFocus()) {
            tabWidget->widget(tabIndex)->setFocus(Qt::TabFocusReason);
        }
    } else {
        assert(false);
    }
}

void QtDynamicGridLayout::removeTab(int index) {
    int tabIndex = -1;
    QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
    if (tabWidget) {
        QWidget* tab = tabWidget->widget(tabIndex);
        tabWidget->removeTab(tabIndex);
        tab->setParent(nullptr);
    }
}

/**
 * This event filter serves the purpose of filtering out all QEvent::Show events targeted at
 * all widgets excepts the currently moving widget.
 * It is required because of the way Qt internally implements the QTabBar::moveTab method.
 * It does not move the actual tab in the underlying structure, but instead removes it from
 * a stacked layout and later adds it again.
 * Both the remove and insert produce a lot signal emission and focus changes. Most of which
 * the application MUST NOT react on because of the QTabBar and the corresponding QTabWidget
 * being out of sync in an inconsistent state.
 */
bool QtDynamicGridLayout::eventFilter(QObject* object, QEvent* event) {
    QtTabbable* tab = qobject_cast<QtTabbable*>(object);
    if (!tab) {
        return false;
    }
    if (tab && (tab != movingTab_)) {
        if (event->type() == QEvent::Show) {
            return true;
        }
    }
    return false;
}

QWidget* QtDynamicGridLayout::currentWidget() const {
    QWidget* current = nullptr;
    current = focusWidget();
    while (current && !dynamic_cast<QtTabbable*>(current)) {
        if (current->parentWidget()) {
            current = current->parentWidget();
        } else {
            current = nullptr;
            break;
        }
    }
    if (!current) {
        current = widget(0);
    }
    return current;
}

void QtDynamicGridLayout::setCurrentWidget(QWidget* widget) {
    if (widget) {
        for (int y = 0; y < gridLayout_->rowCount(); y++) {
            for (int x = 0; x < gridLayout_->columnCount(); x++) {
                QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
                QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
                if (tabWidget) {
                    for (int n = 0; n < tabWidget->count(); n++) {
                        if (tabWidget->widget(n) == widget) {
                            tabWidget->setCurrentWidget(widget);
                        }
                    }
                }
            }
        }
    }
}

QtTabWidget* QtDynamicGridLayout::indexToTabWidget(int index, int& tabIndex) {
    for (int y = 0; y < gridLayout_->rowCount(); y++) {
        for (int x = 0; x < gridLayout_->columnCount(); x++) {
            QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
            QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
            if (tabWidget) {
                if (index < tabWidget->count()) {
                    tabIndex = index;
                    return tabWidget;
                }
                else {
                    index -= tabWidget->count();
                    if (index < 0) {
                        qWarning() << "Called QtDynamicGridLayout::setCurrentIndex with index out of bounds: index = " << index;
                        tabIndex = -1;
                        return nullptr;
                    }
                }
            }
        }
    }
    tabIndex = -1;
    return nullptr;
}

bool QtDynamicGridLayout::isDNDEnabled() const {
    return dndEnabled_;
}

QHash<QString, QPoint> QtDynamicGridLayout::getTabPositions() const {
    return tabPositions_;
}

void QtDynamicGridLayout::setTabPositions(const QHash<QString, QPoint> positions) {
    tabPositions_ = positions;
}

QSize QtDynamicGridLayout::getDimension() const {
    return QSize(gridLayout_->columnCount(), gridLayout_->rowCount());
}

void QtDynamicGridLayout::setDimensions(const QSize& dim) {
    resizing_ = true;
    assert(dim.width() > 0 && dim.height() > 0);
    setUpdatesEnabled(false);

    QWidget* restoredWidget = currentWidget();

    QGridLayout* oldLayout = dynamic_cast<QGridLayout*>(layout());
    QGridLayout* newLayout = new QGridLayout(this);
    newLayout->setSpacing(4);
    newLayout->setContentsMargins(0,0,0,0);

    int oldWidth = oldLayout->columnCount();
    int oldHeight = oldLayout->rowCount();
    int maxCol = qMax(oldWidth, dim.width());
    int minCol = qMin(oldWidth, dim.width());
    int maxRow = qMax(oldHeight, dim.height());
    int minRow = qMin(oldHeight, dim.height());

    for (int row = 0; row < maxRow; row++) {
        for (int col = 0; col < maxCol; col++) {
            QLayoutItem* oldItem = oldLayout->itemAtPosition(row, col);
            QLayoutItem* newItem = newLayout->itemAtPosition(row, col);
            bool removeRow = !(row < dim.height());
            bool removeCol = !(col < dim.width());

            if (removeCol || removeRow) {
                if (oldItem) {
                    int squeezeRow = removeRow ? (minRow - 1) : row;
                    int squeezeCol = removeCol ? (minCol - 1) : col;
                    newItem = newLayout->itemAtPosition(squeezeRow, squeezeCol);
                    if (!newItem) {
                        newLayout->addWidget(createDNDTabWidget(this), squeezeRow, squeezeCol);
                        newItem = newLayout->itemAtPosition(squeezeRow, squeezeCol);
                    }
                    QtTabWidget* oldTabWidget = dynamic_cast<QtTabWidget*>(oldItem->widget());
                    QtTabWidget* newTabWidget = dynamic_cast<QtTabWidget*>(newItem->widget());
                    assert(oldTabWidget && newTabWidget);

                    oldTabWidget->hide();
                    while(oldTabWidget->count()) {
                        QIcon icon = oldTabWidget->tabIcon(0);
                        QString text = oldTabWidget->tabText(0);
                        QWidget* movingTab = oldTabWidget->widget(0);
                        //If handling was allowed, QtChatTabs::handleWidgetShown() would be triggered when newTabWidget has no tabs.
                        //That would access indices of the gridLayout_ that are null because they have been migrated to the newLayout.
                        movingTab->blockSignals(true);
                        newTabWidget->addTab(movingTab, icon, text);
                        movingTab->blockSignals(false);
                    }
                    delete oldTabWidget;
                }
            } else {
                if (oldItem) {
                    newLayout->addWidget(oldItem->widget(), row, col);
                    newItem = newLayout->itemAtPosition(row, col);
                } else {
                    newLayout->addWidget(createDNDTabWidget(this), row, col);
                }
            }
        }
    }

    for (int col = 0; col < dim.width(); col++) {
        newLayout->setColumnStretch(col, 1);
    }
    for (int row = 0; row < dim.height(); row++) {
        newLayout->setRowStretch(row, 1);
    }

    setUpdatesEnabled(true);
    delete layout();
    setLayout(newLayout);
    gridLayout_ = newLayout;

    resizing_ = false;
    setCurrentWidget(restoredWidget);

    updateEmphasiseFocusOnTabs();
}

void QtDynamicGridLayout::updateEmphasiseFocusOnTabs() {
    const auto currentDimensions = getDimension();

    for (int y = 0; y < gridLayout_->rowCount(); y++) {
        for (int x = 0; x < gridLayout_->columnCount(); x++) {
            QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
            QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
            assert(tabWidget);
            for (int index = 0; index < tabWidget->count(); index++) {
                QtTabbable* tab = dynamic_cast<QtTabbable*>(tabWidget->widget(index));
                assert(tab);
                tab->setEmphasiseFocus(currentDimensions.height() > 1 || currentDimensions.width() > 1);
            }
        }
    }
}

void QtDynamicGridLayout::moveCurrentTabRight() {
    int index = currentIndex();
    if (index >= 0) {
        int tabIndex = -1;
        QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
        assert(tabWidget);
        int newTabIndex = (tabIndex + 1) % tabWidget->count();
        moveTab(tabWidget, tabIndex, newTabIndex);
    }
}

void QtDynamicGridLayout::moveCurrentTabLeft() {
    int index = currentIndex();
    if (index >= 0) {
        int tabIndex = -1;
        QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
        assert(tabWidget);
        int newTabIndex = (tabWidget->count() + tabIndex - 1) % tabWidget->count();
        moveTab(tabWidget, tabIndex, newTabIndex);
    }
}

void QtDynamicGridLayout::moveCurrentTabToNextGroup() {
    int index = currentIndex();
    if (index >= 0) {
        int tabIndex = -1;
        QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);

        int row = -1;
        int col = -1;
        int tmp;
        gridLayout_->getItemPosition(gridLayout_->indexOf(tabWidget), &row, &col, &tmp, &tmp);

        // calculate next cell
        col++;
        if (!(col < gridLayout_->columnCount())) {
            col = 0;
            row++;
            if (!(row < gridLayout_->rowCount())) {
                row = 0;
            }
        }

        QtTabWidget* targetTabWidget = dynamic_cast<QtTabWidget*>(gridLayout_->itemAtPosition(row, col)->widget());
        assert(tabWidget);
        assert(targetTabWidget);

        // fetch tab information
        QWidget* tab = tabWidget->widget(tabIndex);
        QString tabText = tabWidget->tabText(tabIndex);

        // move tab
        tab->blockSignals(true);
        targetTabWidget->addTab(tab, tabText);
        tab->blockSignals(false);
        tab->setFocus(Qt::TabFocusReason);

        updateTabPositions();
    }
}

void QtDynamicGridLayout::moveCurrentTabToPreviousGroup() {
    int index = currentIndex();
    if (index >= 0) {
        int tabIndex = -1;
        QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);

        int row = -1;
        int col = -1;
        int tmp;
        gridLayout_->getItemPosition(gridLayout_->indexOf(tabWidget), &row, &col, &tmp, &tmp);

        // calculate next cell
        col--;
        if (col < 0) {
            col = gridLayout_->columnCount() - 1;
            row--;
            if (row < 0) {
                row = gridLayout_->rowCount() - 1;
            }
        }

        QtTabWidget* targetTabWidget = dynamic_cast<QtTabWidget*>(gridLayout_->itemAtPosition(row, col)->widget());
        assert(tabWidget);
        assert(targetTabWidget);

        // fetch tab information
        QWidget* tab = tabWidget->widget(tabIndex);
        QString tabText = tabWidget->tabText(tabIndex);

        // move tab
        tab->blockSignals(true);
        targetTabWidget->addTab(tab, tabText);
        tab->blockSignals(false);
        tab->setFocus(Qt::TabFocusReason);

        updateTabPositions();
    }
}

void QtDynamicGridLayout::handleTabCloseRequested(int index) {
    updateTabPositions();
    QtTabWidget* tabWidgetSender = dynamic_cast<QtTabWidget*>(sender());
    for (int y = 0; y < gridLayout_->rowCount(); y++) {
        for (int x = 0; x < gridLayout_->columnCount(); x++) {
            QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
            QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
            if (tabWidget == tabWidgetSender) {
                tabCloseRequested(index);
            }
            else {
                index += tabWidget->count();
            }
        }
    }
}

void QtDynamicGridLayout::handleTabCurrentChanged(int index) {
    if (movingTab_) {
        return;
    }

    if (index >= 0) {
        QTabWidget* sendingTabWidget = dynamic_cast<QTabWidget*>(sender());
        assert(sendingTabWidget);
        sendingTabWidget->widget(index)->setFocus();
    }
}

void QtDynamicGridLayout::updateTabPositions() {
    for (int y = 0; y < gridLayout_->rowCount(); y++) {
        for (int x = 0; x < gridLayout_->columnCount(); x++) {
            QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
            QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
            assert(tabWidget);
            for (int index = 0; index < tabWidget->count(); index++) {
                QtTabbable* tab = dynamic_cast<QtTabbable*>(tabWidget->widget(index));
                assert(tab);
                tabPositions_.insert(P2QSTRING(tab->getID()), QPoint(x, y));
            }
        }
    }
}

void QtDynamicGridLayout::moveTab(QtTabWidget* tabWidget, int oldIndex, int newIndex) {
#if QT_VERSION >= 0x040500
    SWIFT_LOG_ASSERT(movingTab_ == nullptr, error) << std::endl;
    movingTab_ = qobject_cast<QtTabbable*>(tabWidget->widget(oldIndex));
    SWIFT_LOG_ASSERT(movingTab_ != nullptr, error) << std::endl;

    if (movingTab_) {
        // Install event filter that filters out events issued during the internal movement of the
        // tab but not targeted at the moving tab.
        qApp->installEventFilter(this);

        tabWidget->tabBar()->moveTab(oldIndex, newIndex);

        qApp->removeEventFilter(this);
        SWIFT_LOG_ASSERT(movingTab_ == tabWidget->widget(newIndex), error) << std::endl;
    }
    movingTab_ = nullptr;
    tabWidget->widget(newIndex)->setFocus();
#else
#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled.
#endif
}

QtTabWidget* QtDynamicGridLayout::createDNDTabWidget(QWidget* parent) {
    QtTabWidget* tab = new QtTabWidget(parent);
    if (dndEnabled_) {
        QtDNDTabBar* tabBar = new QtDNDTabBar(tab);
        connect(tabBar, SIGNAL(onDropSucceeded()), this, SLOT(updateTabPositions()));
        tab->setTabBar(tabBar);
    }
    tab->setUsesScrollButtons(true);
    tab->setElideMode(Qt::ElideRight);
#if QT_VERSION >= 0x040500
    /*For Macs, change the tab rendering.*/
    tab->setDocumentMode(true);
    /*Closable tabs are only in Qt4.5 and later*/
    tab->setTabsClosable(true);
    tab->setMovable(true);
    connect(tab, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int)));
    connect(tab, SIGNAL(currentChanged(int)), this, SLOT(handleTabCurrentChanged(int)));
#else
#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled.
#endif
    return tab;
}

}
