// SPDX-License-Identifier: LGPL-2.1

/*
 * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
 */

/**
 *  @file    KsMainWindow.cpp
 *  @brief   KernelShark GUI main window.
 */

// C
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>

// C++11
#include <thread>

// Qt
#include <QMenu>
#include <QFileDialog>
#include <QMenuBar>
#include <QLabel>
#include <QLocalSocket>

// KernelShark
#include "libkshark.h"
#include "libkshark-tepdata.h"
#include "KsCmakeDef.hpp"
#include "KsMainWindow.hpp"
#include "KsAdvFilteringDialog.hpp"

using namespace KsWidgetsLib;

/** Create KernelShark Main window. */
KsMainWindow::KsMainWindow(QWidget *parent)
: QMainWindow(parent),
  _splitter(Qt::Vertical, this),
  _data(this),
  _view(this),
  _graph(this),
  _mState(this),
  _plugins(this),
  _capture(this),
  _captureLocalServer(this),
  _openAction("Open Trace File", this),
  _appendAction("Append Trace File", this),
  _restoreSessionAction("Restore Last Session", this),
  _importSessionAction("Import Session", this),
  _exportSessionAction("Export Session", this),
  _quitAction("Quit", this),
  _graphFilterSyncCBox(nullptr),
  _listFilterSyncCBox(nullptr),
  _showEventsAction("Show events", this),
  _showTasksAction("Show tasks", this),
  _showCPUsAction("Show CPUs", this),
  _advanceFilterAction("TEP Advance Filtering", this),
  _clearAllFilters("Clear all filters", this),
  _cpuSelectAction("CPUs", this),
  _taskSelectAction("Tasks", this),
  _managePluginsAction("Manage Plotting plugins", this),
  _addPluginsAction("Add plugins", this),
  _captureAction("Record", this),
  _addOffcetAction("Add Time Offset", this),
  _colorAction(this),
  _colSlider(this),
  _colorPhaseSlider(Qt::Horizontal, this),
  _fullScreenModeAction("Full Screen Mode", this),
  _aboutAction("About", this),
  _contentsAction("Contents", this),
  _bugReportAction("Report a bug", this),
  _deselectShortcut(this),
  _settings(_getCacheDir() + "/setting.ini", QSettings::IniFormat),
  _workInProgress(this),
  _updateSessionSize(true)
{
	setWindowTitle("Kernel Shark");
	_createActions();
	_createMenus();
	_initCapture();
	_plugins.registerPluginMenues();

	if (geteuid() == 0)
		_rootWarning();

	_splitter.addWidget(&_graph);
	_splitter.addWidget(&_view);
	setCentralWidget(&_splitter);

	/*
	 * Add Status bar. First of all remove the bottom margins of the table
	 * so that the Status bar can nicely stick to it.
	 */
	QMargins m = _view.layout()->contentsMargins();
	m.setBottom(0);
	_view.layout()->setContentsMargins(m);

	/* Now create the Status bar and the "Work In Progress" Widget. */
	QStatusBar *sb = statusBar();
	sb->setFixedHeight(1.2 * FONT_HEIGHT);
	_workInProgress.addToStatusBar(sb);

	_graph.setWipPtr(&_workInProgress);
	_graph.glPtr()->setWipPtr(&_workInProgress);
	_view.setWipPtr(&_workInProgress);

	connect(&_splitter,	&QSplitter::splitterMoved,
		this,		&KsMainWindow::_splitterMoved);

	_view.setMarkerSM(&_mState);
	connect(&_mState,	&KsDualMarkerSM::markSwitchForView,
		&_view,		&KsTraceViewer::markSwitch);

	_graph.setMarkerSM(&_mState);

	connect(&_mState,	&KsDualMarkerSM::updateGraph,
		&_graph,	&KsTraceGraph::markEntry);

	connect(&_mState,	&KsDualMarkerSM::updateView,
		&_view,		&KsTraceViewer::showRow);

	connect(&_view,		&KsTraceViewer::select,
		&_graph,	&KsTraceGraph::markEntry);

	connect(&_view,		&KsTraceViewer::addTaskPlot,
		&_graph,	&KsTraceGraph::addTaskPlot);

	connect(_graph.glPtr(), &KsGLWidget::updateView,
		&_view,		&KsTraceViewer::showRow);

	connect(&_graph,	&KsTraceGraph::deselect,
		this,		&KsMainWindow::_deselectActive);

	connect(&_view,		&KsTraceViewer::deselect,
		this,		&KsMainWindow::_deselectActive);

	connect(&_data,		&KsDataStore::updateWidgets,
		&_view,		&KsTraceViewer::update);

	connect(&_data,		&KsDataStore::updateWidgets,
		&_graph,	&KsTraceGraph::update);

	connect(&_plugins,	&KsPluginManager::dataReload,
		&_data,		&KsDataStore::reload);

	_deselectShortcut.setKey(Qt::CTRL + Qt::Key_D);
	connect(&_deselectShortcut,	&QShortcut::activated,
		this,			&KsMainWindow::_deselectActive);

	connect(&_mState,	&KsDualMarkerSM::deselectA,
		this,		&KsMainWindow::_deselectA);

	connect(&_mState,	&KsDualMarkerSM::deselectB,
		this,		&KsMainWindow::_deselectB);

	_lastDataFilePath = _settings.value("dataPath").toString();
	_lastConfFilePath = _settings.value("confPath").toString();
	_lastPluginFilePath = _settings.value("pluginPath").toString();

	_resizeEmpty();
}

/** Destroy KernelShark Main window. */
KsMainWindow::~KsMainWindow()
{
	kshark_context *kshark_ctx(nullptr);
	QString file = lastSessionFile();

	if (!file.isEmpty()) {
		QByteArray fileBA = file.toLocal8Bit();

		_updateSession();
		kshark_save_config_file(fileBA.data(),
					_session.getConfDocPtr());
	}

	_settings.setValue("dataPath", _lastDataFilePath);
	_settings.setValue("confPath", _lastConfFilePath);
	_settings.setValue("pluginPath", _lastPluginFilePath);

	_data.clear();
	_plugins.deletePluginDialogs();

	/*
	 * Do not show error messages if the "capture" process is still
	 * running (Capture dialog is not closed).
	 */
	if (_capture.state() != QProcess::NotRunning) {
		disconnect(_captureErrorConnection);
		_capture.close();
		_capture.waitForFinished();
	}

	/**
	 * The list of shapes generated by the plugins may contain objects
	 * defined inside the plugins. Make sure to delete these objects now,
	 * because after closing the plugins, the destructors of the
	 * plugins-defined objects will no longer be available.
	 */
	_graph.glPtr()->freePluginShapes();

	if (kshark_instance(&kshark_ctx))
		kshark_free(kshark_ctx);
}

