/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
 * A dashboard.
 *
 * @author Stephane MANKOWSKI
 */
#include "skgdashboardpluginwidget.h"

#include <kaboutdata.h>
#include <kservice.h>

#include <qdom.h>
#include <qdrag.h>
#include <qevent.h>
#include <qmenu.h>
#include <qmimedata.h>

#include "skgboardwidget.h"
#include "skgdocument.h"
#include "skgflowlayout.h"
#include "skginterfaceplugin.h"
#include "skgmainpanel.h"
#include "skgservices.h"
#include "skgtraces.h"
#include "skgzoomselector.h"


SKGDashboardPluginWidget::SKGDashboardPluginWidget(QWidget* iParent, SKGDocument* iDocument)
    : SKGTabPage(iParent, iDocument), m_flowLayout(nullptr), m_menu(nullptr), m_addMenu(nullptr)
{
    SKGTRACEINFUNC(1);
    if (iDocument == nullptr) {
        return;
    }

    ui.setupUi(this);

    // Create a context menu for adding widgets
    setContextMenuPolicy(Qt::CustomContextMenu);
    m_menu = new QMenu(this);
    connect(this, &SKGDashboardPluginWidget::customContextMenuRequested, this, &SKGDashboardPluginWidget::showHeaderMenu);
    m_addMenu = m_menu->addMenu(SKGServices::fromTheme(QStringLiteral("list-add")),  i18nc("Verb", "Add"));

    // Drag and drop
    m_clickedPoint = QPoint(-1, -1);

    ui.kTitle->setPixmap(SKGServices::fromTheme(KAboutData::applicationData().programIconName()).pixmap(22, 22), KTitleWidget::ImageLeft);
    ui.kTitle->setComment("<html><body><b>" % i18nc("Message", "Welcome to %1", KAboutData::applicationData().displayName()) % "</b></body></html>");

    // Build menu
    if (m_addMenu != nullptr) {
        m_addMenu->clear();

        int index = 0;
        while (index >= 0) {
            SKGInterfacePlugin* plugin = SKGMainPanel::getMainPanel()->getPluginByIndex(index);
            if (plugin != nullptr) {
                int nbdbw = plugin->getNbDashboardWidgets();
                for (int j = 0; j < nbdbw; ++j) {
                    // Create menu
                    QAction* act = m_addMenu->addAction(plugin->getDashboardWidgetTitle(j));
                    if (act != nullptr) {
                        act->setIcon(SKGServices::fromTheme(plugin->icon()));
                        act->setData(QString(plugin->objectName() % '-' % SKGServices::intToString(j)));

                        connect(act, &QAction::triggered, this, &SKGDashboardPluginWidget::onAddWidget);
                    }
                }
            } else {
                index = -2;
            }
            ++index;
        }
    }

    // Build layout
    m_flowLayout = new SKGFlowLayout(ui.kContent, 0, 0, 0);

    // Plug buttons with menus
    if ((m_addMenu != nullptr) && (ui.kAddWidget != nullptr)) {
        ui.kAddWidget->setIcon(m_addMenu->icon());
        ui.kAddWidget->setMenu(m_addMenu);
        ui.kAddWidget->setPopupMode(QToolButton::InstantPopup);
    }
}

SKGDashboardPluginWidget::~SKGDashboardPluginWidget()
{
    SKGTRACEINFUNC(1);
    m_menu = nullptr;
    m_addMenu = nullptr;
    m_flowLayout = nullptr;
}

QString SKGDashboardPluginWidget::getState()
{
    SKGTRACEINFUNC(10);
    QDomDocument doc(QStringLiteral("SKGML"));
    QDomElement root = doc.createElement(QStringLiteral("parameters"));
    doc.appendChild(root);

    root.setAttribute(QStringLiteral("zoomPosition"), SKGServices::intToString(zoomPosition()));

    int nb = m_items.count();
    for (int i = 0; i < nb; ++i) {
        QDomElement element = doc.createElement("ITEM-" % SKGServices::intToString(i + 1));
        root.appendChild(element);

        QStringList param = SKGServices::splitCSVLine(m_items.at(i), '-');
        SKGBoardWidget* item = m_itemsPointers.at(i);
        if (item != nullptr) {
            element.setAttribute(QStringLiteral("name"), param.at(0));
            element.setAttribute(QStringLiteral("index"), param.at(1));
            element.setAttribute(QStringLiteral("state"), item->getState());
            element.setAttribute(QStringLiteral("zoom"), SKGServices::intToString(item->getZoomRatio() * 5 - 15));
        }
    }
    return doc.toString();
}

