// SPDX-License-Identifier: LGPL-2.1

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

/**
 *  @file    KsSession.cpp
 *  @brief   KernelShark Session.
 */

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

/** Create a KsSession object. */
KsSession::KsSession()
{
	_config = kshark_session_config_new(KS_CONFIG_JSON);
}

/** Destroy a KsSession object. */
KsSession::~KsSession()
{
	kshark_free_config_doc(_config);
}

/** Import a user session from a Json file. */
bool KsSession::importFromFile(QString jfileName)
{
	kshark_config_doc *configTmp =
		kshark_open_config_file(jfileName.toStdString().c_str(),
					"kshark.config.session");

	if (configTmp) {
		kshark_free_config_doc(_config);
		_config = configTmp;
		return true;
	}

	return false;
}

/** Export the current user session from a Json file. */
void KsSession::exportToFile(QString jfileName)
{
	kshark_save_config_file(jfileName.toStdString().c_str(), _config);
}

/**
 * @brief Save the state of the visualization model.
 *
 * @param histo: Input location for the model descriptor.
 */
void KsSession::saveVisModel(kshark_trace_histo *histo)
{
	kshark_config_doc *model =
		kshark_export_model(histo, KS_CONFIG_JSON);

	kshark_config_doc_add(_config, "Model", model);
}

/**
 * @brief Load the state of the visualization model.
 *
 * @param model: Input location for the KsGraphModel object.
 */
void KsSession::loadVisModel(KsGraphModel *model)
{
	kshark_config_doc *modelConf = kshark_config_alloc(KS_CONFIG_JSON);

	if (!kshark_config_doc_get(_config, "Model", modelConf))
		return;

	kshark_import_model(model->histo(), modelConf);
	model->update();
}

/** Save the trace data file. */
void KsSession::saveDataFile(QString fileName, QString dataSetName)
{
	kshark_config_doc *file =
		kshark_export_trace_file(fileName.toStdString().c_str(),
					 dataSetName.toStdString().c_str(),
					 KS_CONFIG_JSON);

	kshark_config_doc_add(_config, "Data", file);
}

/** Get the trace data file. */
QString KsSession::getDataFile(kshark_context *kshark_ctx)
{
	kshark_config_doc *file = kshark_config_alloc(KS_CONFIG_JSON);
	int sd;

	if (!kshark_config_doc_get(_config, "Data", file))
		return QString();

	sd = kshark_import_trace_file(kshark_ctx, file);
	if (sd)
		return QString(kshark_ctx->stream[sd]->file);

	return QString();
}

/**
 * @brief Save the configuration information for all load Data streams.
 *
 * @param kshark_ctx: Input location for context pointer.
 */
void KsSession::saveDataStreams(kshark_context *kshark_ctx)
{
	kshark_export_all_dstreams(kshark_ctx, &_config);
}

/**
 * @brief Load Data streams.
 *
 * @param kshark_ctx: Input location for context pointer.
 * @param data:  Input location for KsDataStore object;
 */
void KsSession::loadDataStreams(kshark_context *kshark_ctx,
				KsDataStore *data)
{
	ssize_t dataSize;

	data->unregisterCPUCollections();

	kshark_close_all(kshark_ctx);
	dataSize = kshark_import_all_dstreams(kshark_ctx,
					      _config,
					      data->rows_r());
	if (dataSize < 0) {
		data->clear();
		return;
	}

	data->setSize(dataSize);
	data->registerCPUCollections();
}

/**
 * @brief Save the state of the table.
 *
 * @param view: Input location for the KsTraceViewer widget.
 */
void KsSession::saveTable(const KsTraceViewer &view) {
	kshark_config_doc *topRow = kshark_config_alloc(KS_CONFIG_JSON);
	int64_t r = view.getTopRow();

	topRow->conf_doc = json_object_new_int64(r);
	kshark_config_doc_add(_config, "ViewTop", topRow);
}

/**
 * @brief Load the state of the table.
 *
 * @param view: Input location for the KsTraceViewer widget.
 */
void KsSession::loadTable(KsTraceViewer *view) {
	kshark_config_doc *topRow = kshark_config_alloc(KS_CONFIG_JSON);
	size_t r = 0;

	if (!kshark_config_doc_get(_config, "ViewTop", topRow))
		return;

	if (_config->format == KS_CONFIG_JSON)
		r = json_object_get_int64(KS_JSON_CAST(topRow->conf_doc));

	view->setTopRow(r);
}