/** Set the list ot CPU cores to be plotted. */
void KsMainWindow::setCPUPlots(int sd, QVector<int> cpus)
{
	kshark_context *kshark_ctx(nullptr);
	kshark_data_stream *stream;

	if (!kshark_instance(&kshark_ctx))
		return;

	stream = kshark_get_data_stream(kshark_ctx, sd);
	if (!stream)
		return;

	int nCPUs = stream->n_cpus;
	auto lamCPUCheck = [=] (int cpu) {
		if (cpu >= nCPUs) {
			qWarning() << "Warning: No CPU" << cpu << "found in the data.";
			return true;
		}

		return false;
	};

	cpus.erase(std::remove_if(cpus.begin(), cpus.end(), lamCPUCheck),
		   cpus.end());

	_graph.cpuReDraw(sd, cpus);
}

/** Set the list ot tasks (pids) to be plotted. */
void KsMainWindow::setTaskPlots(int sd, QVector<int> pids)
{
	QVector<int> allPids = KsUtils::getPidList(sd);
	auto lamPidCheck = [=] (int pid) {
		int i = allPids.indexOf(pid);
		if (i < 0) {
			qWarning() << "Warning: No Pid" << pid << "found in the data.";
			return true;
		}

		return false;
	};

	pids.erase(std::remove_if(pids.begin(), pids.end(), lamPidCheck),
		   pids.end());

	_graph.taskReDraw(sd, pids);
}

/**
 * Reimplemented event handler used to update the geometry of the window on
 * resize events.
 */
void KsMainWindow::resizeEvent(QResizeEvent* event)
{
	QMainWindow::resizeEvent(event);

	if (_updateSessionSize) {
		_session.saveMainWindowSize(*this);
		_session.saveSplitterSize(_splitter);
	}
}

void KsMainWindow::_createActions()
{
	/* File menu */
	_openAction.setIcon(QIcon::fromTheme("document-open"));
	_openAction.setShortcut(tr("Ctrl+O"));
	_openAction.setStatusTip("Open an existing data file");

	connect(&_openAction,	&QAction::triggered,
		this,		&KsMainWindow::_open);

	_appendAction.setIcon(QIcon::fromTheme("document-open"));
	_appendAction.setShortcut(tr("Ctrl+A"));
	_appendAction.setStatusTip("Append an existing data file");

	connect(&_appendAction,	&QAction::triggered,
		this,		&KsMainWindow::_append);

	_restoreSessionAction.setIcon(QIcon::fromTheme("document-open-recent"));
	connect(&_restoreSessionAction,	&QAction::triggered,
		this,			&KsMainWindow::_restoreSession);

	_importSessionAction.setIcon(QIcon::fromTheme("document-send"));
	_importSessionAction.setStatusTip("Load a session");

	connect(&_importSessionAction,	&QAction::triggered,
		this,			&KsMainWindow::_importSession);

	_exportSessionAction.setIcon(QIcon::fromTheme("document-revert"));
	_exportSessionAction.setStatusTip("Export this session");

	connect(&_exportSessionAction,	&QAction::triggered,
		this,			&KsMainWindow::_exportSession);

	_quitAction.setIcon(QIcon::fromTheme("window-close"));
	_quitAction.setShortcut(tr("Ctrl+Q"));
	_quitAction.setStatusTip("Exit KernelShark");

	connect(&_quitAction,	&QAction::triggered,
		this,		&KsMainWindow::close);

	/* Filter menu */
	connect(&_showEventsAction,	&QAction::triggered,
		this,			&KsMainWindow::_showEvents);

	connect(&_showTasksAction,	&QAction::triggered,
		this,			&KsMainWindow::_showTasks);

	connect(&_showCPUsAction,	&QAction::triggered,
		this,			&KsMainWindow::_showCPUs);

	connect(&_advanceFilterAction,	&QAction::triggered,
		this,			&KsMainWindow::_advancedFiltering);

	connect(&_clearAllFilters,	&QAction::triggered,
		this,			&KsMainWindow::_clearFilters);

	/* Plot menu */
	connect(&_cpuSelectAction,	&QAction::triggered,
		this,			&KsMainWindow::_cpuSelect);

	connect(&_taskSelectAction,	&QAction::triggered,
		this,			&KsMainWindow::_taskSelect);

	/* Tools menu */
	_managePluginsAction.setShortcut(tr("Ctrl+P"));
	_managePluginsAction.setIcon(QIcon::fromTheme("preferences-system"));
	_managePluginsAction.setStatusTip("Manage plugins");

	connect(&_managePluginsAction,	&QAction::triggered,
		this,			&KsMainWindow::_pluginSelect);

	_addPluginsAction.setIcon(QIcon::fromTheme("applications-engineering"));
	_addPluginsAction.setStatusTip("Add plugins");

	connect(&_addPluginsAction,	&QAction::triggered,
		this,			&KsMainWindow::_pluginAdd);

	_captureAction.setIcon(QIcon::fromTheme("media-record"));
	_captureAction.setShortcut(tr("Ctrl+R"));
	_captureAction.setStatusTip("Capture trace data");

	connect(&_captureAction,	&QAction::triggered,
		this,			&KsMainWindow::_record);

	connect(&_addOffcetAction,	&QAction::triggered,
		this,			&KsMainWindow::_offset);

	_colorPhaseSlider.setMinimum(20);
	_colorPhaseSlider.setMaximum(180);
	_colorPhaseSlider.setValue(KsPlot::Color::rainbowFrequency() * 100);
	_colorPhaseSlider.setFixedWidth(FONT_WIDTH * 15);

	connect(&_colorPhaseSlider,	&QSlider::valueChanged,
		this,			&KsMainWindow::_setColorPhase);

	/*
	 * Updating the colors of the table can be slow. Do this only when
	 * the slider is released.
	 */
	connect(&_colorPhaseSlider,	&QSlider::sliderReleased,
		&_view,			&KsTraceViewer::loadColors);

	_colSlider.setLayout(new QHBoxLayout);
	_colSlider.layout()->addWidget(new QLabel("Color scheme", this));
	_colSlider.layout()->addWidget(&_colorPhaseSlider);
	_colorAction.setDefaultWidget(&_colSlider);

	_fullScreenModeAction.setIcon(QIcon::fromTheme("view-fullscreen"));
	_fullScreenModeAction.setShortcut(tr("Ctrl+Shift+F"));
	_fullScreenModeAction.setStatusTip("Full Screen Mode");

	connect(&_fullScreenModeAction,	&QAction::triggered,
		this,			&KsMainWindow::_changeScreenMode);

	/* Help menu */
	_aboutAction.setIcon(QIcon::fromTheme("help-about"));

	connect(&_aboutAction,		&QAction::triggered,
		this,			&KsMainWindow::_aboutInfo);

	_contentsAction.setIcon(QIcon::fromTheme("help-contents"));
	connect(&_contentsAction,	&QAction::triggered,
		this,			&KsMainWindow::_contents);

	connect(&_bugReportAction,	&QAction::triggered,
		this,			&KsMainWindow::_bugReport);
}