void SKGDashboardPluginWidget::setState(const QString& iState)
{
    SKGTRACEINFUNC(10);

    QDomDocument doc(QStringLiteral("SKGML"));
    doc.setContent(iState);
    QDomElement root = doc.documentElement();

    // Initialisation
    int nb = (m_flowLayout != nullptr ? m_flowLayout->count() : 0);
    for (int i = 0; i < nb; ++i) {
        SKGBoardWidget* item = m_itemsPointers.at(0);
        if (item != nullptr) {
            m_flowLayout->removeWidget(item);
            item->hide();

            m_items.removeAt(0);
            m_itemsPointers.removeAt(0);

            item->deleteLater();
        }
    }

    QString zoomPositionS = root.attribute(QStringLiteral("zoomPosition"));
    if (zoomPositionS.isEmpty()) {
        zoomPositionS = '0';
    }
    setZoomPosition(SKGServices::stringToInt(zoomPositionS));

    int index = 1;
    while (index > 0) {
        QDomElement element = root.firstChildElement("ITEM-" % SKGServices::intToString(index));
        if (!element.isNull()) {
            SKGInterfacePlugin* plugin = SKGMainPanel::getMainPanel()->getPluginByName(element.attribute(QStringLiteral("name")));
            QString indexString = element.attribute(QStringLiteral("index"));
            if (indexString.isEmpty()) {
                indexString = '0';
            }
            QString zoom = element.attribute(QStringLiteral("zoom"));
            if (zoom.isEmpty()) {
                zoom = '0';
            }
            if (plugin != nullptr) {
                addItem(plugin, SKGServices::stringToInt(indexString), SKGServices::stringToInt(zoom), element.attribute(QStringLiteral("state")));
            }
        } else {
            index = -1;
        }
        ++index;
    }

    // In case of reset
    if (m_items.isEmpty() && root.attribute(QStringLiteral("zoomPosition")).isEmpty()) {
        int index2 = 0;
        while (index2 >= 0) {
            SKGInterfacePlugin* plugin = SKGMainPanel::getMainPanel()->getPluginByIndex(index2);
            if (plugin != nullptr) {
                int nb2 = plugin->getNbDashboardWidgets();
                for (int j = 0; j < nb2; ++j) {
                    addItem(plugin, j);
                }
            } else {
                index2 = -2;
            }
            ++index2;
        }
    }
}

QString SKGDashboardPluginWidget::getDefaultStateAttribute()
{
    return QStringLiteral("SKGDASHBOARD_DEFAULT_PARAMETERS");
}

void SKGDashboardPluginWidget::refresh()
{
    SKGTRACEINFUNC(1);
}

QWidget* SKGDashboardPluginWidget::zoomableWidget()
{
    return SKGTabPage::zoomableWidget();
}

QList< QWidget* > SKGDashboardPluginWidget::printableWidgets()
{
    QList< QWidget* > output;
    output.reserve(m_itemsPointers.count());
    for (auto w : qAsConst(m_itemsPointers)) {
        output.push_back(w);
    }

    return output;
}