/**
 * @brief Save the KernelShark Main window size.
 *
 * @param window: Input location for the KsMainWindow widget.
 */
void KsSession::saveMainWindowSize(const QMainWindow &window)
{
	kshark_config_doc *windowConf = kshark_config_alloc(KS_CONFIG_JSON);
	int width = window.width(), height = window.height();
	json_object *jwindow;

	if (window.isFullScreen()) {
		jwindow = json_object_new_string("FullScreen");
	} else {
		jwindow = json_object_new_array();
		json_object_array_put_idx(jwindow, 0, json_object_new_int(width));
		json_object_array_put_idx(jwindow, 1, json_object_new_int(height));
	}

	windowConf->conf_doc = jwindow;
	kshark_config_doc_add(_config, "MainWindow", windowConf);
}

/**
 * @brief Load the KernelShark Main window size.
 *
 * @param window: Input location for the KsMainWindow widget.
 */
void KsSession::loadMainWindowSize(KsMainWindow *window)
{
	kshark_config_doc *windowConf = kshark_config_alloc(KS_CONFIG_JSON);
	json_object *jwindow, *jwidth, *jheight;
	int width, height;

	if (!kshark_config_doc_get(_config, "MainWindow", windowConf))
		return;

	if (_config->format == KS_CONFIG_JSON) {
		jwindow = KS_JSON_CAST(windowConf->conf_doc);
		if (json_object_get_type(jwindow) == json_type_string &&
		    QString(json_object_get_string(jwindow)) == "FullScreen") {
			window->setFullScreenMode(true);
			return;
		}

		jwidth = json_object_array_get_idx(jwindow, 0);
		jheight = json_object_array_get_idx(jwindow, 1);

		width = json_object_get_int(jwidth);
		height = json_object_get_int(jheight);

		window->setFullScreenMode(false);
		window->resize(width, height);
	}
}

/**
 * @brief Save the state of the Main window spliter.
 *
 * @param splitter: Input location for the splitter widget.
 */
void KsSession::saveSplitterSize(const QSplitter &splitter)
{
	kshark_config_doc *spl = kshark_config_alloc(KS_CONFIG_JSON);
	json_object *jspl = json_object_new_array();
	QList<int> sizes = splitter.sizes();

	json_object_array_put_idx(jspl, 0, json_object_new_int(sizes[0]));
	json_object_array_put_idx(jspl, 1, json_object_new_int(sizes[1]));

	spl->conf_doc = jspl;
	kshark_config_doc_add(_config, "Splitter", spl);
}

/**
 * @brief Load the state of the Main window spliter.
 *
 * @param splitter: Input location for the splitter widget.
 */
void KsSession::loadSplitterSize(QSplitter *splitter)
{
	kshark_config_doc *spl = kshark_config_alloc(KS_CONFIG_JSON);
	json_object *jspl, *jgraphsize, *jviewsize;
	int graphSize(1), viewSize(1);
	QList<int> sizes;

	if (!kshark_config_doc_get(_config, "Splitter", spl))
		return;

	if (_config->format == KS_CONFIG_JSON) {
		jspl = KS_JSON_CAST(spl->conf_doc);
		jgraphsize = json_object_array_get_idx(jspl, 0);
		jviewsize = json_object_array_get_idx(jspl, 1);

		graphSize = json_object_get_int(jgraphsize);
		viewSize = json_object_get_int(jviewsize);

		if (graphSize == 0 && viewSize == 0) {
			/* 0/0 spliter ratio is undefined. Make it 1/1. */
			viewSize = graphSize = 1;
		}
	}

	sizes << graphSize << viewSize;
	splitter->setSizes(sizes);
}

/** @brief Save the Color scheme used. */
void KsSession::saveColorScheme() {
	kshark_config_doc *colSch = kshark_config_alloc(KS_CONFIG_JSON);
	double s = KsPlot::Color::rainbowFrequency();

	colSch->conf_doc = json_object_new_double(s);
	kshark_config_doc_add(_config, "ColorScheme", colSch);
}

