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

#include <ui/qt/models/pref_models.h>
#include <ui/qt/utils/qt_ui_utils.h>
#include <epan/prefs-int.h>

#ifdef HAVE_LIBPCAP
#ifdef _WIN32
#include "caputils/capture-wpcap.h"
#endif /* _WIN32 */
#endif /* HAVE_LIBPCAP */

#include <QFont>
#include <QColor>

// XXX Should we move this to ui/preference_utils?
static GHashTable * pref_ptr_to_pref_ = NULL;
pref_t *prefFromPrefPtr(void *pref_ptr)
{
    return (pref_t *)g_hash_table_lookup(pref_ptr_to_pref_, (gpointer) pref_ptr);
}

static void prefInsertPrefPtr(void * pref_ptr, pref_t * pref)
{
    if ( ! pref_ptr_to_pref_ )
        pref_ptr_to_pref_ = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);

    gpointer key = (gpointer) pref_ptr;
    gpointer val = (gpointer) pref;

    /* Already existing entries will be ignored */
    if ( (pref = (pref_t *)g_hash_table_lookup(pref_ptr_to_pref_, key) ) == NULL )
        g_hash_table_insert(pref_ptr_to_pref_, key, val);
}

PrefsItem::PrefsItem(module_t *module, pref_t *pref, PrefsItem* parent)
    : ModelHelperTreeItem<PrefsItem>(parent),
    pref_(pref),
    module_(module),
    changed_(false)
{
    name_ = QString(module->name ? module->name : module->parent->name);
    if (pref_ != NULL) {
        name_ += QString(".%1").arg(prefs_get_name(pref_));
    }
}

PrefsItem::PrefsItem(const QString name, PrefsItem* parent)
    : ModelHelperTreeItem<PrefsItem>(parent),
    pref_(NULL),
    module_(NULL),
    name_(name),
    changed_(false)
{

}

PrefsItem::~PrefsItem()
{
}

int PrefsItem::getPrefType() const
{
    if (pref_ == NULL)
        return 0;

    return prefs_get_type(pref_);
}

int PrefsItem::getPrefGUIType() const
{
    if (pref_ == NULL)
        return GUI_ALL;

    return prefs_get_gui_type(pref_);
}

bool PrefsItem::isPrefDefault() const
{
    if (pref_ == NULL)
        return true;

    if (changed_ == false)
        return prefs_pref_is_default(pref_) ? true : false;

    return false;
}

QString PrefsItem::getPrefTypeName() const
{
    if (pref_ == NULL)
        return "";

    return QString(prefs_pref_type_name(pref_));
}

QString PrefsItem::getModuleName() const
{
    if (module_ == NULL)
        return name_;

    return QString(module_->name);
}

QString PrefsItem::getModuleTitle() const
{
    if ((module_ == NULL) && (pref_ == NULL))
        return name_;

    Q_ASSERT(module_);

    return QString(module_->title);
}

void PrefsItem::setChanged(bool changed)
{
    changed_ = changed;
}

//: Names of special preferences handled by the GUI
const char* PrefsModel::ADVANCED_PREFERENCE_TREE_NAME = QT_TR_NOOP("Advanced");
const char* PrefsModel::APPEARANCE_PREFERENCE_TREE_NAME = QT_TR_NOOP("Appearance");
const char* PrefsModel::LAYOUT_PREFERENCE_TREE_NAME = QT_TR_NOOP("Layout");
const char* PrefsModel::COLUMNS_PREFERENCE_TREE_NAME = QT_TR_NOOP("Columns");
const char* PrefsModel::FONT_AND_COLORS_PREFERENCE_TREE_NAME = QT_TR_NOOP("Font and Colors");
const char* PrefsModel::CAPTURE_PREFERENCE_TREE_NAME = QT_TR_NOOP("Capture");
const char* PrefsModel::EXPERT_PREFERENCE_TREE_NAME = QT_TR_NOOP("Expert");
const char* PrefsModel::FILTER_BUTTONS_PREFERENCE_TREE_NAME = QT_TR_NOOP("Filter Buttons");
const char* PrefsModel::RSA_KEYS_PREFERENCE_TREE_NAME = QT_TR_NOOP("RSA Keys");


