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

#include <Swift/QtUI/QtChatTabs.h>

#include <algorithm>
#include <vector>

#include <QAction>
#include <QApplication>
#include <QCloseEvent>
#include <QCursor>
#include <QDesktopWidget>
#include <QLayout>
#include <QMenu>
#include <QTabBar>
#include <QTabWidget>
#include <QtGlobal>
#include <QWindow>

#include <Swiften/Base/Log.h>

#include <Swift/Controllers/ChatMessageSummarizer.h>

#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/QtTabWidget.h>
#include <Swift/QtUI/QtTabbable.h>
#include <Swift/QtUI/QtUISettingConstants.h>
#include <Swift/QtUI/Trellis/QtDynamicGridLayout.h>
#include <Swift/QtUI/Trellis/QtGridSelectionDialog.h>

namespace Swift {
QtChatTabs::QtChatTabs(bool singleWindow, SettingsProvider* settingsProvider, bool trellisMode) : QWidget(), singleWindow_(singleWindow), settingsProvider_(settingsProvider), trellisMode_(trellisMode), dynamicGrid_(nullptr), gridSelectionDialog_(nullptr) {
#ifndef Q_OS_MAC
    setWindowIcon(QIcon(":/logo-chat-16.png"));
#else
    setAttribute(Qt::WA_ShowWithoutActivating);
#endif
    dynamicGrid_ = new QtDynamicGridLayout(this, trellisMode);
    connect(dynamicGrid_, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int)));
    connect(dynamicGrid_, SIGNAL(onCurrentIndexChanged(int)), this, SLOT(handleCurrentTabIndexChanged(int)));

    QVBoxLayout *layout = new QVBoxLayout;
    layout->setSpacing(0);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->addWidget(dynamicGrid_);
    setLayout(layout);

    if (trellisMode) {
        // restore size
        std::string gridSizeString = settingsProvider->getSetting(QtUISettingConstants::TRELLIS_GRID_SIZE);
        if (!gridSizeString.empty()) {
            QByteArray gridSizeData = QByteArray::fromBase64(P2QSTRING(gridSizeString).toUtf8());
            QDataStream dataStreamGridSize(&gridSizeData, QIODevice::ReadWrite);
            QSize gridSize(1,1);
            dataStreamGridSize >> gridSize;
            dynamicGrid_->setDimensions(gridSize);
        }

        // restore positions
        std::string tabPositionsString = settingsProvider->getSetting(QtUISettingConstants::TRELLIS_GRID_POSITIONS);
        if (!tabPositionsString.empty()) {
            QByteArray tabPositionsData = QByteArray::fromBase64(P2QSTRING(tabPositionsString).toUtf8());
            QDataStream inTabPositions(&tabPositionsData, QIODevice::ReadWrite);
            QHash<QString, QPoint> tabPositions;
            inTabPositions >> tabPositions;
            dynamicGrid_->setTabPositions(tabPositions);
        }
    }

    gridSelectionDialog_ = new QtGridSelectionDialog();

    // setup shortcuts
    shortcuts_ << new QShortcut(QKeySequence(tr("CTRL+W", "Close chat tab.")), window(), SLOT(handleCloseTabShortcut()));
    shortcuts_ << new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageUp), window(), SLOT(handleRequestedPreviousTab()));
    shortcuts_ << new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageDown), window(), SLOT(handleRequestedNextTab()));
    shortcuts_ << new QShortcut(QKeySequence(Qt::ALT + Qt::Key_A), window(), SLOT(handleRequestedActiveTab()));
}

QtChatTabs::~QtChatTabs() {
    for (auto shortcut : shortcuts_) {
        delete shortcut;
    }

    if (trellisMode_) {
        storeTabPositions();
    }
    delete gridSelectionDialog_;
}

void QtChatTabs::closeEvent(QCloseEvent* event) {
    //Hide first to prevent flickering as each tab is removed.
    hide();
    if (trellisMode_) {
        storeTabPositions();
    }

    for (int i = dynamicGrid_->count() - 1; i >= 0; i--) {
        dynamicGrid_->widget(i)->close();
    }
    event->accept();
}

QtTabbable* QtChatTabs::getCurrentTab() {
    return qobject_cast<QtTabbable*>(dynamicGrid_->currentWidget());
}