void KsMainWindow::_createMenus()
{
	QMenu *file, *sessions, *filter, *plots, *tools, *help;
	kshark_context *kshark_ctx(nullptr);

	if (!kshark_instance(&kshark_ctx))
		return;

	/* File menu */
	file = menuBar()->addMenu("File");
	file->addAction(&_openAction);
	file->addAction(&_appendAction);

	sessions = file->addMenu("Sessions");
	sessions->setIcon(QIcon::fromTheme("document-properties"));
	sessions->addAction(&_restoreSessionAction);
	sessions->addAction(&_importSessionAction);
	sessions->addAction(&_exportSessionAction);
	file->addAction(&_quitAction);

	/*
	 * Enable the "Append Trace File" menu only in the case of multiple
	 * data streams.
	 */
	auto lamEnableAppendAction = [this] () {
		kshark_context *kshark_ctx(nullptr);

		if (!kshark_instance(&kshark_ctx))
			return;

		if (kshark_ctx->n_streams > 0)
			_appendAction.setEnabled(true);
		else
			_appendAction.setEnabled(false);
	};

	connect(file,	&QMenu::aboutToShow, lamEnableAppendAction);

	/* Filter menu */
	filter = menuBar()->addMenu("Filter");

	connect(filter, 		&QMenu::aboutToShow,
		this,			&KsMainWindow::_updateFilterMenu);

	/*
	 * Set the default filter mask. Filter will apply to both View and
	 * Graph.
	 */
	kshark_ctx->filter_mask =
		KS_TEXT_VIEW_FILTER_MASK | KS_GRAPH_VIEW_FILTER_MASK;

	kshark_ctx->filter_mask |= KS_EVENT_VIEW_FILTER_MASK;

	_graphFilterSyncCBox =
		KsUtils::addCheckBoxToMenu(filter, "Apply filters to Graph");
	_graphFilterSyncCBox->setChecked(true);

	connect(_graphFilterSyncCBox,	&QCheckBox::stateChanged,
		this,			&KsMainWindow::_graphFilterSync);

	_listFilterSyncCBox =
		KsUtils::addCheckBoxToMenu(filter, "Apply filters to List");
	_listFilterSyncCBox->setChecked(true);

	connect(_listFilterSyncCBox,	&QCheckBox::stateChanged,
		this,			&KsMainWindow::_listFilterSync);

	filter->addAction(&_showEventsAction);
	filter->addAction(&_showTasksAction);
	filter->addAction(&_showCPUsAction);
	filter->addAction(&_advanceFilterAction);
	filter->addAction(&_clearAllFilters);

	/*
	 * Enable the "TEP Advance Filtering" menu only in the case when TEP
	 * data is loaded.
	 */
	auto lamEnableAdvFilterAction = [this] () {
		kshark_context *kshark_ctx(nullptr);
		QVector<int> streamIds;

		if (!kshark_instance(&kshark_ctx))
			return;

		_advanceFilterAction.setEnabled(false);
		streamIds = KsUtils::getStreamIdList(kshark_ctx);
		for (auto const &sd: streamIds) {
			if (kshark_is_tep(kshark_ctx->stream[sd])) {
				_advanceFilterAction.setEnabled(true);
				break;
			}
		}
	};

	connect(filter,	&QMenu::aboutToShow, lamEnableAdvFilterAction);

	/* Plot menu */
	plots = menuBar()->addMenu("Plots");
	plots->addAction(&_cpuSelectAction);
	plots->addAction(&_taskSelectAction);

	/* Tools menu */
	tools = menuBar()->addMenu("Tools");
	tools->addAction(&_colorAction);
	tools->addAction(&_fullScreenModeAction);
	tools->addSeparator();
	tools->addAction(&_captureAction);
	tools->addAction(&_managePluginsAction);
	tools->addAction(&_addPluginsAction);
	tools->addAction(&_addOffcetAction);

	/*
	 * Enable the "Add Time Offset" menu only in the case of multiple
	 * data streams.
	 */
	auto lamEnableOffcetAction = [this] () {
		kshark_context *kshark_ctx(nullptr);

		if (!kshark_instance(&kshark_ctx))
			return;

		if (kshark_ctx->n_streams > 1)
			_addOffcetAction.setEnabled(true);
		else
			_addOffcetAction.setEnabled(false);
	};

	connect(tools,	&QMenu::aboutToShow, lamEnableOffcetAction);

	/* Help menu */
	help = menuBar()->addMenu("Help");
	help->addAction(&_aboutAction);
	help->addAction(&_contentsAction);
	help->addAction(&_bugReportAction);
}

/**
 * @brief Add a plugin configuration/control menu.
 *
 * @param place: String describing the place to add the menu. For example
 *		 "Tools/Plot Latency".
 * @param func: Function that will launch of plugin control menus.
 */
