/* packet_list_header.cpp
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <QDropEvent>
#include <QMimeData>
#include <QToolTip>
#include <QAction>
#include <QInputDialog>
#include <QJsonDocument>
#include <QJsonObject>

#include <packet_list.h>

#include <wireshark_application.h>
#include <epan/column.h>
#include <ui/recent.h>
#include <ui/preference_utils.h>
#include <ui/packet_list_utils.h>
#include <ui/qt/main_window.h>

#include <models/packet_list_model.h>
#include <ui/qt/utils/wireshark_mime_data.h>
#include <ui/qt/widgets/packet_list_header.h>

PacketListHeader::PacketListHeader(Qt::Orientation orientation, capture_file * cap_file, QWidget *parent) :
    QHeaderView(orientation, parent),
    cap_file_(cap_file),
    sectionIdx(-1),
    lastSize(-1)
{
    setAcceptDrops(true);
    setSectionsMovable(true);
    setStretchLastSection(true);
    setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter);
}

void PacketListHeader::dragEnterEvent(QDragEnterEvent *event)
{
    if (! event || ! event->mimeData())
        return;

    if (event->mimeData()->hasFormat(WiresharkMimeData::DisplayFilterMimeType) && event->source() != this->parent())
    {
        if (event->source() != this)
        {
            event->setDropAction(Qt::CopyAction);
            event->accept();
        } else {
            event->acceptProposedAction();
        }
    }
    else
        QHeaderView::dragEnterEvent(event);
}

void PacketListHeader::dragMoveEvent(QDragMoveEvent *event)
{
    if (! event || ! event->mimeData())
        return;

    if (event->mimeData()->hasFormat(WiresharkMimeData::DisplayFilterMimeType))
    {
        if (event->source() != this)
        {
            event->setDropAction(Qt::CopyAction);
            event->accept();
        } else {
            event->acceptProposedAction();
        }
    }
    else
        QHeaderView::dragMoveEvent(event);
}

void PacketListHeader::dropEvent(QDropEvent *event)
{
    if (! event || ! event->mimeData())
        return;

    /* Moving items around */
    if (event->mimeData()->hasFormat(WiresharkMimeData::DisplayFilterMimeType))
    {
        QByteArray jsonData = event->mimeData()->data(WiresharkMimeData::DisplayFilterMimeType);
        QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
        if (! jsonDoc.isObject())
            return;

        QJsonObject data = jsonDoc.object();

        if ( event->source() != this && data.contains("description") && data.contains("name") )
        {
            event->setDropAction(Qt::CopyAction);
            event->accept();

            MainWindow * mw = qobject_cast<MainWindow *>(wsApp->mainWindow());
            if (mw)
            {
                int idx = logicalIndexAt(event->pos());
                mw->insertColumn(data["description"].toString(), data["name"].toString(), idx);
            }

        } else {
            event->acceptProposedAction();
        }
    }
    else
        QHeaderView::dropEvent(event);
}

void PacketListHeader::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton && sectionIdx < 0)
    {
        /* No move happening yet */
        int sectIdx = logicalIndexAt(e->localPos().x() - 4, e->localPos().y());

        QString headerName = model()->headerData(sectIdx, orientation()).toString();
        lastSize = sectionSize(sectIdx);
        QToolTip::showText(e->globalPos(), QString("Width: %1").arg(sectionSize(sectIdx)));
    }
    QHeaderView::mousePressEvent(e);
}

void PacketListHeader::mouseMoveEvent(QMouseEvent *e)
{
    if (e->button() == Qt::NoButton || ! (e->buttons() & Qt::LeftButton))
    {
        /* no move is happening */
        sectionIdx = -1;
        lastSize = -1;
    }
    else if (e->buttons() & Qt::LeftButton)
    {
        /* section being moved */
        int triggeredSection = logicalIndexAt(e->localPos().x() - 4, e->localPos().y());

        if (sectionIdx < 0)
            sectionIdx = triggeredSection;
        else if (sectionIdx == triggeredSection)
        {
            /* Only run for the current moving section after a change */
            QString headerName = model()->headerData(sectionIdx, orientation()).toString();
            lastSize = sectionSize(sectionIdx);
            QToolTip::showText(e->globalPos(), QString("Width: %1").arg(lastSize));
        }
    }
    QHeaderView::mouseMoveEvent(e);
}