/** @brief Get the Color scheme used. */
float KsSession::getColorScheme() {
	kshark_config_doc *colSch = kshark_config_alloc(KS_CONFIG_JSON);

	/* Default color scheme. */
	float s = 0.75;

	if (!kshark_config_doc_get(_config, "ColorScheme", colSch))
		return s;

	if (_config->format == KS_CONFIG_JSON)
		s = json_object_get_double(KS_JSON_CAST(colSch->conf_doc));

	return s;
}

/**
 * @brief Save the list of the graphs plotted.
 *
 * @param kshark_ctx: Input location for context pointer.
 * @param graphs: Input location for the KsTraceGraph widget..
 */
void KsSession::saveGraphs(kshark_context *kshark_ctx,
			   KsTraceGraph &graphs)
{
	QVector<int> streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		_saveCPUPlots(sd, graphs.glPtr());
		_saveTaskPlots(sd, graphs.glPtr());
	}

	_saveComboPlots(graphs.glPtr());
}

/**
 * @brief Load the list of the graphs and plot.
 *
 * @param kshark_ctx: Input location for context pointer.
 * @param graphs: Input location for the KsTraceGraph widget.
 */
void KsSession::loadGraphs(kshark_context *kshark_ctx,
			   KsTraceGraph &graphs)
{
	QVector<int> combos, streamIds;
	int nCombos;

	streamIds = KsUtils::getStreamIdList(kshark_ctx);
	for (auto const &sd: streamIds) {
		graphs.cpuReDraw(sd, _getCPUPlots(sd));
		graphs.taskReDraw(sd, _getTaskPlots(sd));
	}

	combos = _getComboPlots(&nCombos);
	if (nCombos > 0)
		graphs.comboReDraw(nCombos, combos);
}

void KsSession::_savePlots(int sd, KsGLWidget *glw, bool cpu)
{
	kshark_config_doc *streamsConf = kshark_config_alloc(KS_CONFIG_JSON);
	json_object *jallStreams, *jstream, *jstreamId, *jplots;
	QVector<int> plotIds;
	int nStreams;

	if (cpu) {
		plotIds = glw->_streamPlots[sd]._cpuList;
	} else {
		plotIds = glw->_streamPlots[sd]._taskList;
	}

	if (!kshark_config_doc_get(_config, "data streams", streamsConf) ||
	    streamsConf->format != KS_CONFIG_JSON)
		return;

	jallStreams = KS_JSON_CAST(streamsConf->conf_doc);
	if (json_object_get_type(jallStreams) != json_type_array)
		return;

	nStreams = json_object_array_length(jallStreams);
	for (int i = 0; i < nStreams; ++i) {
		jstream = json_object_array_get_idx(jallStreams, i);
		if (json_object_object_get_ex(jstream, "stream id", &jstreamId) &&
		    json_object_get_int(jstreamId) == sd)
			break;

		jstream = nullptr;
	}

	free(streamsConf);
	if (!jstream)
		return;

	jplots = json_object_new_array();
	for (int i = 0; i < plotIds.count(); ++i) {
		json_object *jcpu = json_object_new_int(plotIds[i]);
		json_object_array_put_idx(jplots, i, jcpu);
	}

	if (cpu)
		json_object_object_add(jstream, "CPUPlots", jplots);
	else
		json_object_object_add(jstream, "TaskPlots", jplots);
}

void KsSession::_saveCPUPlots(int sd, KsGLWidget *glw)
{
	_savePlots(sd, glw, true);
}

void KsSession::_saveTaskPlots(int sd, KsGLWidget *glw)
{
	_savePlots(sd, glw, false);
}

void KsSession::_saveComboPlots(KsGLWidget *glw)
{
	kshark_config_doc *combos = kshark_config_alloc(KS_CONFIG_JSON);
	json_object *jcombos, *jplots, *jplt;
	int var;

	jcombos = json_object_new_array();
	for (auto const &c: glw->_comboPlots) {
		jplots = json_object_new_array();
		for (auto const &p: c) {
			jplt = json_object_new_array();
			var = p._streamId;
			json_object_array_put_idx(jplt, 0,
						  json_object_new_int(var));

			var = p._type;
			json_object_array_put_idx(jplt, 1,
						  json_object_new_int(var));

			var = p._id;
			json_object_array_put_idx(jplt, 2,
						  json_object_new_int(var));

			json_object_array_add(jplots, jplt);
		}

		json_object_array_add(jcombos, jplots);
	}

	combos->conf_doc = jcombos;
	kshark_config_doc_add(_config, "ComboPlots", combos);
}

QVector<int> KsSession::_getPlots(int sd, bool cpu)
{
	kshark_config_doc *streamsConf = kshark_config_alloc(KS_CONFIG_JSON);
	json_object *jallStreams, *jstream, *jstreamId, *jplots;
	int nStreams, nPlots, id;
	const char *plotKey;
	QVector<int> plots;

	if (!kshark_config_doc_get(_config, "data streams", streamsConf) ||
	    streamsConf->format != KS_CONFIG_JSON)
		return plots;

	jallStreams = KS_JSON_CAST(streamsConf->conf_doc);
	if (json_object_get_type(jallStreams) != json_type_array)
		return plots;

	nStreams = json_object_array_length(jallStreams);
	for (int i = 0; i < nStreams; ++i) {
		jstream = json_object_array_get_idx(jallStreams, i);
		if (json_object_object_get_ex(jstream, "stream id", &jstreamId) &&
		    json_object_get_int(jstreamId) == sd)
			break;

		jstream = nullptr;
	}

	if (!jstream)
		return plots;

	if (cpu)
		plotKey = "CPUPlots";
	else
		plotKey = "TaskPlots";

	if (!json_object_object_get_ex(jstream, plotKey, &jplots) ||
	    json_object_get_type(jplots) != json_type_array)
		return plots;

	nPlots = json_object_array_length(jplots);
	for (int i = 0; i < nPlots; ++i) {
		id = json_object_get_int(json_object_array_get_idx(jplots, i));
		plots.append(id);
	}

	return plots;
}

QVector<int> KsSession::_getCPUPlots(int sd)
{
	return _getPlots(sd, true);
}

QVector<int> KsSession::_getTaskPlots(int sd)
{
	return _getPlots(sd, false);
}

QVector<int> KsSession::_getComboPlots(int *n)
{
	kshark_config_doc *combos = kshark_config_alloc(KS_CONFIG_JSON);
	int nCombos, nPlots, sd, type, id;
	json_object *jcombos, *jplots, *jplt, *jvar;
	QVector<int> vec;

	*n = 0;
	if (!kshark_config_doc_get(_config, "ComboPlots", combos))
		return {};

	if (_config->format == KS_CONFIG_JSON) {
		jcombos = KS_JSON_CAST(combos->conf_doc);
		if (json_object_get_type(jcombos) != json_type_array)
			return {};

		*n = nCombos = json_object_array_length(jcombos);
		for (int i = 0; i < nCombos; ++i) {
			jplots = json_object_array_get_idx(jcombos, i);
			if (json_object_get_type(jplots) != json_type_array)
				return {};

			nPlots =  json_object_array_length(jplots);
			vec.append(nPlots);
			for (int j = 0; j < nPlots; ++j) {
				jplt = json_object_array_get_idx(jplots, j);
				if (json_object_get_type(jplt) != json_type_array)
					return {};

				jvar = json_object_array_get_idx(jplt, 0);
				sd = json_object_get_int(jvar);

				jvar = json_object_array_get_idx(jplt, 1);
				type = json_object_get_int(jvar);

				jvar = json_object_array_get_idx(jplt, 2);
				id = json_object_get_int(jvar);

				vec.append({sd, type, id});
			}
		}
	}

	return vec;
}

/**
 * @brief Save the state of the Dual marker.
 *
 * @param dm: Input location for the KsDualMarkerSM object.
 */
void KsSession::saveDualMarker(KsDualMarkerSM *dm)
{
	kshark_config_doc *markers =
		kshark_config_new("kshark.config.markers", KS_CONFIG_JSON);
	json_object *jd_mark = KS_JSON_CAST(markers->conf_doc);

	auto save_mark = [&jd_mark] (KsGraphMark *m, const char *name)
	{
		json_object *jmark = json_object_new_object();

		if (m->_isSet) {
			json_object_object_add(jmark, "isSet",
					       json_object_new_boolean(true));

			json_object_object_add(jmark, "row",
					       json_object_new_int(m->_pos));
		} else {
			json_object_object_add(jmark, "isSet",
					       json_object_new_boolean(false));
		}

		json_object_object_add(jd_mark, name, jmark);
	};

	save_mark(&dm->markerA(), "markA");
	save_mark(&dm->markerB(), "markB");

	if (dm->getState() == DualMarkerState::A)
		json_object_object_add(jd_mark, "Active",
				       json_object_new_string("A"));
	else
		json_object_object_add(jd_mark, "Active",
				       json_object_new_string("B"));

	kshark_config_doc_add(_config, "Markers", markers);
}