void KsMainWindow::addPluginMenu(QString place, pluginActionFunc func)
{
	QStringList dialogPath = place.split("/");
	QAction *pluginAction;

	auto lamAddMenu = [this, func] () {
		func(this);
	};

	for (auto &m:  menuBar()->findChildren<QMenu*>()) {
		if(dialogPath[0] == m->menuAction()->text()) {
			pluginAction = new QAction(dialogPath[1], this);
			m->addAction(pluginAction);

			connect(pluginAction,	&QAction::triggered,
				lamAddMenu);
		}
	}
}

/** Select the kshark_entry having given index with a given maker. */
void KsMainWindow::markEntry(ssize_t row, DualMarkerState st)
{
	if (row >= 0) {
		_mState.setState(st);
		_graph.markEntry(row);
		_view.showRow(row, true);
	}
}

/** Select given kshark_entry with a given maker. */
void KsMainWindow::markEntry(const kshark_entry *e, DualMarkerState st)
{
	ssize_t row;

	if (!e) {
		_mState.getMarker(st).reset();
		return;
	}

	row = kshark_find_entry_by_time(e->ts, _data.rows(),
					0, _data.size() - 1);

	markEntry(row, st);
}


void KsMainWindow::_open()
{
	QString fileName;

	fileName = KsUtils::getFile(this, "Open File",
				    "trace-cmd files (*.dat);;All files (*)",
				    _lastDataFilePath);

	if (!fileName.isEmpty())
		loadDataFile(fileName);
}

void KsMainWindow::_append()
{
	QString fileName = KsUtils::getFile(this,
					    "Append File",
					    "trace-cmd files (*.dat);;Text files (*.txt);;All files (*)",
					    _lastDataFilePath);

	if (!fileName.isEmpty())
		appendDataFile(fileName);
}

QString KsMainWindow::_getCacheDir()
{
	QString dir;

	auto lamMakePath = [&] (bool ask) {
		if (ask) {
			QMessageBox::StandardButton reply;
			QString err("KernelShark cache directory not found!\n");
			QString question =
				QString("Do you want to create %1").arg(dir);

			reply = QMessageBox::question(this, "KernelShark",
						      err + question,
						      QMessageBox::Yes |
						      QMessageBox::No);

			if (reply == QMessageBox::No) {
				dir.clear();
				return;
			}
		}

		QDir().mkpath(dir);
	};

	auto lamRootHome = [] () {
		struct passwd *pwd = getpwuid(0);

		return pwd ? QString(pwd->pw_dir) : QString("/root");
	};

	dir = getenv("KS_USER_CACHE_DIR");
	if (!dir.isEmpty()) {
		if (!QDir(dir).exists())
			lamMakePath(true);
	} else {
		auto appCachePath = QStandardPaths::GenericCacheLocation;
		dir = QStandardPaths::writableLocation(appCachePath);
		dir += "/kernelshark";

		if (geteuid() == 0)
			dir.replace(QDir::homePath(), lamRootHome());

		if (!QDir(dir).exists())
			lamMakePath(false);
	}

	return dir;
}

/** Get the description file of the last session. */
QString KsMainWindow::lastSessionFile()
{
	QString file;

	file = _getCacheDir();
	if (!file.isEmpty())
		file += "/lastsession.json";

	return file;
}

void KsMainWindow::_restoreSession()
{
	loadSession(lastSessionFile());
}

void KsMainWindow::_importSession()
{
	QString fileName;

	fileName = KsUtils::getFile(this, "Import Session",
				    "Kernel Shark Config files (*.json);;",
				    _lastConfFilePath);

	if (fileName.isEmpty())
		return;

	loadSession(fileName);
}

void KsMainWindow::_updateSession()
{
	kshark_context *kshark_ctx(nullptr);

	if (!kshark_instance(&kshark_ctx))
		return;

	_session.saveVisModel(_graph.glPtr()->model()->histo());
	_session.saveDataStreams(kshark_ctx);
	_session.saveGraphs(kshark_ctx, _graph);
	_session.saveDualMarker(&_mState);
	_session.saveTable(_view);
	_session.saveColorScheme();
	_session.saveUserPlugins(_plugins);
}

void KsMainWindow::_exportSession()
{
	QString fileName;

	fileName = KsUtils::getSaveFile(this, "Export Filter",
					"Kernel Shark Config files (*.json);;",
					".json",
					_lastConfFilePath);
	if (fileName.isEmpty())
		return;

	_updateSession();
	_session.exportToFile(fileName);
}

void KsMainWindow::_filterSyncCBoxUpdate(kshark_context *kshark_ctx)
{
	if (kshark_ctx->filter_mask & KS_TEXT_VIEW_FILTER_MASK)
		_listFilterSyncCBox->setChecked(true);
	else
		_listFilterSyncCBox->setChecked(false);

	if (kshark_ctx->filter_mask &
	    (KS_GRAPH_VIEW_FILTER_MASK | KS_EVENT_VIEW_FILTER_MASK))
		_graphFilterSyncCBox->setChecked(true);
	else
		_graphFilterSyncCBox->setChecked(false);
}

void KsMainWindow::_updateFilterMenu()
{
	kshark_context *kshark_ctx(nullptr);

	if (kshark_instance(&kshark_ctx))
		_filterSyncCBoxUpdate(kshark_ctx);
}

void KsMainWindow::_listFilterSync(int state)
{
	KsUtils::listFilterSync(state);
	_data.update();
}

void KsMainWindow::_graphFilterSync(int state)
{
	KsUtils::graphFilterSync(state);
	_data.update();
}

void KsMainWindow::_presetCBWidget(kshark_hash_id *showFilter,
				   kshark_hash_id *hideFilter,
				   KsCheckBoxWidget *cbw)
{
	if (!kshark_this_filter_is_set(showFilter) &&
	    !kshark_this_filter_is_set(hideFilter)) {
		/*
		 * No filter is set currently. All CheckBoxes of the Widget
		 * will be checked.
		 */
		cbw->setDefault(true);
	} else {
		QVector<int> ids = cbw->getIds();
		QVector<int>  status;
		int n = ids.count();
		bool show, hide;

		if (kshark_this_filter_is_set(showFilter)) {
			/*
			 * The "show only" filter is set. The default status
			 * of all CheckBoxes will be "unchecked".
			 */
			status = QVector<int>(n, false);
			for (int i = 0; i < n; ++i) {
				show = !!kshark_hash_id_find(showFilter,
							         ids[i]);

				hide = !!kshark_hash_id_find(hideFilter,
							         ids[i]);

				if (show && !hide) {
					/*
					 * Both "show" and "hide" define this
					 * Id as visible. Set the status of
					 * its CheckBoxes to "checked".
					 */
					status[i] = true;
				}
			}
		} else {
			/*
			 * Only the "do not show" filter is set. The default
			 * status of all CheckBoxes will be "checked".
			 */
			status = QVector<int>(n, true);
			for (int i = 0; i < n; ++i) {
				hide = !!kshark_hash_id_find(hideFilter,
							         ids[i]);

				if (hide)
					status[i] = false;
			}
		}

		cbw->set(status);
	}
}

void KsMainWindow::_applyFilter(int sd, QVector<int> all, QVector<int> show,
				std::function<void(int, QVector<int>)> posFilter,
				std::function<void(int, QVector<int>)> negFilter)
{
	if (show.count() != 0 && show.count() < all.count() / 2) {
		posFilter(sd, show);
	} else {
		/*
		 * It is more efficiant to apply negative (do not show) filter.
		 */
		QVector<int> diff;

		/*
		 * The Ids may not be sorted, because in the widgets the items
		 * are shown sorted by name. Get those Ids sorted first.
		 */
		std::sort(all.begin(), all.end());
		std::sort(show.begin(), show.end());

		/*
		 * The IDs of the "do not show" filter are given by the
		 * difference between "all" Ids and the Ids of the "show only"
		 * filter.
		 */
		std::set_difference(all.begin(), all.end(),
				    show.begin(), show.end(),
				    std::inserter(diff, diff.begin()));

		negFilter(sd, diff);
	}
}

/* Quiet warnings over documenting simple structures */
//! @cond Doxygen_Suppress

#define LAMBDA_FILTER(method) [=] (int sd, QVector<int> vec) {method(sd, vec);}

//! @endcond

void KsMainWindow::_showEvents()
{
	kshark_context *kshark_ctx(nullptr);
	QVector<KsCheckBoxWidget *> cbws;
	KsCheckBoxWidget *events_cbw;
	KsCheckBoxDialog *dialog;
	kshark_data_stream *stream;
	QVector<int> streamIds;

	if (!kshark_instance(&kshark_ctx))
		return;

	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		stream = kshark_ctx->stream[sd];
		events_cbw = new KsEventsCheckBoxWidget(stream, this);
		cbws.append(events_cbw);
		_presetCBWidget(stream->show_event_filter,
				stream->hide_event_filter,
				events_cbw);
	}

	dialog = new KsCheckBoxDialog(cbws, this);

	auto lamFilter = [=] (int sd, QVector<int> show) {
		QVector<int> all = KsUtils::getEventIdList(sd);
		_applyFilter(sd, all, show,
			     LAMBDA_FILTER(_data.applyPosEventFilter),
			     LAMBDA_FILTER(_data.applyNegEventFilter));
	};

	connect(dialog,		&KsCheckBoxDialog::apply, lamFilter);

	dialog->show();
}

void KsMainWindow::_showTasks()
{
	kshark_context *kshark_ctx(nullptr);
	QVector<KsCheckBoxWidget *> cbws;
	kshark_data_stream *stream;
	KsCheckBoxWidget *tasks_cbw;
	KsCheckBoxDialog *dialog;
	QVector<int> streamIds;

	if (!kshark_instance(&kshark_ctx))
		return;

	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		stream = kshark_ctx->stream[sd];
		tasks_cbw = new KsTasksCheckBoxWidget(stream, true, this);
		cbws.append(tasks_cbw);
		_presetCBWidget(stream->show_task_filter,
				stream->hide_task_filter,
				tasks_cbw);
	}

	dialog = new KsCheckBoxDialog(cbws, this);

	auto lamFilter = [=] (int sd, QVector<int> show) {
		QVector<int> all = KsUtils::getPidList(sd);
		_applyFilter(sd, all, show,
			     LAMBDA_FILTER(_data.applyPosTaskFilter),
			     LAMBDA_FILTER(_data.applyNegTaskFilter));
	};

	connect(dialog,		&KsCheckBoxDialog::apply, lamFilter);

	dialog->show();
}

void KsMainWindow::_showCPUs()
{
	kshark_context *kshark_ctx(nullptr);
	QVector<KsCheckBoxWidget *> cbws;
	kshark_data_stream *stream;
	KsCheckBoxWidget *cpus_cbw;
	KsCheckBoxDialog *dialog;
	QVector<int> streamIds;

	if (!kshark_instance(&kshark_ctx))
		return;

	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		stream = kshark_ctx->stream[sd];
		cpus_cbw = new KsCPUCheckBoxWidget(stream, this);
		cbws.append(cpus_cbw);
		_presetCBWidget(stream->show_task_filter,
				stream->hide_task_filter,
				cpus_cbw);
	}

	dialog = new KsCheckBoxDialog(cbws, this);

	auto lamFilter = [=] (int sd, QVector<int> show) {
		QVector<int> all = KsUtils::getCPUList(sd);
		_applyFilter(sd, all, show,
			     LAMBDA_FILTER(_data.applyPosCPUFilter),
			     LAMBDA_FILTER(_data.applyNegCPUFilter));
	};

	connect(dialog,		&KsCheckBoxDialog::apply, lamFilter);

	dialog->show();
}

void KsMainWindow::_advancedFiltering()
{
	KsAdvFilteringDialog *dialog;

	dialog = new KsAdvFilteringDialog(this);
	connect(dialog,		&KsAdvFilteringDialog::dataReload,
		&_data,		&KsDataStore::reload);

	dialog->show();
}

void KsMainWindow::_clearFilters()
{
	_data.clearAllFilters();
}

