// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2022 Solidigm.
 *
 * Author: leonardo.da.cunha@solidigm.com
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <libnvme.h>

#include "cleanup.h"
#include "global-ctx.h"
#include "nvme-print.h"
#include "plugin.h"
#include "solidigm-telemetry.h"
#include "solidigm-telemetry/data-area.h"
#include "solidigm-telemetry/telemetry-log.h"
#include "solidigm-util.h"

static int read_file2buffer(char *file_name, char **buffer, size_t *length)
{
	FILE *fd = fopen(file_name, "rb");

	if (!fd)
		return -errno;

	fseek(fd, 0, SEEK_END);
	size_t length_bytes = ftell(fd);

	fseek(fd, 0, SEEK_SET);

	*buffer = malloc(length_bytes);
	if (!*buffer) {
		fclose(fd);
		return -errno;
	}
	*length = fread(*buffer, 1, length_bytes, fd);
	fclose(fd);
	return 0;
}

struct config {
	__u32 host_gen;
	bool ctrl_init;
	__u8  data_area;
	char *cfg_file;
	char *binary_file;
	char *jq_filter;
};

static void cleanup_json_object(struct json_object **jobj_ptr)
{
	json_free_object(*jobj_ptr);
	*jobj_ptr = NULL;
}

int solidigm_get_telemetry_log(int argc, char **argv, struct command *acmd, struct plugin *plugin)
{
	const char *desc = "Parse Solidigm Telemetry log";
	const char *hgen = "Controls when to generate new host initiated report. Default value '1' generates new host initiated report, value '0' causes retrieval of existing log.";
	const char *cgen = "Gather report generated by the controller.";
	const char *dgen = "Pick which telemetry data area to report. Default is 3 to fetch areas 1-3. Valid options are 1, 2, 3, 4.";
	const char *cfile = "JSON configuration file";
	const char *sfile = "binary file containing log dump";
	const char *jqfilt = "JSON config entry name containing jq filter";
	bool has_binary_file = false;
	__cleanup_nvme_global_ctx struct libnvme_global_ctx *ctx = NULL;
	__cleanup_nvme_transport_handle struct libnvme_transport_handle *hdl = NULL;
	__cleanup_free struct nvme_telemetry_log *tlog = NULL;

	__attribute__((cleanup(cleanup_json_object))) struct json_object *configuration = NULL;

	__attribute__((cleanup(cleanup_json_object))) struct json_object *root =
		json_create_object();

	struct telemetry_log tl = {
		.root = root,
	};

	struct config cfg = {
		.host_gen   = 1,
		.ctrl_init  = false,
	};

	NVME_ARGS(opts,
		OPT_UINT("host-generate",   'g', &cfg.host_gen,  hgen),
		OPT_FLAG("controller-init", 'c', &cfg.ctrl_init, cgen),
		OPT_BYTE("data-area",       'd', &cfg.data_area, dgen),
		OPT_FILE("config-file",     'j', &cfg.cfg_file, cfile),
		OPT_FILE("source-file",     's', &cfg.binary_file, sfile),
		OPT_STR("jq-filter",        'q', &cfg.jq_filter, jqfilt));

	int err = parse_args(argc, argv, desc, opts);

	if (err) {
		return err;
	}
	/* When not selected on the command line, get minimum data area required */
	if (!argconfig_parse_seen(opts, "data-area"))
		cfg.data_area = argconfig_parse_seen(opts, "config-file") ? 3 : 1;

	if (cfg.data_area < 1 || cfg.data_area > 4) {
		nvme_show_error("data-area = '%d'", cfg.data_area);
		return -EINVAL;
	}

	has_binary_file = argconfig_parse_seen(opts, "source-file");
	if (has_binary_file) {
		// If a binary file is provided, we don't want to open a device.
		// GNU getopt() permutes the contents of argv as it scans,
		// so that eventually all the nonoptions are at the end.
		if (argc > optind) {
			nvme_show_error(
			"Device path not allowed when using --source-file");
			return -EINVAL;
		}
		err = read_file2buffer(cfg.binary_file, (char **)&tlog, &tl.log_size);
	} else {
		err = parse_and_open(&ctx, &hdl, argc, argv, desc, opts);
	}
	if (err < 0)
		nvme_show_err(err, "Error");
	if (err)
		return err;

	if (cfg.host_gen > 1) {
		nvme_show_error("host-generate = '%d'", cfg.host_gen);
		return -EINVAL;
	}

	if (argconfig_parse_seen(opts, "config-file")) {
		__cleanup_free char *conf_str = NULL;
		size_t length = 0;
		enum json_tokener_error jerr;

		err = read_file2buffer(cfg.cfg_file, &conf_str, &length);
		if (err) {
			nvme_show_perror("config-file %s", cfg.cfg_file);
			return err;
		}
		configuration = json_tokener_parse_verbose(conf_str, &jerr);
		if (!configuration) {
			nvme_show_error(
				"Failed to parse JSON file %s: %s",
					cfg.cfg_file,
					json_tokener_error_desc(jerr));
			return -EINVAL;
		}
		tl.configuration = configuration;
	}

	if (!has_binary_file) {
		size_t max_data_tx;
		size_t power2;
		__u8 mdts = 0;

		err = libnvme_get_telemetry_max(hdl, NULL, &max_data_tx);
		if (err) {
			nvme_show_err(err, "identify_ctrl");
			return err;
		}
		power2 = max_data_tx / NVME_LOG_PAGE_PDU_SIZE;
		while (power2 && !(1 & power2)) {
			power2 >>= 1;
			mdts++;
		}

		err = sldgm_dynamic_telemetry(hdl, cfg.host_gen, cfg.ctrl_init, true,
					      mdts, cfg.data_area, &tlog, &tl.log_size);
		if (err) {
			nvme_show_err(err, "get-telemetry-log");
			return err;
		}
	}
	tl.log = tlog;
	solidigm_telemetry_log_data_areas_parse(&tl, cfg.data_area);

	/* Check if jq filter is requested and available */
	if (cfg.jq_filter && configuration) {
		struct json_object *jq_filter_obj = NULL;

		if (json_object_object_get_ex(configuration, cfg.jq_filter,
					      &jq_filter_obj)) {
			const char *jq_filter_str;

			jq_filter_str = json_object_get_string(jq_filter_obj);
			if (jq_filter_str) {
				/* Get JSON string representation */
				const char *json_str;
				char cmd[1024];
				FILE *jq_pipe;

				json_str = json_object_to_json_string(tl.root);

				/* Create jq command and pipe JSON through it */
				snprintf(cmd, sizeof(cmd), "jq -r '%s'",
					 jq_filter_str);
				jq_pipe = popen(cmd, "w");
				if (jq_pipe) {
					fprintf(jq_pipe, "%s", json_str);
					err = pclose(jq_pipe);
					if (err != 0)
						err = -EINVAL;
				} else {
					errno = ENOENT;
					nvme_show_perror(
						"Failed to execute jq command");
					err = -ENOENT;
				}
			} else {
				nvme_show_error(
					"jq filter entry '%s' is not a valid string",
					cfg.jq_filter);
				err = -EINVAL;
			}
		} else {
			nvme_show_error(
				"jq filter entry '%s' not found in configuration file",
				cfg.jq_filter);
			err = -ENOENT;
		}
	} else {
		/*
		 * No jq filter requested or no config file,
		 * use normal JSON output
		 */
		json_print_object(tl.root, NULL);
	}
	printf("\n");

	return err;
}