void PacketListHeader::setCaptureFile(capture_file *cap_file)
{
    this->cap_file_ = cap_file;
}

void PacketListHeader::contextMenuEvent(QContextMenuEvent *event)
{
    int sectionIdx = logicalIndexAt(event->pos());
    char xalign = recent_get_column_xalign(sectionIdx);
    QAction * action = Q_NULLPTR;
    QMenu * contextMenu = new QMenu(this);
    contextMenu->setProperty("column", QVariant::fromValue(sectionIdx));

    QActionGroup * alignmentActions = new QActionGroup(contextMenu);
    alignmentActions->setExclusive(false);
    alignmentActions->setProperty("column", QVariant::fromValue(sectionIdx));
    action = alignmentActions->addAction(tr("Align Left"));
    action->setCheckable(true);
    action->setChecked(xalign == COLUMN_XALIGN_LEFT ? true : false);
    action->setData(QVariant::fromValue(COLUMN_XALIGN_LEFT));
    action = alignmentActions->addAction(tr("Align Center"));
    action->setCheckable(true);
    action->setChecked(xalign == COLUMN_XALIGN_CENTER ? true : false);
    action->setData(QVariant::fromValue(COLUMN_XALIGN_CENTER));
    action = alignmentActions->addAction(tr("Align Right"));
    action->setCheckable(true);
    action->setChecked(xalign == COLUMN_XALIGN_RIGHT ? true : false);
    action->setData(QVariant::fromValue(COLUMN_XALIGN_RIGHT));
    connect(alignmentActions, &QActionGroup::triggered, this, &PacketListHeader::setAlignment);

    contextMenu->addActions(alignmentActions->actions());
    contextMenu->addSeparator();

    action = contextMenu->addAction(tr("Column Preferences…"));
    connect(action, &QAction::triggered, this, &PacketListHeader::showColumnPrefs);
    action = contextMenu->addAction(tr("Edit Column"));
    connect(action, &QAction::triggered, this, &PacketListHeader::doEditColumn);
    action = contextMenu->addAction(tr("Resize to Contents"));
    connect(action, &QAction::triggered, this, &PacketListHeader::resizeToContent);
    action = contextMenu->addAction(tr("Resize Column to Width…"));
    connect(action, &QAction::triggered, this, &PacketListHeader::resizeToWidth);

    action = contextMenu->addAction(tr("Resolve Names"));
    bool canResolve = resolve_column(sectionIdx, cap_file_);
    action->setEnabled(canResolve);
    action->setChecked(canResolve && get_column_resolved(sectionIdx));
    connect(action, &QAction::triggered, this, &PacketListHeader::doResolveNames);

    contextMenu->addSeparator();

    for (int cnt = 0; cnt < prefs.num_cols; cnt++) {
        QString title(get_column_title(cnt));
        QString detail;
        if (get_column_format(cnt) == COL_CUSTOM) {
            detail = get_column_custom_fields(cnt);
        } else {
            detail = col_format_desc(get_column_format(cnt));
        }

        if (prefs.gui_qt_packet_header_column_definition)
            title.append(QString("\t%1").arg(detail));

        QAction *action = new QAction(title, this);
        action->setToolTip(detail);
        action->setCheckable(true);
        action->setChecked(get_column_visible(cnt));
        action->setData(QVariant::fromValue(cnt));
        connect(action, &QAction::triggered, this, &PacketListHeader::columnVisibilityTriggered);
        contextMenu->addAction(action);
    }
    contextMenu->setToolTipsVisible(true);

    contextMenu->addSeparator();

    action = contextMenu->addAction(tr("Remove this Column"));
    action->setEnabled(sectionIdx >= 0 && count() > 2);
    connect(action, &QAction::triggered, this, &PacketListHeader::removeColumn);

    contextMenu->popup(viewport()->mapToGlobal(event->pos()));
}