void KsMainWindow::_cpuSelect()
{
	kshark_context *kshark_ctx(nullptr);
	QVector<KsCheckBoxWidget *> cbws;
	kshark_data_stream *stream;
	KsCheckBoxWidget *cpus_cbd;
	KsCheckBoxDialog *dialog;
	QVector<int> streamIds;
	int nCPUs;

	if (!kshark_instance(&kshark_ctx))
		return;

	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		stream = kshark_ctx->stream[sd];
		cpus_cbd = new KsCPUCheckBoxWidget(stream, this);
		cbws.append(cpus_cbd);

		nCPUs = stream->n_cpus;
		if (nCPUs == _graph.glPtr()->cpuGraphCount(sd)) {
			cpus_cbd->setDefault(true);
		} else {
			QVector<int> v(nCPUs, false);
			for (auto const &cpu: _graph.glPtr()->_streamPlots[sd]._cpuList)
				v[cpu] = true;

			cpus_cbd->set(v);
		}
	}

	dialog = new KsCheckBoxDialog(cbws, this);

	connect(dialog,		&KsCheckBoxDialog::apply,
		&_graph,	&KsTraceGraph::cpuReDraw);

	dialog->show();
}

void KsMainWindow::_taskSelect()
{
	kshark_context *kshark_ctx(nullptr);
	QVector<KsCheckBoxWidget *> cbws;
	QVector<int> streamIds, pids;
	kshark_data_stream *stream;
	KsCheckBoxWidget *tasks_cbd;
	KsCheckBoxDialog *dialog;
	int nPids;

	if (!kshark_instance(&kshark_ctx))
		return;

	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		stream = kshark_ctx->stream[sd];
		tasks_cbd = new KsTasksCheckBoxWidget(stream, true, this);
		cbws.append(tasks_cbd);

		pids = KsUtils::getPidList(sd);
		nPids = pids.count();
		if (nPids == _graph.glPtr()->taskGraphCount(sd)) {
			tasks_cbd->setDefault(true);
		} else {
			QVector<int> v(nPids, false);
			for (int i = 0; i < nPids; ++i) {
				QVector<int> plots =
					_graph.glPtr()->_streamPlots[sd]._taskList;
				for (auto const &pid: plots) {
					if (pids[i] == pid) {
						v[i] = true;
						break;
					}
				}
			}

			tasks_cbd->set(v);
		}
	}

	dialog = new KsCheckBoxDialog(cbws, this);

	connect(dialog,		&KsCheckBoxDialog::apply,
		&_graph,	&KsTraceGraph::taskReDraw);

	dialog->show();
}

void KsMainWindow::_pluginSelect()
{
	QVector<int> streamIds, enabledPlugins, failedPlugins;
	kshark_context *kshark_ctx(nullptr);
	QVector<KsCheckBoxWidget *> cbws;
	KsPluginCheckBoxWidget *plugin_cbw;
	KsCheckBoxDialog *dialog;
	QStringList pluginList;

	if (!kshark_instance(&kshark_ctx))
		return;

	if (kshark_ctx->n_streams == 0) {
		QString err("Data has to be loaded first.");
		QMessageBox msgBox;
		msgBox.critical(nullptr, "Error", err);

		return;
	}

	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		pluginList = _plugins.getStreamPluginList(sd);
		enabledPlugins = _plugins.getActivePlugins(sd);
		failedPlugins =
			_plugins.getPluginsByStatus(sd, KSHARK_PLUGIN_FAILED);

		plugin_cbw = new KsPluginCheckBoxWidget(sd, pluginList, this);
		plugin_cbw->set(enabledPlugins);
		plugin_cbw->setActive(failedPlugins, false);

		cbws.append(plugin_cbw);
	}

	dialog = new KsPluginsCheckBoxDialog(cbws, &_data, this);
	dialog->applyStatus();

	connect(dialog,		&KsCheckBoxDialog::apply,
		this,		&KsMainWindow::_pluginUpdate);

	dialog->show();

	_graph.update(&_data);
}

void KsMainWindow::_pluginUpdate(int sd, QVector<int> pluginStates)
{
	kshark_context *kshark_ctx(nullptr);
	QVector<int> streamIds;

	if (!kshark_instance(&kshark_ctx))
		return;

	_plugins.updatePlugins(sd, pluginStates);
	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	if (streamIds.size() && streamIds.last() == sd) {
		/* This is the last stream. Reload the data. */
		if (_data.size())
			_data.reload();
	}
}

void KsMainWindow::_pluginAdd()
{
	kshark_context *kshark_ctx(nullptr);
	QStringList fileNames;
	QVector<int> streams;

	if (!kshark_instance(&kshark_ctx))
		return;

	fileNames = KsUtils::getFiles(this,
				      "Add KernelShark plugins",
				      "KernelShark Plugins (*.so);;",
				      _lastPluginFilePath);

	if (!fileNames.isEmpty()) {
		if (kshark_ctx->n_streams > 1) {
			KsDStreamCheckBoxWidget *stream_cbw;
			QVector<KsCheckBoxWidget *> cbws;
			KsCheckBoxDialog *dialog;

			stream_cbw = new KsDStreamCheckBoxWidget();
			cbws.append(stream_cbw);
			dialog = new KsCheckBoxDialog(cbws, this);

			auto lamStreams = [&streams] (int, QVector<int> s) {
				streams = s;
			};

			connect(dialog, &KsCheckBoxDialog::apply, lamStreams);
			dialog->exec();
		}

		_graph.startOfWork(KsDataWork::UpdatePlugins);

		_plugins.addPlugins(fileNames, streams);
		if (_data.size())
			_data.reload();

		_graph.endOfWork(KsDataWork::UpdatePlugins);
	}
}

void KsMainWindow::_record()
{
	bool canDoAsRoot(false);

#ifdef DO_AS_ROOT
	canDoAsRoot = true;
#endif

	if (geteuid() && !canDoAsRoot) {
		QString message;

		message = "Record is currently not supported.";
		message += " Install \"pkexec\" and then do:<br>";
		message += " cd build <br> sudo ./cmake_uninstall.sh <br>";
		message += " ./cmake_clean.sh <br> cmake .. <br> make <br>";
		message += " sudo make install";

		_error(message, "recordCantStart", false);
		return;
	}

	_capture.start();
}

