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

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

#include "common.h"
#include "nvme.h"
#include "libnvme.h"
#include "plugin.h"
#include "nvme-print.h"
#include "solidigm-telemetry.h"
#include "solidigm-telemetry/telemetry-log.h"
#include "solidigm-telemetry/cod.h"
#include "solidigm-telemetry/header.h"
#include "solidigm-telemetry/config.h"
#include "solidigm-telemetry/data-area.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;
	int  data_area;
	char *cfg_file;
	bool is_input_file;
};

int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, 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 = "data source <device> is binary file containing log dump instead of block or character device";
	struct nvme_dev *dev;

	struct telemetry_log tl = {
		.root = json_create_object(),
		.log = NULL,
	};

	struct config cfg = {
		.host_gen   = 1,
		.ctrl_init  = false,
		.data_area  = -1,
		.cfg_file   = NULL,
		.is_input_file = false,
	};

	OPT_ARGS(opts) = {
		OPT_UINT("host-generate",   'g', &cfg.host_gen,  hgen),
		OPT_FLAG("controller-init", 'c', &cfg.ctrl_init, cgen),
		OPT_UINT("data-area",       'd', &cfg.data_area, dgen),
		OPT_FILE("config-file",     'j', &cfg.cfg_file, cfile),
		OPT_FLAG("source-file",     's', &cfg.is_input_file, sfile),
		OPT_END()
	};

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

	if (err)
		goto ret;

	/* When not selected on the command line, get minimum data area required */
	if (cfg.data_area == -1)
		cfg.data_area =  cfg.cfg_file ? 3 : 1;

	if (cfg.is_input_file) {
		if (optind >= argc) {
			err = errno = EINVAL;
			perror(argv[0]);
			goto ret;
		}
		char *binary_file_name = argv[optind];

		err = read_file2buffer(binary_file_name, (char **)&tl.log, &tl.log_size);
	} else {
		err = parse_and_open(&dev, argc, argv, desc, opts);
	}
	if (err)
		goto ret;

	if (cfg.host_gen > 1) {
		SOLIDIGM_LOG_WARNING("Invalid host-generate value '%d'", cfg.host_gen);
		err = EINVAL;
		goto close_fd;
	}

	if (cfg.cfg_file) {
		char *conf_str = NULL;
		size_t length = 0;

		err = read_file2buffer(cfg.cfg_file, &conf_str, &length);
		if (err) {
			SOLIDIGM_LOG_WARNING("Failed to open JSON configuration file %s: %s!",
				cfg.cfg_file, strerror(err));
			goto close_fd;
		}
		struct json_tokener *jstok = json_tokener_new();

		tl.configuration = json_tokener_parse_ex(jstok, conf_str, length);
		free(conf_str);
		if (jstok->err != json_tokener_success)	{
			SOLIDIGM_LOG_WARNING("Parsing error on JSON configuration file %s: %s (at offset %d)",
					     cfg.cfg_file,
					     json_tokener_error_desc(jstok->err),
					     jstok->char_offset);
			json_tokener_free(jstok);
			err = EINVAL;
			goto close_fd;
		}
		json_tokener_free(jstok);
	}

	if (!cfg.is_input_file) {
		size_t max_data_tx;
		size_t power2;
		__u8 mdts = 0;

		err = nvme_get_telemetry_max(dev_fd(dev), NULL, &max_data_tx);
		if (err < 0) {
			SOLIDIGM_LOG_WARNING("identify_ctrl: %s",
					     nvme_strerror(errno));
			goto close_fd;
		} else if (err > 0) {
			nvme_show_status(err);
			SOLIDIGM_LOG_WARNING("Failed to acquire identify ctrl %d!", err);
			goto close_fd;
		}
		power2 = max_data_tx / NVME_LOG_PAGE_PDU_SIZE;
		while (power2 && !(1 & power2)) {
			power2 >>= 1;
			mdts++;
		}

		err = sldgm_dynamic_telemetry(dev_fd(dev), cfg.host_gen, cfg.ctrl_init, true,
					      mdts, cfg.data_area, &tl.log, &tl.log_size);
		if (err < 0) {
			SOLIDIGM_LOG_WARNING("get-telemetry-log: %s",
					     nvme_strerror(errno));
			goto close_fd;
		} else if (err > 0) {
			nvme_show_status(err);
			SOLIDIGM_LOG_WARNING("Failed to acquire telemetry log %d!", err);
			goto close_fd;
		}
	}
	solidigm_telemetry_log_data_areas_parse(&tl, cfg.data_area);

	json_print_object(tl.root, NULL);
	json_free_object(tl.root);
	printf("\n");

close_fd:
	if (!cfg.is_input_file) {
		/* Redundant close() to make static code analysis happy */
		close(dev->direct.fd);
		dev_close(dev);
	}
ret:
	json_free_object(tl.configuration);
	free(tl.log);
	return err;
}