void QtChatTabs::setViewMenu(QMenu* viewMenu) {
    if (trellisMode_) {
        viewMenu->addSeparator();
        QAction* action = new QAction(tr("Change &layout"), this);
        action->setShortcutContext(Qt::ApplicationShortcut);
        action->setShortcut(QKeySequence(tr("Ctrl+Alt+L")));
        connect(action, SIGNAL(triggered()), this, SLOT(handleOpenLayoutChangeDialog()));
        viewMenu->addAction(action);

        action = new QAction(tr("Move Tab right"), this);
        action->setShortcutContext(Qt::ApplicationShortcut);
        action->setShortcut(QKeySequence(tr("Ctrl+Shift+PgDown")));
        connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabRight()));
        viewMenu->addAction(action);

        action = new QAction(tr("Move Tab left"), this);
        action->setShortcutContext(Qt::ApplicationShortcut);
        action->setShortcut(QKeySequence(tr("Ctrl+Shift+PgUp")));
        connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabLeft()));
        viewMenu->addAction(action);

        action = new QAction(tr("Move Tab to next group"), this);
        action->setShortcutContext(Qt::ApplicationShortcut);
        action->setShortcut(QKeySequence(tr("Ctrl+Alt+PgDown")));
        connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabToNextGroup()));
        viewMenu->addAction(action);

        action = new QAction(tr("Move Tab to previous group"), this);
        action->setShortcutContext(Qt::ApplicationShortcut);
        action->setShortcut(QKeySequence(tr("Ctrl+Alt+PgUp")));
        connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabToPreviousGroup()));
        viewMenu->addAction(action);
    }
}