void PacketListHeader::setSectionVisibility()
{
    for (int cnt = 0; cnt < prefs.num_cols; cnt++)
        setSectionHidden(cnt, get_column_visible(cnt) ? false : true);
}

void PacketListHeader::columnVisibilityTriggered()
{
    QAction *ha = qobject_cast<QAction*>(sender());
    if (!ha) return;

    int col = ha->data().toInt();
    set_column_visible(col, ha->isChecked());
    setSectionVisibility();
    if (ha->isChecked())
        emit resetColumnWidth(col);

    prefs_main_write();
}

void PacketListHeader::setAlignment(QAction *action)
{
    if (!action)
        return;

    QActionGroup * group = action->actionGroup();
    if (! group)
        return;

    int section = group->property("column").toInt();
    if (section >= 0)
    {
        QChar data = action->data().toChar();
        recent_set_column_xalign(section, action->isChecked() ? data.toLatin1() : COLUMN_XALIGN_DEFAULT);
        emit updatePackets(false);
    }
}

void PacketListHeader::showColumnPrefs()
{
    emit showColumnPreferences(PrefsModel::typeToString(PrefsModel::Columns));
}

void PacketListHeader::doEditColumn()
{
    QAction * action = qobject_cast<QAction *>(sender());
    if (!action)
        return;

    QMenu * menu = qobject_cast<QMenu *>(action->parent());
    if (! menu)
        return;

    int section = menu->property("column").toInt();
    emit editColumn(section);
}

void PacketListHeader::doResolveNames()
{
    QAction * action = qobject_cast<QAction *>(sender());
    if (!action)
        return;

    QMenu * menu = qobject_cast<QMenu *>(action->parent());
    if (!menu)
        return;

    PacketListModel * plmModel = qobject_cast<PacketListModel *>(model());
    if (!plmModel)
        return;

    int section = menu->property("column").toInt();

    set_column_resolved(section, action->isChecked());
    plmModel->resetColumns();
    prefs_main_write();
    emit updatePackets(true);
}

void PacketListHeader::resizeToContent()
{
    QAction * action = qobject_cast<QAction *>(sender());
    if (!action)
        return;

    QMenu * menu = qobject_cast<QMenu *>(action->parent());
    if (!menu)
        return;

    int section = menu->property("column").toInt();
    PacketList * packetList = qobject_cast<PacketList *>(parent());
    if (packetList)
        packetList->resizeColumnToContents(section);
}

void PacketListHeader::removeColumn()
{
    QAction * action = qobject_cast<QAction *>(sender());
    if (!action)
        return;

    QMenu * menu = qobject_cast<QMenu *>(action->parent());
    if (!menu)
        return;

    int section = menu->property("column").toInt();

    if (count() > 2) {
        column_prefs_remove_nth(section);
        emit columnsChanged();
        prefs_main_write();
    }
}

void PacketListHeader::resizeToWidth()
{
    QAction * action = qobject_cast<QAction *>(sender());
    if (!action)
        return;

    QMenu * menu = qobject_cast<QMenu *>(action->parent());
    if (!menu)
        return;

    bool ok = false;
    int width = -1;
    int section = menu->property("column").toInt();
    QString headerName = model()->headerData(section, orientation()).toString();
    width = QInputDialog::getInt(this, tr("Column %1").arg(headerName), tr("Width:"),
                                 sectionSize(section), 0, 1000, 1, &ok);
    if (ok)
        resizeSection(section, width);
}

/*
 * Editor modelines
 *
 * Local Variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * ex: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */
