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

#include "config.h"

#include "about_dialog.h"
#include <ui_about_dialog.h>

#include "wireshark_application.h"
#include <wsutil/filesystem.h>

#include <QDesktopServices>
#include <QUrl>

#ifdef HAVE_LIBSMI
#include <epan/oids.h>
#endif

#include <epan/maxmind_db.h>

#ifdef HAVE_LUA
#include <epan/wslua/init_wslua.h>
#endif

#include "log.h"
#include "epan/register.h"

#include "ui/alert_box.h"
#include "ui/last_open_dir.h"
#include "ui/help_url.h"
#include "ui/text_import_scanner.h"
#include <wsutil/utf8_entities.h>

#include "file.h"
#include "wsutil/file_util.h"
#include "wsutil/tempfile.h"
#include "wsutil/plugins.h"
#include "wsutil/copyright_info.h"
#include "version_info.h"

#include "extcap.h"

#include <ui/qt/utils/qt_ui_utils.h>
#include <ui/qt/utils/variant_pointer.h>
#include <ui/qt/models/astringlist_list_model.h>
#include <ui/qt/models/url_link_delegate.h>

#include <QFontMetrics>
#include <QKeySequence>
#include <QTextStream>
#include <QUrl>
#include <QRegExp>
#include <QAbstractItemModel>
#include <QHash>
#include <QDesktopServices>
#include <QClipboard>
#include <QMenu>
#include <QFileInfo>

AuthorListModel::AuthorListModel(QObject * parent) :
AStringListListModel(parent)
{
    bool readAck = false;
    QFile f_authors;

    f_authors.setFileName(get_datafile_path("AUTHORS-SHORT"));
    f_authors.open(QFile::ReadOnly | QFile::Text);
    QTextStream ReadFile_authors(&f_authors);
    ReadFile_authors.setCodec("UTF-8");

    QRegExp rx("(.*)[<(]([\\s'a-zA-Z0-9._%+-]+(\\[[Aa][Tt]\\])?[a-zA-Z0-9._%+-]+)[>)]");
    acknowledgement_.clear();
    while (!ReadFile_authors.atEnd()) {
        QString line = ReadFile_authors.readLine();

        if ( ! readAck && line.trimmed().length() == 0 )
                continue;
        if ( line.startsWith("------") )
            continue;

        if ( line == "Acknowledgements" )
        {
            readAck = true;
            continue;
        }
        else if ( rx.indexIn(line) != -1 )
            appendRow( QStringList() << rx.cap(1).trimmed() << rx.cap(2).trimmed());

        if ( readAck )
            acknowledgement_.append(QString("%1\n").arg(line));
    }
    f_authors.close();

}

AuthorListModel::~AuthorListModel() { }

QString AuthorListModel::acknowledgment() const
{
    return acknowledgement_;
}

QStringList AuthorListModel::headerColumns() const
{
    return QStringList() << tr("Name") << tr("Email");
}

#if defined(HAVE_PLUGINS) || defined(HAVE_LUA)
static void plugins_add_description(const char *name, const char *version,
                                    const char *types, const char *filename,
                                    void *user_data)
{
    QList<QStringList> *plugin_data = (QList<QStringList> *)user_data;
    QStringList plugin_row = QStringList() << name << version << types << filename;
    *plugin_data << plugin_row;
}
#endif

PluginListModel::PluginListModel(QObject * parent) : AStringListListModel(parent)
{
    QList<QStringList> plugin_data;
#ifdef HAVE_PLUGINS
    plugins_get_descriptions(plugins_add_description, &plugin_data);
#endif

#ifdef HAVE_LUA
    wslua_plugins_get_descriptions(plugins_add_description, &plugin_data);
#endif

    GHashTable * tools = extcap_loaded_interfaces();
    if (tools && g_hash_table_size(tools) > 0) {
        GList * walker = g_list_first(g_hash_table_get_keys(tools));
        while (walker && walker->data) {
            extcap_info * tool = (extcap_info *)g_hash_table_lookup(tools, walker->data);
            if (tool) {
                QStringList plugin_row = QStringList() << tool->basename << tool->version << tr("extcap") << tool->full_path;
                plugin_data << plugin_row;
            }
            walker = g_list_next(walker);
        }
    }

    typeNames_ << QString("");
    foreach(QStringList row, plugin_data)
    {
        QString type_name = row.at(2);
        typeNames_ << type_name;
        appendRow(row);
    }

    typeNames_.sort();
    typeNames_.removeDuplicates();
}

QStringList PluginListModel::typeNames() const
{
    return typeNames_;
}

QStringList PluginListModel::headerColumns() const
{
    return QStringList() << tr("Name") << tr("Version") << tr("Type") << tr("Path");
}

ShortcutListModel::ShortcutListModel(QObject * parent):
        AStringListListModel(parent)
{
    QMap<QString, QPair<QString, QString> > shortcuts; // name -> (shortcut, description)
    foreach (const QWidget *child, wsApp->mainWindow()->findChildren<QWidget *>()) {
        // Recent items look funny here.
        if (child->objectName().compare("menuOpenRecentCaptureFile") == 0) continue;
        foreach (const QAction *action, child->actions()) {

            if (!action->shortcut().isEmpty()) {
                QString name = action->text();
                name.replace('&', "");
                shortcuts[name] = QPair<QString, QString>(action->shortcut().toString(QKeySequence::NativeText), action->toolTip());
            }
        }
    }

    QStringList names = shortcuts.keys();
    names.sort();
    foreach (const QString &name, names) {
        QStringList row;
        row << shortcuts[name].first << name << shortcuts[name].second;
        appendRow(row);
    }
}

QStringList ShortcutListModel::headerColumns() const
{
    return QStringList() << tr("Shortcut") << tr("Name") << tr("Description");
}

FolderListModel::FolderListModel(QObject * parent):
        AStringListListModel(parent)
{
    /* "file open" */
    appendRow( QStringList() << tr("\"File\" dialogs") << get_last_open_dir() << tr("capture files"));

    /* temp */
    appendRow( QStringList() << tr("Temp") << g_get_tmp_dir() << tr("untitled capture files"));

    /* pers conf */
    appendRow( QStringList() << tr("Personal configuration")
            << gchar_free_to_qstring(get_persconffile_path("", FALSE))
            << tr("dfilters, preferences, ethers, " UTF8_HORIZONTAL_ELLIPSIS));

    /* global conf */
    QString dirPath = get_datafile_dir();
    if (! dirPath.isEmpty()) {
        appendRow ( QStringList() << tr("Global configuration") << dirPath
                << tr("dfilters, preferences, manuf, " UTF8_HORIZONTAL_ELLIPSIS));
    }

    /* system */
    appendRow( QStringList() << tr("System") << get_systemfile_dir() << tr("ethers, ipxnets"));

    /* program */
    appendRow( QStringList() << tr("Program") << get_progfile_dir() << tr("program files"));

#ifdef HAVE_PLUGINS
    /* pers plugins */
    appendRow( QStringList() << tr("Personal Plugins") << get_plugins_pers_dir_with_version() << tr("binary plugins"));

    /* global plugins */
    appendRow( QStringList() << tr("Global Plugins") << get_plugins_dir_with_version() << tr("binary plugins"));
#endif

#ifdef HAVE_LUA
    /* pers plugins */
    appendRow( QStringList() << tr("Personal Lua Plugins") << get_plugins_pers_dir() << tr("lua scripts"));

    /* global plugins */
    appendRow( QStringList() << tr("Global Lua Plugins") << get_plugins_dir() << tr("lua scripts"));
#endif

    /* Extcap */
    QStringList extPaths = QString(get_extcap_dir()).split(G_SEARCHPATH_SEPARATOR_S);

    foreach(QString path, extPaths)
        appendRow( QStringList() << tr("Extcap path") << path.trimmed() << tr("Extcap Plugins search path"));

#ifdef HAVE_MAXMINDDB
    /* MaxMind DB */
    QStringList maxMindDbPaths = QString(maxmind_db_get_paths()).split(G_SEARCHPATH_SEPARATOR_S);
    foreach(QString path, maxMindDbPaths)
        appendRow( QStringList() << tr("MaxMind DB path") << path.trimmed() << tr("MaxMind DB database search path"));
#endif

#ifdef HAVE_LIBSMI
    /* SMI MIBs/PIBs */
    char *default_mib_path = oid_get_default_mib_path();
    QStringList smiPaths = QString(default_mib_path).split(G_SEARCHPATH_SEPARATOR_S);
    g_free(default_mib_path);
    foreach(QString path, smiPaths)
        appendRow( QStringList() << tr("MIB/PIB path") << path.trimmed() << tr("SMI MIB/PIB search path"));
#endif
}

QStringList FolderListModel::headerColumns() const
{
    return QStringList() << tr("Name") << tr("Location") << tr("Typical Files");
}

// To do:
// - Tweak and enhance ui...

AboutDialog::AboutDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::AboutDialog)
{
    ui->setupUi(this);
    setAttribute(Qt::WA_DeleteOnClose, true);
    QFile f_license;
    QString message;

    QString vcs_version_info_str = get_ws_vcs_version_info();
    QString copyright_info_str = get_copyright_info();
    QString comp_info_str = gstring_free_to_qbytearray(get_compiled_version_info(get_wireshark_qt_compiled_info,
                                              get_gui_compiled_info));
    QString runtime_info_str = gstring_free_to_qbytearray(get_runtime_version_info(get_wireshark_runtime_info));


    AuthorListModel * authorModel = new AuthorListModel(this);
    AStringListListSortFilterProxyModel * proxyAuthorModel = new AStringListListSortFilterProxyModel(this);
    proxyAuthorModel->setSourceModel(authorModel);
    proxyAuthorModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
    proxyAuthorModel->setColumnToFilter(0);
    proxyAuthorModel->setColumnToFilter(1);
    ui->tblAuthors->setModel(proxyAuthorModel);
    ui->tblAuthors->setRootIsDecorated(false);
    ui->pte_Authors->clear();
    ui->pte_Authors->appendPlainText(authorModel->acknowledgment());
    ui->pte_Authors->moveCursor(QTextCursor::Start);

    ui->tblAuthors->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->tblAuthors, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
    connect(ui->searchAuthors, SIGNAL(textChanged(QString)), proxyAuthorModel, SLOT(setFilter(QString)));

    /* Wireshark tab */

    /* Construct the message string */
    message = "<p>Version " + html_escape(vcs_version_info_str) + "</p>\n\n";
    message += "<p>" + html_escape(copyright_info_str) + "</p>\n\n";
    message += "<p>" + html_escape(comp_info_str) + "</p>\n\n";
    message += "<p>" + html_escape(runtime_info_str) + "</p>\n\n";
    message += "<p>Wireshark is Open Source Software released under the GNU General Public License.</p>\n\n";
    message += "<p>Check the man page and http://www.wireshark.org for more information.</p>\n\n";

    ui->label_wireshark->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
    ui->label_wireshark->setTextFormat(Qt::RichText);
    ui->label_wireshark->setWordWrap(true);
    ui->label_wireshark->setTextInteractionFlags(Qt::TextSelectableByMouse);
    ui->label_wireshark->setText(message);

/* Check if it is a dev release... (VERSION_MINOR is odd in dev release) */
#if VERSION_MINOR & 1
        ui->label_logo->setPixmap(QPixmap(":/about/wssplash_dev.png"));
#endif

    /* Folders */
    FolderListModel * folderModel = new FolderListModel(this);
    AStringListListSortFilterProxyModel * folderProxyModel = new AStringListListSortFilterProxyModel(this);
    folderProxyModel->setSourceModel(folderModel);
    folderProxyModel->setColumnToFilter(1);
    folderProxyModel->setFilterType(AStringListListSortFilterProxyModel::FilterByStart);
    AStringListListUrlProxyModel * folderDisplayModel = new AStringListListUrlProxyModel(this);
    folderDisplayModel->setSourceModel(folderProxyModel);
    folderDisplayModel->setUrlColumn(1);
    ui->tblFolders->setModel(folderDisplayModel);
    ui->tblFolders->setRootIsDecorated(false);
    ui->tblFolders->setItemDelegateForColumn(1, new UrlLinkDelegate(this));
    ui->tblFolders->setContextMenuPolicy(Qt::CustomContextMenu);
    ui->tblFolders->setTextElideMode(Qt::ElideMiddle);
    connect(ui->tblFolders, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
    connect(ui->searchFolders, SIGNAL(textChanged(QString)), folderProxyModel, SLOT(setFilter(QString)));
    connect(ui->tblFolders, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(urlDoubleClicked(QModelIndex)));


    /* Plugins */
    ui->label_no_plugins->hide();
    PluginListModel * pluginModel = new PluginListModel(this);
    AStringListListSortFilterProxyModel * pluginFilterModel = new AStringListListSortFilterProxyModel(this);
    pluginFilterModel->setSourceModel(pluginModel);
    pluginFilterModel->setColumnToFilter(0);
    AStringListListSortFilterProxyModel * pluginTypeModel = new AStringListListSortFilterProxyModel(this);
    pluginTypeModel->setSourceModel(pluginFilterModel);
    pluginTypeModel->setColumnToFilter(2);
    ui->tblPlugins->setModel(pluginTypeModel);
    ui->tblPlugins->setRootIsDecorated(false);
#ifdef HAVE_LUA
    UrlLinkDelegate *plugin_delegate = new UrlLinkDelegate(this);
    QString pattern = QString("^%1$").arg(wslua_plugin_type_name());
    plugin_delegate->setColCheck(2, pattern);
    ui->tblPlugins->setItemDelegateForColumn(3, plugin_delegate);
#endif
    ui->cmbType->addItems(pluginModel->typeNames());
    ui->tblPlugins->setContextMenuPolicy(Qt::CustomContextMenu);
    ui->tblPlugins->setTextElideMode(Qt::ElideMiddle);
    connect(ui->tblPlugins, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
    connect(ui->searchPlugins, SIGNAL(textChanged(QString)), pluginFilterModel, SLOT(setFilter(QString)));
    connect(ui->cmbType, SIGNAL(currentIndexChanged(QString)), pluginTypeModel, SLOT(setFilter(QString)));
    if (ui->tblPlugins->model()->rowCount() < 1) {
        foreach (QWidget *w, ui->tab_plugins->findChildren<QWidget *>()) {
            w->hide();
        }
        ui->label_no_plugins->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
        ui->label_no_plugins->setEnabled(false);
        ui->label_no_plugins->show();
    }

    /* Shortcuts */
    ShortcutListModel * shortcutModel = new ShortcutListModel(this);
    AStringListListSortFilterProxyModel * shortcutProxyModel = new AStringListListSortFilterProxyModel(this);
    shortcutProxyModel->setSourceModel(shortcutModel);
    shortcutProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
    shortcutProxyModel->setColumnToFilter(1);
    shortcutProxyModel->setColumnToFilter(2);
    ui->tblShortcuts->setModel(shortcutProxyModel);
    ui->tblShortcuts->setRootIsDecorated(false);
    ui->tblShortcuts->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->tblShortcuts, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(handleCopyMenu(QPoint)));
    connect(ui->searchShortcuts, SIGNAL(textChanged(QString)), shortcutProxyModel, SLOT(setFilter(QString)));

    /* License */
#if defined(_WIN32)
    f_license.setFileName(get_datafile_path("COPYING.txt"));
#else
    f_license.setFileName(get_datafile_path("COPYING"));
#endif

    f_license.open(QFile::ReadOnly | QFile::Text);
    QTextStream ReadFile_license(&f_license);

    ui->pte_License->setFont(wsApp->monospaceFont());
    ui->pte_License->insertPlainText(ReadFile_license.readAll());
    ui->pte_License->moveCursor(QTextCursor::Start);

}

AboutDialog::~AboutDialog()
{
    delete ui;
}

void AboutDialog::showEvent(QShowEvent * event)
{
    int one_em = fontMetrics().height();

    // Authors: Names slightly narrower than emails.
    QAbstractItemModel *model = ui->tblAuthors->model();
    int column_count = model->columnCount();
    if (column_count) {
        ui->tblAuthors->setColumnWidth(0, (ui->tblAuthors->parentWidget()->width() / column_count) - one_em);
    }

    // Folders: First and last to contents.
    ui->tblFolders->resizeColumnToContents(0);
    ui->tblFolders->resizeColumnToContents(2);
    ui->tblFolders->setColumnWidth(1, ui->tblFolders->parentWidget()->width() -
                                   (ui->tblFolders->columnWidth(0) + ui->tblFolders->columnWidth(2)));

    // Plugins: All but the last to contents.
    model = ui->tblPlugins->model();
    for (int col = 0; model && col < model->columnCount() - 1; col++) {
        ui->tblPlugins->resizeColumnToContents(col);
    }

    // Contents + 2 em-widths
    ui->tblShortcuts->resizeColumnToContents(0);
    ui->tblShortcuts->setColumnWidth(0, ui->tblShortcuts->columnWidth(0) + (one_em * 2));
    ui->tblShortcuts->setColumnWidth(1, one_em * 12);
    ui->tblShortcuts->resizeColumnToContents(2);

    QDialog::showEvent(event);
}

void AboutDialog::urlDoubleClicked(const QModelIndex &idx)
{
    if (idx.column() != 1) {
        return;
    }
    QTreeView * table = qobject_cast<QTreeView *>(sender());
    if ( ! table )
        return;

    QString urlText = table->model()->data(idx).toString();
    if ( urlText.isEmpty() )
        return;

    QFileInfo fi (urlText);
    if ( fi.isDir() && fi.exists() )
    {
        QUrl url = QUrl::fromLocalFile(urlText);
        if ( url.isValid() )
            QDesktopServices::openUrl(url);
    }
}

void AboutDialog::handleCopyMenu(QPoint pos)
{
    QTreeView * tree = qobject_cast<QTreeView *>(sender());
    if ( ! tree )
        return;

    QModelIndex index = tree->indexAt(pos);
    if ( ! index.isValid() )
        return;

    QMenu * menu = new QMenu(this);

    QAction * copyColumnAction = menu->addAction(tr("Copy"));
    copyColumnAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
    connect(copyColumnAction, SIGNAL(triggered()), this, SLOT(copyActionTriggered()));

    QAction * copyRowAction = menu->addAction(tr("Copy Row(s)"));
    copyRowAction->setData(VariantPointer<QTreeView>::asQVariant(tree));
    connect(copyRowAction, SIGNAL(triggered()), this, SLOT(copyRowActionTriggered()));

    menu->popup(tree->viewport()->mapToGlobal(pos));
}

void AboutDialog::copyRowActionTriggered()
{
    copyActionTriggered(true);
}

void AboutDialog::copyActionTriggered(bool copyRow)
{
    QAction * sendingAction = qobject_cast<QAction *>(sender());
    if ( ! sendingAction )
        return;

    QTreeView * tree = VariantPointer<QTreeView>::asPtr(sendingAction->data());

    QModelIndexList selIndeces = tree->selectionModel()->selectedIndexes();

    int copyColumn = -1;
    if ( ! copyRow )
    {
        QMenu * menu = qobject_cast<QMenu *>(sendingAction->parentWidget());
        if ( menu )
        {
            QPoint menuPosOnTable = tree->mapFromGlobal(menu->pos());
            QModelIndex clickedIndex = tree->indexAt(menuPosOnTable);
            if ( clickedIndex.isValid() )
                copyColumn = clickedIndex.column();
        }
    }

    QString clipdata;
    if ( selIndeces.count() > 0 )
    {
        int columnCount = tree->model()->columnCount();
        QList<int> visitedRows;

        foreach(QModelIndex index, selIndeces)
        {
            if ( visitedRows.contains(index.row()) )
                continue;

            QStringList row;
            if ( copyRow )
            {
                for ( int cnt = 0; cnt < columnCount; cnt++ )
                {
                    QModelIndex dataIdx = tree->model()->index(index.row(), cnt);
                    row << tree->model()->data(dataIdx).toString();
                }
            }
            else
            {
                if ( copyColumn < 0 )
                    copyColumn = index.column();

                QModelIndex dataIdx = tree->model()->index(index.row(), copyColumn);
                row << tree->model()->data(dataIdx).toString();
            }

            clipdata.append(row.join("\t\t").append("\n"));

            visitedRows << index.row();
        }
    }
    QClipboard * clipBoard = QApplication::clipboard();
    clipBoard->setText(clipdata);
}

#ifdef HAVE_LUA
void AboutDialog::on_tblPlugins_doubleClicked(const QModelIndex &index)
{
    const int type_col = 2;
    const int path_col = 3;
    if (index.column() != path_col) {
        return;
    }
    const int row = index.row();
    const QAbstractItemModel *model = index.model();
    if (model->index(row, type_col).data().toString() == wslua_plugin_type_name()) {
        QDesktopServices::openUrl(QUrl::fromLocalFile(model->index(row, path_col).data().toString()));
    }
}
#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:
 */