bool SKGDashboardPluginWidget::eventFilter(QObject* iObject, QEvent* iEvent)
{
    if ((iEvent != nullptr) && iEvent->type() == QEvent::HoverLeave) {
        // Leave widget
        m_timer.stop();
        return true;
    }

    if ((iEvent != nullptr) && (iObject != nullptr) &&
        (iEvent->type() == QEvent::MouseButtonPress ||
         iEvent->type() == QEvent::MouseButtonRelease ||
         iEvent->type() == QEvent::MouseMove ||
         iEvent->type() == QEvent::DragEnter ||
         iEvent->type() == QEvent::DragMove ||
         iEvent->type() == QEvent::Drop ||
         iEvent->type() == QEvent::HoverMove)) {
        // Search SKGBoardWidget corresponding to this widget
        SKGBoardWidget* toMove = nullptr;
        int toMoveIndex = -1;
        int nb = m_itemsPointers.count();
        for (int i = 0; toMove == nullptr && i < nb; ++i) {
            SKGBoardWidget* w = m_itemsPointers.at(i);
            if ((w != nullptr) && w->getDragWidget() == iObject) {
                toMove = w;
                toMoveIndex = i;
            }
        }

        if (iEvent->type() == QEvent::MouseButtonPress) {
            // Drag
            auto* mevent = dynamic_cast<QMouseEvent*>(iEvent);
            if (mevent->button() == Qt::LeftButton) {
                m_clickedPoint = mevent->pos();
                m_timer.stop();
            }
        } else if (iEvent->type() == QEvent::MouseButtonRelease) {
            // Drag
            auto* mevent = dynamic_cast<QMouseEvent*>(iEvent);
            if (mevent->button() == Qt::LeftButton) {
                m_clickedPoint = QPoint(-1, -1);
            }
        } else if (iEvent->type() == QEvent::MouseMove) {
            // Drag
            if (m_clickedPoint != QPoint(-1, -1) && toMoveIndex != -1) {
                auto* mevent = dynamic_cast<QMouseEvent*>(iEvent);
                int distance = (mevent->pos() - m_clickedPoint).manhattanLength();
                if (distance >= QApplication::startDragDistance()) {
                    auto mimeData = new QMimeData;
                    mimeData->setData(QStringLiteral("application/x-skgdashboardpluginwidget"), SKGServices::intToString(toMoveIndex).toLatin1());

                    auto drag = new QDrag(this);
                    drag->setMimeData(mimeData);
                    drag->exec();  // krazy:exclude=crashy

                    return true;
                }
            }
        } else if (iEvent->type() == QEvent::DragEnter) {
            // Drop move
            auto* devent = dynamic_cast<QDragEnterEvent*>(iEvent);
            if (devent->mimeData()->hasFormat(QStringLiteral("application/x-skgdashboardpluginwidget"))) {
                devent->accept();

                return true;
            }
        } else if (iEvent->type() == QEvent::DragMove) {
            // Drop move
            auto* devent = dynamic_cast<QDragMoveEvent*>(iEvent);
            if (devent->mimeData()->hasFormat(QStringLiteral("application/x-skgdashboardpluginwidget"))) {
                int oldPos = SKGServices::stringToInt(devent->mimeData()->data(QStringLiteral("application/x-skgdashboardpluginwidget")));
                if (oldPos != toMoveIndex) {
                    devent->accept();
                } else {
                    devent->ignore();
                }

                return true;
            }
        } else if (iEvent->type() == QEvent::Drop) {
            // Drop
            auto* devent = dynamic_cast<QDropEvent*>(iEvent);
            if (devent->mimeData()->hasFormat(QStringLiteral("application/x-skgdashboardpluginwidget"))) {
                int oldPos = SKGServices::stringToInt(devent->mimeData()->data(QStringLiteral("application/x-skgdashboardpluginwidget")));

                if (oldPos + 1 == toMoveIndex) {
                    ++toMoveIndex;
                }

                // Move item
                if (toMoveIndex > oldPos) {
                    --toMoveIndex;
                }
                moveItem(oldPos, toMoveIndex);

                return true;
            }
        }
    }
    return SKGTabPage::eventFilter(iObject, iEvent);
}

void SKGDashboardPluginWidget::showHeaderMenu(const QPoint iPos)
{
    // Display menu
    if (m_menu != nullptr) {
        m_menu->popup(mapToGlobal(iPos));
    }
}

void SKGDashboardPluginWidget::onAddWidget()
{
    auto* send = qobject_cast<QAction*>(this->sender());
    if (send != nullptr) {
        QString id = send->data().toString();
        QStringList param = SKGServices::splitCSVLine(id, '-');

        SKGInterfacePlugin* db = SKGMainPanel::getMainPanel()->getPluginByName(param.at(0));
        if (db != nullptr) {
            addItem(db, SKGServices::stringToInt(param.at(1)));
        }
    }
}