PrefsModel::PrefsModel(QObject *parent) :
    QAbstractItemModel(parent),
    root_(new PrefsItem(QString("ROOT"), NULL))
{
    populate();
}

PrefsModel::~PrefsModel()
{
    delete root_;
}

int PrefsModel::rowCount(const QModelIndex &parent) const
{
    PrefsItem *parent_item;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parent_item = root_;
    else
        parent_item = static_cast<PrefsItem*>(parent.internalPointer());

    if (parent_item == NULL)
        return 0;

    return parent_item->childCount();
}

int PrefsModel::columnCount(const QModelIndex&) const
{
    return colLast;
}


QModelIndex PrefsModel::parent(const QModelIndex& index) const
{
    if (!index.isValid())
        return QModelIndex();

    PrefsItem* item = static_cast<PrefsItem*>(index.internalPointer());
    if (item != NULL) {
        PrefsItem* parent_item = item->parentItem();
        if (parent_item != NULL) {
            if (parent_item == root_)
                return QModelIndex();

            return createIndex(parent_item->row(), 0, parent_item);
        }
    }

    return QModelIndex();
}

QModelIndex PrefsModel::index(int row, int column, const QModelIndex& parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    PrefsItem *parent_item, *child_item;

    if (!parent.isValid())
        parent_item = root_;
    else
        parent_item = static_cast<PrefsItem*>(parent.internalPointer());

    Q_ASSERT(parent_item);

    child_item = parent_item->child(row);
    if (child_item) {
        return createIndex(row, column, child_item);
    }

    return QModelIndex();
}

QVariant PrefsModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::UserRole))
        return QVariant();

    PrefsItem* item = static_cast<PrefsItem*>(index.internalPointer());
    if (item == NULL)
        return QVariant();

    if (role == Qt::UserRole)
        return VariantPointer<PrefsItem>::asQVariant(item);

    switch ((enum PrefsModelColumn)index.column()) {
    case colName:
        return item->getName();

    case colStatus:
        if ((item->getPrefType() == PREF_UAT && (item->getPrefGUIType() == GUI_ALL || item->getPrefGUIType() == GUI_QT)) || item->getPrefType() == PREF_CUSTOM)
            return QObject::tr("Unknown");

        if (item->isPrefDefault())
            return QObject::tr("Default");

        return QObject::tr("Changed");
    case colType:
        return item->getPrefTypeName();
    case colValue:
        if (item->getPref() == NULL)
            return QVariant();

        return QString(gchar_free_to_qstring(prefs_pref_to_str(item->getPref(), pref_stashed)).remove(QRegExp("\n\t")));
    default:
        break;
    }

    return QVariant();
}

static guint
fill_prefs(module_t *module, gpointer root_ptr)
{
    PrefsItem* root_item = static_cast<PrefsItem*>(root_ptr);

    if ((module == NULL) || (root_item == NULL))
        return 1;

    if (module->numprefs < 1 && !prefs_module_has_submodules(module))
        return 0;

    PrefsItem* module_item = new PrefsItem(module, NULL, root_item);
    root_item->prependChild(module_item);

    for (GList *pref_l = module->prefs; pref_l && pref_l->data; pref_l = g_list_next(pref_l)) {
        pref_t *pref = (pref_t *) pref_l->data;

        if (prefs_get_type(pref) == PREF_OBSOLETE || prefs_get_type(pref) == PREF_STATIC_TEXT)
            continue;

        const char *type_name = prefs_pref_type_name(pref);
        if (!type_name)
            continue;

        pref_stash(pref, NULL);

        PrefsItem* item = new PrefsItem(module, pref, module_item);
        module_item->prependChild(item);

        // .uat is a void * so it wins the "useful key value" prize.
        if (prefs_get_uat_value(pref)) {
            prefInsertPrefPtr( prefs_get_uat_value(pref), pref);
        }
    }

    if(prefs_module_has_submodules(module))
        return prefs_modules_foreach_submodules(module, fill_prefs, module_item);

    return 0;
}

void PrefsModel::populate()
{
    //Since "expert" is really a pseudo protocol, it shouldn't be
    //categorized with other "real" protocols when it comes to
    //preferences.  Since it's just a UAT, don't bury it in
    //with the other protocols
    module_t *expert_module = prefs_find_module("_ws.expert");
    if (expert_module)
       expert_module->use_gui = FALSE;

    prefs_modules_foreach_submodules(NULL, fill_prefs, (gpointer)root_);

    //Add the "specially handled" preferences
    PrefsItem *appearance_item, *appearance_subitem, *special_item;

    appearance_item = new PrefsItem(APPEARANCE_PREFERENCE_TREE_NAME, root_);
    root_->prependChild(appearance_item);

    appearance_subitem = new PrefsItem(LAYOUT_PREFERENCE_TREE_NAME, appearance_item);
    appearance_item->prependChild(appearance_subitem);
    appearance_subitem = new PrefsItem(COLUMNS_PREFERENCE_TREE_NAME, appearance_item);
    appearance_item->prependChild(appearance_subitem);
    appearance_subitem = new PrefsItem(FONT_AND_COLORS_PREFERENCE_TREE_NAME, appearance_item);
    appearance_item->prependChild(appearance_subitem);

    special_item = new PrefsItem(CAPTURE_PREFERENCE_TREE_NAME, root_);
    root_->prependChild(special_item);
    special_item = new PrefsItem(EXPERT_PREFERENCE_TREE_NAME, root_);
    root_->prependChild(special_item);
    special_item = new PrefsItem(FILTER_BUTTONS_PREFERENCE_TREE_NAME, root_);
    root_->prependChild(special_item);
#ifdef HAVE_LIBGNUTLS
    special_item = new PrefsItem(RSA_KEYS_PREFERENCE_TREE_NAME, root_);
    root_->prependChild(special_item);
#endif
    special_item = new PrefsItem(ADVANCED_PREFERENCE_TREE_NAME, root_);
    root_->prependChild(special_item);
}





AdvancedPrefsModel::AdvancedPrefsModel(QObject * parent)
: QSortFilterProxyModel(parent),
filter_()
{
}

QVariant AdvancedPrefsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {

        switch (section) {
        case colName:
            return tr("Name");
        case colStatus:
            return tr("Status");
        case colType:
            return tr("Type");
        case colValue:
            return tr("Value");
        default:
            break;
        }
    }
    return QVariant();
}

QVariant AdvancedPrefsModel::data(const QModelIndex &dataindex, int role) const
{
    if (!dataindex.isValid())
        return QVariant();

    QModelIndex modelIndex = mapToSource(dataindex);

    PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
    if (item == NULL)
        return QVariant();

    switch (role)
    {
    case Qt::DisplayRole:
        switch ((AdvancedPrefsModelColumn)dataindex.column())
        {
        case colName:
            if (item->getPref() == NULL)
                return item->getModule()->title;

            return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colName, modelIndex.parent()), role);
        case colStatus:
            if (item->getPref() == NULL)
                return QVariant();

            return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colStatus, modelIndex.parent()), role);
        case colType:
            if (item->getPref() == NULL)
                return QVariant();

            return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colType, modelIndex.parent()), role);
        case colValue:
            if (item->getPref() == NULL)
                return QVariant();

            return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colValue, modelIndex.parent()), role);
        default:
            break;
        }
        break;
    case Qt::ToolTipRole:
        switch ((AdvancedPrefsModelColumn)dataindex.column())
        {
        case colName:
            if (item->getPref() == NULL)
                return QString("<span>%1</span>").arg(item->getModule()->description);

            return QString("<span>%1</span>").arg(prefs_get_description(item->getPref()));
        case colStatus:
            if (item->getPref() == NULL)
                return QVariant();

            return QObject::tr("Has this preference been changed?");
        case colType:
            if (item->getPref() == NULL) {
                return QVariant();
            } else {
                QString type_desc = gchar_free_to_qstring(prefs_pref_type_description(item->getPref()));
                return QString("<span>%1</span>").arg(type_desc);
            }
            break;
        case colValue:
            if (item->getPref() == NULL) {
                return QVariant();
            } else {
                QString default_value = gchar_free_to_qstring(prefs_pref_to_str(item->getPref(), pref_stashed));
                return QString("<span>%1</span>").arg(
                            default_value.isEmpty() ? default_value : QObject::tr("Default value is empty"));
            }
        default:
            break;
        }
        break;
    case Qt::FontRole:
        if (item->getPref() == NULL)
            return QVariant();

        if (!item->isPrefDefault() &&
            /* UATs and custom preferences are "unknown", that shouldn't mean that they are always bolded */
            item->getPrefType() != PREF_UAT && item->getPrefType() != PREF_CUSTOM) {
            QFont font;
            font.setBold(true);
            return font;
        }
        break;
    case Qt::UserRole:
        return sourceModel()->data(modelIndex, role);
    default:
        break;
    }

    return QVariant();
}