/**
 * @brief Load the state of the Dual marker.
 *
 * @param dm: Input location for the KsDualMarkerSM object.
 * @param graphs: Input location for the KsTraceGraph widget.
 */
void KsSession::loadDualMarker(KsDualMarkerSM *dm, KsTraceGraph *graphs)
{
	size_t pos;

	dm->reset();
	dm->setState(DualMarkerState::A);
	if (_getMarker("markA", &pos)) {
		graphs->markEntry(pos);
	} else {
		dm->markerA().remove();
	}

	dm->setState(DualMarkerState::B);
	if (_getMarker("markB", &pos)) {
		graphs->markEntry(pos);
	} else {
		dm->markerB().remove();
	}

	dm->setState(_getMarkerState());
	if (dm->activeMarker()._isSet) {
		pos = dm->activeMarker()._pos;
		emit graphs->glPtr()->updateView(pos, true);
	}
}

json_object *KsSession::_getMarkerJson()
{
	kshark_config_doc *markers =
		kshark_config_alloc(KS_CONFIG_JSON);

	if (!kshark_config_doc_get(_config, "Markers", markers) ||
	    !kshark_type_check(markers, "kshark.config.markers"))
		return nullptr;

	return KS_JSON_CAST(markers->conf_doc);
}

bool KsSession::_getMarker(const char* name, size_t *pos)
{
	json_object *jd_mark, *jmark;

	*pos = 0;
	jd_mark = _getMarkerJson();
	if (!jd_mark)
		return false;

	if (json_object_object_get_ex(jd_mark, name, &jmark)) {
		json_object *jis_set;
		json_object_object_get_ex(jmark, "isSet", &jis_set);
		if (!json_object_get_boolean(jis_set))
			return false;

		json_object *jpos;
		json_object_object_get_ex(jmark, "row", &jpos);
		*pos = json_object_get_int64(jpos);
	}

	return true;
}

DualMarkerState KsSession::_getMarkerState()
{
	json_object *jd_mark, *jstate;
	const char* state;

	jd_mark = _getMarkerJson();
	json_object_object_get_ex(jd_mark, "Active", &jstate);
	state = json_object_get_string(jstate);

	if (strcmp(state, "A") == 0)
		return DualMarkerState::A;

	return DualMarkerState::B;
}

/**
 * @brief Save the configuration of the plugins.
 *
 * @param pm: Input location for the KsPluginManager object.
 */
void KsSession::saveUserPlugins(const KsPluginManager &pm)
{
	kshark_config_doc *plugins =
		kshark_config_new("kshark.config.plugins", KS_CONFIG_JSON);
	json_object *jplugins = KS_JSON_CAST(plugins->conf_doc);
	json_object *jplg, *jlist = json_object_new_array();

	for (auto const p: pm.getUserPlugins()) {
		kshark_config_doc *lib =
			kshark_export_plugin_file(p, KS_CONFIG_JSON);
		jplg = KS_JSON_CAST(lib->conf_doc);
		json_object_array_add(jlist, jplg);
		free(lib);
	}

	json_object_object_add(jplugins, "obj. files", jlist);
	kshark_config_doc_add(_config, "User Plugins", plugins);
}

/**
 * @brief Load the configuration of the plugins.
 *
 * @param kshark_ctx: Input location for context pointer.
 * @param pm: Input location for the KsPluginManager object.
 */
void KsSession::loadUserPlugins(kshark_context *kshark_ctx, KsPluginManager *pm)
{
	kshark_config_doc *plugins =
		kshark_config_alloc(KS_CONFIG_JSON);
	kshark_plugin_list *list, **last;

	if (!kshark_config_doc_get(_config, "User Plugins", plugins))
		return;

	/* Get the list of already loaded plugins. */
	list = kshark_ctx->plugins;

	kshark_import_all_plugins(kshark_ctx, plugins);

	/* Loop until the beginning of the old list. */
	for (last = &kshark_ctx->plugins; *last != list; last = &(*last)->next)
		pm->addUserPluginToList(*last);
}