void QtChatTabs::addTab(QtTabbable* tab) {
    QSizePolicy policy = sizePolicy();
    /* Chat windows like to grow - don't let them */
    setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    dynamicGrid_->addTab(tab, tab->windowTitle());
    connect(tab, SIGNAL(titleUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection);
    connect(tab, SIGNAL(countUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection);
    connect(tab, SIGNAL(windowClosing()), this, SLOT(handleTabClosing()), Qt::UniqueConnection);
    connect(tab, SIGNAL(windowOpening()), this, SLOT(handleWidgetShown()), Qt::UniqueConnection);
    connect(tab, SIGNAL(wantsToActivate()), this, SLOT(handleWantsToActivate()), Qt::UniqueConnection);
    connect(tab, SIGNAL(requestNextTab()), this, SLOT(handleRequestedNextTab()), Qt::UniqueConnection);
    connect(tab, SIGNAL(requestActiveTab()), this, SLOT(handleRequestedActiveTab()), Qt::UniqueConnection);
    connect(tab, SIGNAL(requestPreviousTab()), this, SLOT(handleRequestedPreviousTab()), Qt::UniqueConnection);
    connect(tab, SIGNAL(requestFlash()), this, SLOT(flash()), Qt::UniqueConnection);
    setSizePolicy(policy);
}

void QtChatTabs::handleWidgetShown() {
    QtTabbable* widget = qobject_cast<QtTabbable*>(sender());
    if (!widget) {
        return;
    }
    checkForFirstShow();
    if (dynamicGrid_->indexOf(widget) >= 0) {
        handleTabTitleUpdated(widget);
        return;
    }
    widget->blockSignals(true);
    addTab(widget);
    widget->blockSignals(false);
    show();
}

void QtChatTabs::handleCurrentTabIndexChanged(int newIndex) {
    handleTabTitleUpdated(dynamicGrid_->widget(newIndex));
}

void QtChatTabs::handleWantsToActivate() {
    QtTabbable* widget = qobject_cast<QtTabbable*>(sender());
    Q_ASSERT(widget);
    //Un-minimize and bring to front.
    setWindowState(windowState() & ~Qt::WindowMinimized);
    setWindowState(windowState() | Qt::WindowActive);
    show();
    widget->show();
    dynamicGrid_->setCurrentWidget(widget);
    handleTabTitleUpdated(widget);
    widget->setFocus();
    raise();
    activateWindow();
}

void QtChatTabs::handleTabClosing() {
    QWidget* widget = qobject_cast<QWidget*>(sender());
    int index;
    if (widget && ((index = dynamicGrid_->indexOf(widget)) >= 0)) {
        dynamicGrid_->removeTab(index);
        if (dynamicGrid_->count() == 0) {
            if (!singleWindow_) {
                hide();
            }
            else {
                setWindowTitle("");
                onTitleChanged("");
            }
        }
        else {
            handleTabTitleUpdated(dynamicGrid_->currentWidget());
        }
    }
}

void QtChatTabs::handleRequestedPreviousTab() {
    int newIndex = dynamicGrid_->currentIndex() - 1;
    dynamicGrid_->setCurrentIndex(newIndex >= 0 ? newIndex : dynamicGrid_->count() - 1);
}

void QtChatTabs::handleRequestedNextTab() {
    int newIndex = dynamicGrid_->currentIndex() + 1;
    dynamicGrid_->setCurrentIndex(newIndex < dynamicGrid_->count() ? newIndex : 0);
}

void QtChatTabs::handleRequestedActiveTab() {
    QtTabbable::AlertType types[] = {QtTabbable::WaitingActivity, QtTabbable::ImpendingActivity};
    bool finished = false;
    for (auto& type : types) {
        bool looped = false;
        for (int i = dynamicGrid_->currentIndex() + 1; !finished && i != dynamicGrid_->currentIndex(); i++) {
            if (i >= dynamicGrid_->count()) {
                if (looped) {
                    break;
                }
                looped = true;
                i = 0;
            }
            if (qobject_cast<QtTabbable*>(dynamicGrid_->widget(i))->getWidgetAlertState() == type) {
                dynamicGrid_->setCurrentIndex(i);
                finished = true;
                break;
            }
        }
    }
}


void QtChatTabs::handleCloseTabShortcut() {
    QWidget* currentWidget = dynamicGrid_->currentWidget();
    if (currentWidget) {
        currentWidget->close();
    }
}

void QtChatTabs::handleTabCloseRequested(int index) {
    if (trellisMode_) {
        storeTabPositions();
    }

    assert(index < dynamicGrid_->count());
    QWidget* widget = dynamicGrid_->widget(index);
    assert(widget);
    widget->close();
}

void QtChatTabs::handleTabTitleUpdated() {
    QWidget* widget = qobject_cast<QWidget*>(sender());
    handleTabTitleUpdated(widget);
}

void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
    if (!widget) {
        return;
    }
    QtTabbable* tabbable = qobject_cast<QtTabbable*>(widget);
    int index = dynamicGrid_->indexOf(widget);
    if (index < 0) {
        return;
    }

    QString tabText = tabbable->windowTitle().simplified();
    // look for spectrum-generated and other long JID localparts, and
    // try to abbreviate the resulting long tab texts
    QRegExp hasTrailingGarbage("^(.[-\\w\\s,&]+)([^\\s\\,w].*)$");
    if (hasTrailingGarbage.exactMatch(tabText) &&
        hasTrailingGarbage.cap(1).simplified().length() >= 2 &&
        hasTrailingGarbage.cap(2).length() >= 7) {
        // there may be some trailing garbage, and it's long
        // enough to be worth removing, and we'd leave at
        // least a couple of characters.
        tabText = hasTrailingGarbage.cap(1).simplified();
    }
    // QTabBar interprets &, so escape that
    tabText.replace("&", "&&");
    // see which alt[a-z] keys other tabs use
    bool accelsTaken[26];
    int i = 0;
    while (i < 26) {
        accelsTaken[i] = (i == 0); //A is used for 'switch to active tab'
        i++;
    }

    int other = dynamicGrid_->count();
    while (other >= 0) {
        other--;
        if (other != index) {
            int tabIndex = -1;
            QtTabWidget* tabWidget = dynamicGrid_->indexToTabWidget(other, tabIndex);
            QString t = tabWidget->tabBar()->tabText(tabIndex).toLower();
            int r = t.indexOf('&');
            if (r >= 0 && t[r+1] >= 'a' && t[r+1] <= 'z') {
                accelsTaken[t[r+1].unicode()-'a'] = true;
            }
        }
    }
    // then look to see which letters in tabText may be used
    i = 0;
    int accelPos = -1;
    while (i < tabText.length() && accelPos < 0) {
        if (tabText[i] >= 'A' && tabText[i] <= 'Z' &&
             !accelsTaken[tabText[i].unicode()-'A']) {
            accelPos = i;
        }
        if (tabText[i] >= 'a' && tabText[i] <= 'z' &&
             !accelsTaken[tabText[i].unicode()-'a']) {
            accelPos = i;
        }
        ++i;
    }
    if (accelPos >= 0) {
        tabText = tabText.mid(0, accelPos) + "&" + tabText.mid(accelPos);
    }
    // this could be improved on some european keyboards, such as
    // the German one (where alt-Sz-Ligature is available) and basically
    // doesn't work on Arabic/Indic keyboards (where Latin letters
    // aren't available), but I don't care to deal with those.

    int tabIndex = -1;
    QtTabWidget* tabWidget = dynamicGrid_->indexToTabWidget(index, tabIndex);
    tabWidget->setTabText(tabIndex, tabbable->getCount() > 0 ? QString("(%1) %2").arg(tabbable->getCount()).arg(tabText) : tabText);
    QColor tabTextColor;
    switch (tabbable->getWidgetAlertState()) {
    case QtTabbable::WaitingActivity : tabTextColor = QColor(217, 20, 43); break;
    case QtTabbable::ImpendingActivity : tabTextColor = QColor(27, 171, 32); break;
    case QtTabbable::NoActivity : tabTextColor = QColor(); break;
    }
    tabWidget->tabBar()->setTabTextColor(tabIndex, tabTextColor);

    std::vector<std::pair<std::string, int> > unreads;
    for (int i = 0; i < dynamicGrid_->count(); i++) {
        QtTabbable* tab = qobject_cast<QtTabbable*>(dynamicGrid_->widget(i));
        if (tab) {
            unreads.push_back(std::pair<std::string, int>(Q2PSTRING(tab->windowTitle()), tab->getCount()));
        }
    }

    std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(dynamicGrid_->currentWidget())->windowTitle()));
    ChatMessageSummarizer summary;
    QString title = summary.getSummary(current, unreads).c_str();
    setWindowTitle(title);
    emit onTitleChanged(title);
}