bool AdvancedPrefsModel::setData(const QModelIndex &dataindex, const QVariant &value, int role)
{
    if ((!dataindex.isValid()) || (role != Qt::EditRole))
        return false;

    QModelIndex modelIndex = mapToSource(dataindex);

    PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
    if (item == NULL)
        return false;

    if (value.isNull()) {
        //reset preference to default
        reset_stashed_pref(item->getPref());
        item->setChanged(false);
    } else {
        item->setChanged(true);
        switch (item->getPrefType())
        {
        case PREF_DECODE_AS_UINT:
        case PREF_UINT:
            {
            bool ok;
            guint new_val = value.toString().toUInt(&ok, prefs_get_uint_base(item->getPref()));

            if (ok)
                prefs_set_uint_value(item->getPref(), new_val, pref_stashed);
            }
            break;
        case PREF_BOOL:
            prefs_invert_bool_value(item->getPref(), pref_stashed);
            break;
        case PREF_ENUM:
            prefs_set_enum_value(item->getPref(), value.toInt(), pref_stashed);
            break;
        case PREF_STRING:
            prefs_set_string_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed);
            break;
        case PREF_DECODE_AS_RANGE:
        case PREF_RANGE:
            prefs_set_stashed_range_value(item->getPref(), value.toString().toUtf8().constData());
            break;
        case PREF_SAVE_FILENAME:
        case PREF_OPEN_FILENAME:
        case PREF_DIRNAME:
            prefs_set_string_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed);
            break;
        case PREF_COLOR:
        {
            QColor qc(value.toString());
            color_t color;

            color.red = qc.red() << 8 | qc.red();
            color.green = qc.green() << 8 | qc.green();
            color.blue = qc.blue() << 8 | qc.blue();

            prefs_set_color_value(item->getPref(), color, pref_stashed);
            break;
        }
        }
    }

    QVector<int> roles;
    roles << role;

    // The status field may change as well as the value, so mark them for update
    emit dataChanged(index(dataindex.row(), AdvancedPrefsModel::colStatus),
                     index(dataindex.row(), AdvancedPrefsModel::colValue), roles);

    return true;
}

Qt::ItemFlags AdvancedPrefsModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;

    QModelIndex modelIndex = mapToSource(index);

    PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
    if (item == NULL)
        return 0;

    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    if (item->getPref() == NULL) {
        /* Base modules aren't changable */
        flags &= ~(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
    } else {
        flags |= Qt::ItemIsEditable;
    }

    return flags;
}


int AdvancedPrefsModel::columnCount(const QModelIndex&) const
{
    return colLast;
}

void AdvancedPrefsModel::setFirstColumnSpanned(QTreeView* tree, const QModelIndex& mIndex)
{
    int childCount, row;
    PrefsItem* item;
    if (mIndex.isValid()) {
        item = VariantPointer<PrefsItem>::asPtr(data(mIndex, Qt::UserRole));
        if (item != NULL) {
            childCount = item->childCount();
            if (childCount > 0) {
                tree->setFirstColumnSpanned(mIndex.row(), mIndex.parent(), true);
                for (row = 0; row < childCount; row++) {
                    setFirstColumnSpanned(tree, mIndex.child(row, 0));
                }
            }
        }
    } else {
        for (row = 0; row < rowCount(); row++) {
            setFirstColumnSpanned(tree, index(row, 0));
        }
    }
}

