/* decode_as_delegate.cpp
 * Delegates for editing various field types in a Decode As record.
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "decode_as_delegate.h"

#include "epan/decode_as.h"
#include "epan/epan_dissect.h"

#include <ui/qt/utils/variant_pointer.h>

#include <QComboBox>
#include <QEvent>
#include <QLineEdit>
#include <QTreeView>

typedef struct _dissector_info_t {
    QString             proto_name;
    dissector_handle_t  dissector_handle;
} dissector_info_t;

Q_DECLARE_METATYPE(dissector_info_t *)

DecodeAsDelegate::DecodeAsDelegate(QObject *parent, capture_file *cf)
 : QStyledItemDelegate(parent),
    cap_file_(cf)
{
    cachePacketProtocols();
}

DecodeAsItem* DecodeAsDelegate::indexToField(const QModelIndex &index) const
{
    const QVariant v = index.model()->data(index, Qt::UserRole);
    return static_cast<DecodeAsItem*>(v.value<void *>());
}

void DecodeAsDelegate::cachePacketProtocols()
{
    //cache the list of potential decode as protocols in the current packet
    if (cap_file_ && cap_file_->edt) {

        wmem_list_frame_t * protos = wmem_list_head(cap_file_->edt->pi.layers);
        guint8 curr_layer_num = 1;

        while (protos != NULL) {
            int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos));
            const gchar * proto_name = proto_get_protocol_filter_name(proto_id);
            for (GList *cur = decode_as_list; cur; cur = cur->next) {
                decode_as_t *entry = (decode_as_t *) cur->data;
                if (g_strcmp0(proto_name, entry->name) == 0) {
                    packet_proto_data_t proto_data;

                    proto_data.table_ui_name = get_dissector_table_ui_name(entry->table_name);
                    proto_data.proto_name = proto_name;
                    proto_data.curr_layer_num = curr_layer_num;

                    packet_proto_list_.append(proto_data);
                }
            }
            protos = wmem_list_frame_next(protos);
            curr_layer_num++;
        }
    }
}

void DecodeAsDelegate::collectDAProtocols(QSet<QString>& all_protocols, QList<QString>& current_list) const
{
    // If a packet is selected group its tables at the top in order
    // from last-dissected to first.

    //gather the initial list
    for (GList *cur = decode_as_list; cur; cur = cur->next) {
        decode_as_t *entry = (decode_as_t *) cur->data;
        const char *table_name = get_dissector_table_ui_name(entry->table_name);
        if (table_name) {
            all_protocols.insert(get_dissector_table_ui_name(entry->table_name));
        }
    }

    //filter out those in selected packet
    foreach(packet_proto_data_t proto, packet_proto_list_)
    {
        current_list.append(proto.table_ui_name);
        all_protocols.remove(proto.table_ui_name);
    }
}

//Determine if there are multiple values in the selector field that would
//correspond to using a combo box
bool DecodeAsDelegate::isSelectorCombo(DecodeAsItem* item) const
{
    const gchar *proto_name = NULL;

    foreach(packet_proto_data_t proto, packet_proto_list_)
    {
        if (g_strcmp0(proto.table_ui_name, item->tableUIName_) == 0) {
            proto_name = proto.proto_name;
            break;
        }
    }

    for (GList *cur = decode_as_list; cur; cur = cur->next) {
        decode_as_t *entry = (decode_as_t *) cur->data;
        if ((g_strcmp0(proto_name, entry->name) == 0) &&
            (g_strcmp0(item->tableName_, entry->table_name) == 0) &&
            (cap_file_ && cap_file_->edt) &&
            (entry->num_items > 1)) {
                return true;
        }
    }

    return false;
}

void DecodeAsDelegate::decodeAddProtocol(const gchar *, const gchar *proto_name, gpointer value, gpointer user_data)
{
    QMap<QString, dissector_info_t*>* proto_list = (QMap<QString, dissector_info_t*>*)user_data;

    if (!proto_list)
        return;

    dissector_info_t  *dissector_info = new dissector_info_t();
    dissector_info->proto_name = proto_name;
    dissector_info->dissector_handle = (dissector_handle_t) value;

    proto_list->insert(proto_name, dissector_info);
}

QWidget* DecodeAsDelegate::createEditor(QWidget *parentWidget, const QStyleOptionViewItem &option,
                                  const QModelIndex &index) const
{
    DecodeAsItem* item = indexToField(index);

    switch(index.column())
    {
    case DecodeAsModel::colTable:
        {
        QComboBox *editor = new QComboBox(parentWidget);
        QSet<QString> da_set;
        QList<QString> packet_list;
        QString table_ui_name;

        collectDAProtocols(da_set, packet_list);

        editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);

        //put the protocols from the packet first in the combo box
        foreach (table_ui_name, packet_list) {
            editor->addItem(table_ui_name, table_ui_name);
        }
        if (packet_list.count() > 0) {
            editor->insertSeparator(packet_list.count());
        }

        //put the rest of the protocols in the combo box
        QList<QString> da_list = da_set.toList();
        qSort(da_list.begin(), da_list.end());

        foreach (table_ui_name, da_list) {
            editor->addItem(table_ui_name, table_ui_name);
        }

        //Make sure the combo box is at least as wide as the column
        QTreeView* parentTree = (QTreeView*)parent();
        int protoColWidth = parentTree->columnWidth(index.column());
        if (protoColWidth > editor->size().width())
            editor->setFixedWidth(protoColWidth);

        return editor;
        }
    case DecodeAsModel::colSelector:
        {
        QComboBox *editor = NULL;
        const gchar *proto_name = NULL;
        bool edt_present = cap_file_ && cap_file_->edt;
        gint8 curr_layer_num_saved = edt_present ? cap_file_->edt->pi.curr_layer_num : 0;

        foreach(packet_proto_data_t proto, packet_proto_list_)
        {
            if (g_strcmp0(proto.table_ui_name, item->tableUIName_) == 0) {
                if (edt_present) {
                    cap_file_->edt->pi.curr_layer_num = proto.curr_layer_num;
                }
                proto_name = proto.proto_name;
                //XXX - break?  Or do we always want the last layer of tunnelled protocols?
            }
        }

        for (GList *cur = decode_as_list; cur; cur = cur->next) {
            decode_as_t *entry = (decode_as_t *) cur->data;
            if ((g_strcmp0(proto_name, entry->name) == 0) &&
                (g_strcmp0(item->tableName_, entry->table_name) == 0)) {
                if (edt_present) {
                    if (entry->num_items > 1)
                    {
                        //only create a combobox if there is a choice of values, otherwise it looks funny
                        editor = new QComboBox(parentWidget);

                        //Don't limit user to just what's in combo box
                        editor->setEditable(true);

                        editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);

                        //add the current value of the column
                        const QString& current_value = index.model()->data(index, Qt::EditRole).toString();
                        if (!current_value.isEmpty())
                            editor->addItem(current_value);

                        //get the value(s) from the packet
                        for (uint ni = 0; ni < entry->num_items; ni++) {
                            if (entry->values[ni].num_values == 1) { // Skip over multi-value ("both") entries
                                QString entryStr = DecodeAsModel::entryString(entry->table_name,
                                                                        entry->values[ni].build_values[0](&cap_file_->edt->pi));
                                //don't duplicate entries
                                if (editor->findText(entryStr) < 0)
                                    editor->addItem(entryStr);
                            }
                        }
                        editor->setCurrentIndex(entry->default_index_value);

                        //Make sure the combo box is at least as wide as the column
                        QTreeView* parentTree = (QTreeView*)parent();
                        int protoColWidth = parentTree->columnWidth(index.column());
                        if (protoColWidth > editor->size().width())
                            editor->setFixedWidth(protoColWidth);

                    }
                }
                break;
            }
        }

        if (edt_present) {
            cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved;
        }

        //if there isn't a need for a combobox, just let user have a text box for direct edit
        if (editor == NULL)
            return QStyledItemDelegate::createEditor(parentWidget, option, index);

        return editor;
        }

    case DecodeAsModel::colProtocol:
        {
        QComboBox *editor = new QComboBox(parentWidget);
        QMap<QString, dissector_info_t*> protocols;

        editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);

        for (GList *cur = decode_as_list; cur; cur = cur->next) {
            decode_as_t *entry = (decode_as_t *) cur->data;
            if (g_strcmp0(item->tableName_, entry->table_name) == 0) {
                entry->populate_list(entry->table_name, decodeAddProtocol, &protocols);
                break;
            }
        }

        editor->addItem(DECODE_AS_NONE);
        editor->insertSeparator(editor->count());

        //QMap already sorts the keys (protocols) alphabetically
        QMap<QString, dissector_info_t*>::iterator protocol;
        for(protocol = protocols.begin(); protocol != protocols.end(); ++protocol)
        {
            editor->addItem(protocol.key(), VariantPointer<dissector_info_t>::asQVariant(protocol.value()));
        }

        //Make sure the combo box is at least as wide as the column
        QTreeView* parentTree = (QTreeView*)parent();
        int protoColWidth = parentTree->columnWidth(index.column());
        if (protoColWidth > editor->size().width())
            editor->setFixedWidth(protoColWidth);

        return editor;
        }
    }

    return NULL;
}

void DecodeAsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    DecodeAsItem* item = indexToField(index);

    switch(index.column())
    {
    case DecodeAsModel::colTable:
    case DecodeAsModel::colProtocol:
        {
        QComboBox *combobox = static_cast<QComboBox *>(editor);
        const QString &data = index.model()->data(index, Qt::EditRole).toString();
        combobox->setCurrentText(data);
        }
        break;
    case DecodeAsModel::colSelector:
        if (isSelectorCombo(item)) {
            QComboBox *combobox = static_cast<QComboBox *>(editor);
            const QString &data = index.model()->data(index, Qt::EditRole).toString();
            combobox->setCurrentText(data);
        }
        else {
            QStyledItemDelegate::setEditorData(editor, index);
        }
        break;
    default:
        QStyledItemDelegate::setEditorData(editor, index);
        break;
    }
}

void DecodeAsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                              const QModelIndex &index) const
{
    DecodeAsItem* item = indexToField(index);

    switch(index.column())
    {
    case DecodeAsModel::colTable:
        {
        QComboBox *combobox = static_cast<QComboBox *>(editor);
        const QString &data = combobox->currentText();
        model->setData(index, data, Qt::EditRole);
        break;
        }
    case DecodeAsModel::colSelector:
        if (isSelectorCombo(item)) {
            QComboBox *combobox = static_cast<QComboBox *>(editor);
            const QString &data = combobox->currentText();
            model->setData(index, data, Qt::EditRole);
        } else {
            QStyledItemDelegate::setModelData(editor, model, index);
        }
        break;
    case DecodeAsModel::colProtocol:
        {
        QComboBox *combobox = static_cast<QComboBox *>(editor);
        const QString &data = combobox->currentText();
        model->setData(index, data, Qt::EditRole);

        //set the dissector handle
        QVariant var = combobox->itemData(combobox->currentIndex());
        dissector_info_t* dissector_info = VariantPointer<dissector_info_t>::asPtr(var);
        if (dissector_info != NULL) {
            ((DecodeAsModel*)model)->setDissectorHandle(index, dissector_info->dissector_handle);
        } else {
            ((DecodeAsModel*)model)->setDissectorHandle(index, NULL);
        }
        break;
        }
    default:
        QStyledItemDelegate::setModelData(editor, model, index);
        break;
    }
}

#if 0
// Qt docs suggest overriding updateEditorGeometry, but the defaults seem sane.
void UatDelegate::updateEditorGeometry(QWidget *editor,
        const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyledItemDelegate::updateEditorGeometry(editor, option, index);
}
#endif

/* * 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:
 */