void QtChatTabs::flash() {
#ifndef SWIFTEN_PLATFORM_MACOSX
    QApplication::alert(this, 0);
#endif
}

void QtChatTabs::handleOpenLayoutChangeDialog() {
    disconnect(gridSelectionDialog_, SIGNAL(currentGridSizeChanged(QSize)), dynamicGrid_, SLOT(setDimensions(QSize)));
    gridSelectionDialog_->setCurrentGridSize(dynamicGrid_->getDimension());

    int screen = QApplication::desktop()->screenNumber(QCursor::pos());
    QPoint center = QApplication::desktop()->screenGeometry(screen).center();
    gridSelectionDialog_->move(center);

    connect(gridSelectionDialog_, SIGNAL(currentGridSizeChanged(QSize)), dynamicGrid_, SLOT(setDimensions(QSize)));
    gridSelectionDialog_->show();

    QPoint pos(gridSelectionDialog_->getFrameSize().width() / 2, gridSelectionDialog_->getFrameSize().height() / 2);
    QCursor::setPos(gridSelectionDialog_->windowHandle()->screen(), gridSelectionDialog_->mapToGlobal(QPoint(gridSelectionDialog_->width(), (gridSelectionDialog_->height() - gridSelectionDialog_->getDescriptionTextHeight())) - pos));
}

void QtChatTabs::storeTabPositions() {
    // save size
    QByteArray gridSizeData;
    QDataStream dataStreamGridSize(&gridSizeData, QIODevice::ReadWrite);
    dataStreamGridSize << dynamicGrid_->getDimension();
    settingsProvider_->storeSetting(QtUISettingConstants::TRELLIS_GRID_SIZE, Q2PSTRING(QString(gridSizeData.toBase64())));

    // save positions
    QByteArray tabPositionsData;
    QDataStream dataStreamTabPositions(&tabPositionsData, QIODevice::ReadWrite);
    dynamicGrid_->updateTabPositions();
    dataStreamTabPositions << dynamicGrid_->getTabPositions();
    settingsProvider_->storeSetting(QtUISettingConstants::TRELLIS_GRID_POSITIONS, Q2PSTRING(QString(tabPositionsData.toBase64())));
}

void QtChatTabs::resizeEvent(QResizeEvent*) {
    emit geometryChanged();
}

void QtChatTabs::moveEvent(QMoveEvent*) {
    emit geometryChanged();
}

void QtChatTabs::checkForFirstShow() {
    if (!isVisible()) {
#ifndef Q_OS_MAC
        showMinimized();
#else
        /* https://bugreports.qt-project.org/browse/QTBUG-19194
         * ^ When the above is fixed we can swap the below for just show();
         *  WA_ShowWithoutActivating seems to helpfully not work, so... */

        QWidget* currentWindow = QApplication::activeWindow(); /* Remember who had focus if we're the current application*/
        show();
        QCoreApplication::processEvents(); /* Run through the eventloop to clear the show() */
        if (currentWindow) {
            currentWindow->activateWindow(); /* Set focus back */
        }
#endif
    }
}

}