bool AdvancedPrefsModel::filterAcceptItem(PrefsItem& item) const
{
    if (filter_.isEmpty())
        return true;

    QString name, tooltip;
    if (item.getPref() == NULL) {
        name = item.getModule()->title;
        tooltip = item.getModule()->description;
    } else {
        name = QString(item.getModule()->name ? item.getModule()->name : item.getModule()->parent->name);
        name += QString(".%1").arg(prefs_get_name(item.getPref()));
        tooltip = prefs_get_description(item.getPref());
    }

    if (name.contains(filter_, Qt::CaseInsensitive) || tooltip.contains(filter_, Qt::CaseInsensitive))
        return true;

    PrefsItem *child_item;
    for (int child_row = 0; child_row < item.childCount(); child_row++)
    {
        child_item = item.child(child_row);
        if ((child_item != NULL) && (filterAcceptItem(*child_item)))
            return true;
    }

    return false;
}

bool AdvancedPrefsModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    QModelIndex nameIdx = sourceModel()->index(sourceRow, PrefsModel::colName, sourceParent);
    PrefsItem* item = static_cast<PrefsItem*>(nameIdx.internalPointer());
    if (item == NULL)
        return true;

    //filter out the "special" preferences
    if ((item->getModule() == NULL) && (item->getPref() == NULL))
        return false;

    if (filterAcceptItem(*item))
        return true;

    return false;
}

void AdvancedPrefsModel::setFilter(const QString& filter)
{
    filter_ = filter;
    invalidateFilter();
}





ModulePrefsModel::ModulePrefsModel(QObject* parent)
    : QSortFilterProxyModel(parent)
    , advancedPrefName_(PrefsModel::ADVANCED_PREFERENCE_TREE_NAME)
{
}

QVariant ModulePrefsModel::data(const QModelIndex &dataindex, int role) const
{
    if (!dataindex.isValid())
        return QVariant();

    QModelIndex modelIndex = mapToSource(dataindex);

    PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
    if (item == NULL)
        return QVariant();

    switch (role)
    {
    case Qt::DisplayRole:
        switch ((ModulePrefsModelColumn)dataindex.column())
        {
        case colName:
            return item->getModuleTitle();
        default:
            break;
        }
        break;
    case Qt::UserRole:
        return sourceModel()->data(modelIndex, role);
    case ModuleName:
        return item->getModuleName();
    default:
        break;
    }

    return QVariant();
}
Qt::ItemFlags ModulePrefsModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;

    bool disable_capture = true;
#ifdef HAVE_LIBPCAP
#ifdef _WIN32
    /* Is WPcap loaded? */
    if (has_wpcap) {
#endif /* _WIN32 */
        disable_capture = false;
#ifdef _WIN32
    }
#endif /* _WIN32 */
#endif /* HAVE_LIBPCAP */

    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    if (disable_capture) {
        QModelIndex modelIndex = mapToSource(index);

        PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
        if (item == NULL)
            return flags;

        if (item->getName().compare(PrefsModel::CAPTURE_PREFERENCE_TREE_NAME) == 0) {
            flags &= (~Qt::ItemIsEnabled);
        }
    }

    return flags;
}

int ModulePrefsModel::columnCount(const QModelIndex&) const
{
    return colLast;
}

bool ModulePrefsModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
    PrefsItem* left_item = static_cast<PrefsItem*>(source_left.internalPointer());
    PrefsItem* right_item = static_cast<PrefsItem*>(source_right.internalPointer());

    if ((left_item != NULL) && (right_item != NULL)) {
        QString left_name = left_item->getModuleTitle(),
                right_name = right_item->getModuleTitle();

        //Force "Advanced" preferences to be at bottom of model
        if (source_left.isValid() && !source_left.parent().isValid() &&
            source_right.isValid() && !source_right.parent().isValid()) {
            if (left_name.compare(advancedPrefName_) == 0) {
                return false;
            }
            if (right_name.compare(advancedPrefName_) == 0) {
                return true;
            }
        }

        if (left_name.compare(right_name, Qt::CaseInsensitive) < 0)
            return true;
    }

    return false;
}

bool ModulePrefsModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    QModelIndex nameIdx = sourceModel()->index(sourceRow, PrefsModel::colName, sourceParent);
    PrefsItem* item = static_cast<PrefsItem*>(nameIdx.internalPointer());
    if (item == NULL)
        return true;

    if (item->getPref() != NULL)
        return false;

    if (item->getModule() != NULL) {
        if (!item->getModule()->use_gui) {
            return false;
        }
    }

    return true;
}




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