void KsMainWindow::_offset()
{
	KsTimeOffsetDialog *dialog = new KsTimeOffsetDialog(this);

	auto lamApplyOffset = [&] (int sd, double ms) {
		_data.setClockOffset(sd, ms * 1000);
		_graph.update(&_data);
	};

	connect(dialog, &KsTimeOffsetDialog::apply, lamApplyOffset);
}

void KsMainWindow::_setColorPhase(int f)
{
	KsPlot::Color::setRainbowFrequency(f / 100.);
	_graph.glPtr()->loadColors();
	_graph.glPtr()->model()->update();
}

void KsMainWindow::_changeScreenMode()
{
	if (isFullScreen()) {
		_fullScreenModeAction.setText("Full Screen Mode");
		_fullScreenModeAction.setIcon(QIcon::fromTheme("view-fullscreen"));
		showNormal();
	} else {
		_fullScreenModeAction.setText("Exit Full Screen Mode");
		_fullScreenModeAction.setIcon(QIcon::fromTheme("view-restore"));
		showFullScreen();
	}
}

void KsMainWindow::_aboutInfo()
{
	KsMessageDialog *message;
	QString text;

	text.append(" KernelShark\n\n version: ");
	text.append(KS_VERSION_STRING);
	text.append("\n");

	message = new KsMessageDialog(text);
	message->setWindowTitle("About");
	message->show();
}

void KsMainWindow::_contents()
{
	QDesktopServices::openUrl(QUrl("http://kernelshark.org/",
				  QUrl::TolerantMode));
}

void KsMainWindow::_bugReport()
{
	QUrl bugs("https://bugzilla.kernel.org/buglist.cgi?component=Trace-cmd%2FKernelshark&product=Tools&resolution=---",
		  QUrl::TolerantMode);

	QDesktopServices::openUrl(bugs);
}

void KsMainWindow::_load(const QString& fileName, bool append)
{
	QString pbLabel("Loading    ");
	bool loadDone = false;
	struct stat st;
	double shift(.0);
	int ret, sd;

	ret = stat(fileName.toStdString().c_str(), &st);
	if (ret != 0) {
		QString text("Unable to find file ");

		text.append(fileName);
		text.append(".");
		_error(text, "loadDataErr1", true);

		return;
	}

	qInfo() << "Loading " << fileName;

	if (fileName.size() < 40) {
		pbLabel += fileName;
	} else {
		pbLabel += "...";
		pbLabel += fileName.mid(fileName.size() - 37, 37);
	}

	setWindowTitle("Kernel Shark");
	KsWidgetsLib::KsProgressBar pb(pbLabel);
	QApplication::processEvents();

	_view.reset();
	if (!append)
		_graph.reset();

	auto lamLoadJob = [&, this] () {
		QVector<kshark_dpi *> v;
		for (auto const p: _plugins.getUserPlugins()) {
			if (p->process_interface)
				v.append(p->process_interface);
		}

		sd = _data.loadDataFile(fileName, v);
		loadDone = true;
	};

	auto lamAppendJob = [&, this] () {
		sd = _data.appendDataFile(fileName, shift);
		loadDone = true;
	};

	std::thread job;
	if (append) {
		job = std::thread(lamAppendJob);
	} else {
		job = std::thread(lamLoadJob);
	}

	for (int i = 0; i < 160; ++i) {
		/*
		 * TODO: The way this progress bar gets updated here is a pure
		 * cheat. See if this can be implemented better.
		*/
		if (loadDone)
			break;

		pb.setValue(i);
		usleep(150000);
	}

	job.join();

	if (sd < 0 || !_data.size()) {
		QString text("File ");

		text.append(fileName);
		text.append(" contains no data.");
		_error(text, "loadDataErr2", true);
	}

	_view.loadData(&_data);
	pb.setValue(175);

	_graph.loadData(&_data, !append);
	if (append)
		_graph.cpuReDraw(sd, KsUtils::getCPUList(sd));

	pb.setValue(195);
}

/** Load trace data for file. */
void KsMainWindow::loadDataFile(const QString& fileName)
{
	_mState.reset();
	_load(fileName, false);
	setWindowTitle("Kernel Shark (" + fileName + ")");
}

/** Append trace data for file. */
void KsMainWindow::appendDataFile(const QString& fileName)
{
	kshark_entry *eMarkA(nullptr), *eMarkB(nullptr);
	int rowA = _mState.markerAPos();
	int rowB = _mState.markerBPos();

	if (rowA >= 0)
		eMarkA = _data.rows()[rowA];

	if (rowB >= 0)
		eMarkB = _data.rows()[rowB];

	_load(fileName, true);

	markEntry(eMarkA, DualMarkerState::A);
	markEntry(eMarkB, DualMarkerState::B);
}

void KsMainWindow::_error(const QString &mesg,
			  const QString &errCode,
			  bool resize)
{
	QErrorMessage *em = new QErrorMessage(this);
	QString text = mesg;
	QString html = mesg;

	if (resize)
		_resizeEmpty();

	text.replace("<br>", "\n", Qt::CaseInsensitive);
	html.replace("\n", "<br>", Qt::CaseInsensitive);

	qCritical().noquote() << "ERROR:" << text;
	em->showMessage(html, errCode);
	em->exec();
}

/**
 * @brief Load user session.
 *
 * @param fileName: Json file containing the description of the session.
 */