void SKGDashboardPluginWidget::onMoveWidget(int iMove)
{
    // Get current position
    QWidget* send = qobject_cast<QWidget*>(this->sender());
    if (send != nullptr) {
        int currentPos = m_itemsPointers.indexOf(parentBoardWidget(send));
        int newPos = currentPos + iMove;
        if (newPos < 0) {
            newPos = 0;
        } else if (newPos > m_items.count() - 1) {
            newPos = m_items.count() - 1;
        }

        moveItem(currentPos, newPos);
    }
}

void SKGDashboardPluginWidget::moveItem(int iFrom, int iTo)
{
    // Compute new position
    if (iTo != iFrom) {
        // Move item
        m_items.move(iFrom, iTo);
        m_itemsPointers.move(iFrom, iTo);

        // Build list of items in the right order
        QList<SKGBoardWidget*> listWidgets;
        int nb = m_itemsPointers.count();
        listWidgets.reserve(nb);
        for (int i = 0; i < nb; ++i) {
            SKGBoardWidget* wgt2 = m_itemsPointers.at(i);
            m_flowLayout->removeWidget(wgt2);
            listWidgets.push_back(wgt2);
        }

        // Add items
        nb = listWidgets.count();
        for (int i = 0; i < nb; ++i) {
            SKGBoardWidget* dbw = listWidgets.at(i);
            dbw->setParent(ui.kContent);
            m_flowLayout->addWidget(dbw);
        }
    }
}

SKGBoardWidget* SKGDashboardPluginWidget::parentBoardWidget(QWidget* iWidget)
{
    auto* output = qobject_cast< SKGBoardWidget* >(iWidget);
    if ((output == nullptr) && (iWidget != nullptr)) {
        QWidget* iParent = iWidget->parentWidget();
        if (iParent != nullptr) {
            output = SKGDashboardPluginWidget::parentBoardWidget(iParent);
        }
    }

    return output;
}

void SKGDashboardPluginWidget::onRemoveWidget()
{
    int p = -1;
    QWidget* send = qobject_cast<QWidget*>(this->sender());
    if (send != nullptr) {
        p = m_itemsPointers.indexOf(parentBoardWidget(send));
    }
    if (p >= 0) {
        // Get item
        SKGBoardWidget* wgt = m_itemsPointers.at(p);

        // Delete widget
        m_flowLayout->removeWidget(wgt);
        wgt->hide();
        wgt->deleteLater();

        // Remove item
        m_items.removeAt(p);
        m_itemsPointers.removeAt(p);
    }
}

void SKGDashboardPluginWidget::addItem(SKGInterfacePlugin* iDashboard, int iIndex, int iZoom, const QString& iState)
{
    if ((iDashboard != nullptr) && (m_flowLayout != nullptr)) {
        SKGBoardWidget* dbw = iDashboard->getDashboardWidget(iIndex);
        if (dbw != nullptr) {
            // Add widget
            dbw->setParent(ui.kContent);
            dbw->setState(iState);
            m_flowLayout->addWidget(dbw);

            // Install filter
            QWidget* drag = dbw->getDragWidget();
            if (drag != nullptr) {
                drag->installEventFilter(this);
                drag->setAcceptDrops(true);
                drag->setAttribute(Qt::WA_Hover);
            }

            // Connect widget
            connect(dbw, &SKGBoardWidget::requestRemove, this, &SKGDashboardPluginWidget::onRemoveWidget, Qt::QueuedConnection);
            connect(dbw, &SKGBoardWidget::requestMove, this, &SKGDashboardPluginWidget::onMoveWidget, Qt::QueuedConnection);

            // Set size
            dbw->setZoomRatio((iZoom + 15.0) / 5.0);

            QString id = iDashboard->objectName() % '-' % SKGServices::intToString(iIndex);
            m_items.push_back(id);
            m_itemsPointers.push_back(dbw);
        }
    }
}