void KsMainWindow::loadSession(const QString &fileName)
{
	kshark_context *kshark_ctx(nullptr);
	bool loadDone = false;
	struct stat st;
	int ret;

	if (!kshark_instance(&kshark_ctx))
		return;

	ret = stat(fileName.toStdString().c_str(), &st);
	if (ret != 0) {
		QString text("Unable to find session file ");

		text.append(fileName);
		text.append("\n");
		_error(text, "loadSessErr0", true);

		return;
	}

	KsWidgetsLib::KsProgressBar pb("Loading session settings ...");
	pb.setValue(10);

	_updateSessionSize = false;
	if (!_session.importFromFile(fileName)) {
		QString text("Unable to open session description file ");

		text.append(fileName);
		text.append(".\n");
		_error(text, "loadSessErr1", true);

		return;
	}

	_view.reset();
	_graph.reset();
	_data.clear();

	_session.loadUserPlugins(kshark_ctx, &_plugins);
	pb.setValue(20);

	auto lamLoadJob = [&] (KsDataStore *d) {
		_session.loadDataStreams(kshark_ctx, &_data);
		loadDone = true;
	};

	std::thread job = std::thread(lamLoadJob, &_data);

	for (int i = 0; i < 150; ++i) {
		/*
		 * TODO: The way this progress bar gets updated here is a pure
		 * cheat. See if this can be implemented better.
		*/
		if (loadDone)
			break;

		pb.setValue(i);
		usleep(300000);
	}

	job.join();

	_view.loadData(&_data);
	pb.setValue(155);

	_graph.loadData(&_data, true);
	_filterSyncCBoxUpdate(kshark_ctx);
	pb.setValue(175);

	_session.loadSplitterSize(&_splitter);
	_session.loadMainWindowSize(this);
	_updateSessionSize = true;
	pb.setValue(180);

	_session.loadDualMarker(&_mState, &_graph);
	_session.loadVisModel(_graph.glPtr()->model());
	_mState.updateMarkers(_data, _graph.glPtr());
	_session.loadGraphs(kshark_ctx, _graph);
	pb.setValue(190);

	_session.loadTable(&_view);
	_colorPhaseSlider.setValue(_session.getColorScheme() * 100);
	_graph.updateGeom();
}

void KsMainWindow::_initCapture()
{
	bool canDoAsRoot(false);

#ifdef DO_AS_ROOT
	canDoAsRoot = true;
#endif

	if (geteuid() && !canDoAsRoot)
		return;

	if (geteuid()) {
		_capture.setProgram("kshark-su-record");
	} else {
		QStringList argv;

		_capture.setProgram("kshark-record");
		argv << QString("-o") << QDir::homePath() + "/trace.dat";
		_capture.setArguments(argv);
	}

	connect(&_capture,	&QProcess::started,
		this,		&KsMainWindow::_captureStarted);

	/*
	 * Using the old Signal-Slot syntax because QProcess::finished has
	 * overloads.
	 */
	connect(&_capture,	SIGNAL(finished(int, QProcess::ExitStatus)),
		this,		SLOT(_captureFinished(int, QProcess::ExitStatus)));

	_captureErrorConnection =
		connect(&_capture,	&QProcess::errorOccurred,
			this,		&KsMainWindow::_captureError);

	connect(&_captureLocalServer,	&QLocalServer::newConnection,
		this,			&KsMainWindow::_readSocket);

}

void KsMainWindow::_captureStarted()
{
	_captureLocalServer.listen("KSCapture");
}

/**
 * If the authorization could not be obtained because the user dismissed
 * the authentication dialog (clicked Cancel), pkexec exits with a return
 * value of 126.
 */
#define PKEXEC_DISMISS_RET	126

void KsMainWindow::_captureFinished(int ret, QProcess::ExitStatus st)
{
	QProcess *capture = (QProcess *)sender();

	_captureLocalServer.close();

	if (ret == PKEXEC_DISMISS_RET) {
		/*
		 * Authorization could not be obtained because the user
		 * dismissed the authentication dialog.
		 */
		return;
	}

	if (ret != 0 && st == QProcess::NormalExit)
		_captureErrorMessage(capture);
}

void KsMainWindow::_captureError(QProcess::ProcessError error)
{
	QProcess *capture = (QProcess *)sender();
	_captureErrorMessage(capture);
}

void KsMainWindow::_captureErrorMessage(QProcess *capture)
{
	QString message = "Capture process failed: ";

	message += capture->errorString();
	message += "<br>Standard Error: ";
	message += capture->readAllStandardError();
	_error(message, "captureFinishedErr", false);
}

void KsMainWindow::_readSocket()
{
	QLocalSocket *socket;
	quint32 blockSize;
	QString fileName;

	auto lamSocketError = [&](QString message)
	{
		message = "ERROR from Local Server: " + message;
		_error(message, "readSocketErr", false);
	};

	socket = _captureLocalServer.nextPendingConnection();
	if (!socket) {
		lamSocketError("Pending connectio not found!");
		return;
	}

	QDataStream in(socket);
	socket->waitForReadyRead();
	if (socket->bytesAvailable() < (int)sizeof(quint32)) {
		lamSocketError("Message size is corrupted!");
		return;
	};

	in >> blockSize;
	if (socket->bytesAvailable() < blockSize || in.atEnd()) {
		lamSocketError("Message is corrupted!");
		return;
	}

	in >> fileName;
	loadDataFile(fileName);
}

void KsMainWindow::_splitterMoved(int pos, int index)
{
	_session.saveSplitterSize(_splitter);
}

void KsMainWindow::_deselectActive()
{
	_view.clearSelection();
	_mState.activeMarker().remove();
	_mState.updateLabels();
	_graph.glPtr()->model()->update();
}

void KsMainWindow::_deselectA()
{
	if (_mState.getState() == DualMarkerState::A)
		_view.clearSelection();
	else
		_view.passiveMarkerSelectRow(KS_NO_ROW_SELECTED);

	_mState.markerA().remove();
	_mState.updateLabels();
	_graph.glPtr()->model()->update();
}

void KsMainWindow::_deselectB()
{
	if (_mState.getState() == DualMarkerState::B)
		_view.clearSelection();
	else
		_view.passiveMarkerSelectRow(KS_NO_ROW_SELECTED);

	_mState.markerB().remove();
	_mState.updateLabels();
	_graph.glPtr()->model()->update();
}

void KsMainWindow::_rootWarning()
{
	QString cbFlag("noRootWarn");

	if (_settings.value(cbFlag).toBool())
		return;

	QMessageBox warn;
	warn.setText("KernelShark is running with Root privileges.");
	warn.setInformativeText("Continue at your own risk.");
	warn.setIcon(QMessageBox::Warning);
	warn.setStandardButtons(QMessageBox::Close);

	QCheckBox cb("Don't show this message again.");

	auto lamCbChec = [&] (int state) {
		if (state)
			_settings.setValue(cbFlag, true);
	};

	connect(&cb, &QCheckBox::stateChanged, lamCbChec);
	warn.setCheckBox(&cb);
	warn.exec();
}
