/*****************************************************************************\
 *  openapi.c - OpenAPI helpers
 *****************************************************************************
 *  Copyright (C) SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include "src/common/data.h"
#include "src/common/http.h"
#include "src/common/openapi.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#define MAGIC_FOREACH_PATH 0xaba1aaab

/*
 * Based on
 * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#data-types
 */
static const struct {
	openapi_type_t type;
	openapi_type_format_t format;
	char *str_type;
	char *str_format;
	data_type_t data_type; /* compatible data type for this field */
} openapi_types[] = {
	{ OPENAPI_TYPE_INTEGER, OPENAPI_FORMAT_INT, "integer", NULL,
	  DATA_TYPE_INT_64 },
	{ OPENAPI_TYPE_INTEGER, OPENAPI_FORMAT_INT32, "integer", "int32",
	  DATA_TYPE_INT_64 },
	{ OPENAPI_TYPE_INTEGER, OPENAPI_FORMAT_INT64, "integer", "int64",
	  DATA_TYPE_INT_64 },
	{ OPENAPI_TYPE_NUMBER, OPENAPI_FORMAT_NUMBER, "number", NULL,
	  DATA_TYPE_FLOAT },
	{ OPENAPI_TYPE_NUMBER, OPENAPI_FORMAT_FLOAT, "number", "float",
	  DATA_TYPE_FLOAT },
	{ OPENAPI_TYPE_NUMBER, OPENAPI_FORMAT_DOUBLE, "number", "double",
	  DATA_TYPE_FLOAT },
	{ OPENAPI_TYPE_STRING, OPENAPI_FORMAT_STRING, "string", NULL,
	  DATA_TYPE_STRING },
	{ OPENAPI_TYPE_STRING, OPENAPI_FORMAT_PASSWORD, "string", "password",
	  DATA_TYPE_STRING },
	{ OPENAPI_TYPE_BOOL, OPENAPI_FORMAT_BOOL, "boolean", NULL,
	  DATA_TYPE_BOOL },
	{ OPENAPI_TYPE_OBJECT, OPENAPI_FORMAT_OBJECT, "object", NULL,
	  DATA_TYPE_DICT },
	{ OPENAPI_TYPE_ARRAY, OPENAPI_FORMAT_ARRAY, "array", NULL,
	  DATA_TYPE_LIST },
};

typedef struct {
	int magic; /* MAGIC_FOREACH_PATH */
	char *path;
	char *at;
} merge_path_strings_t;

#define APPEND_REL_PATH_ARGS_MAGIC 0xa33bafaf

typedef struct {
	int magic; /* APPEND_REL_PATH_ARGS_MAGIC */
	data_t *dst;
} append_rel_path_args_t;

extern const char *openapi_type_format_to_format_string(
	openapi_type_format_t format)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (openapi_types[i].format == format)
			return openapi_types[i].str_format;

	return NULL;
}

extern const char *openapi_type_format_to_type_string(
	openapi_type_format_t format)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (openapi_types[i].format == format)
			return openapi_types[i].str_type;

	return NULL;
}

extern const char *openapi_type_to_string(openapi_type_t type)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (openapi_types[i].type == type)
			return openapi_types[i].str_type;

	return NULL;
}

extern openapi_type_t openapi_string_to_type(const char *str)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (!xstrcasecmp(openapi_types[i].str_type, str))
			return openapi_types[i].type;

	return OPENAPI_TYPE_INVALID;
}

extern openapi_type_format_t openapi_string_to_type_format(const char *str)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (!xstrcasecmp(openapi_types[i].str_format, str))
			return openapi_types[i].format;

	return OPENAPI_FORMAT_INVALID;
}

extern data_type_t openapi_type_format_to_data_type(
	openapi_type_format_t format)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (openapi_types[i].format == format)
			return openapi_types[i].data_type;

	return DATA_TYPE_NONE;
}

extern openapi_type_format_t openapi_data_type_to_type_format(data_type_t type)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (openapi_types[i].data_type == type)
			return openapi_types[i].format;

	return OPENAPI_FORMAT_INVALID;
}

extern openapi_type_t openapi_type_format_to_type(openapi_type_format_t format)
{
	for (int i = 0; i < ARRAY_SIZE(openapi_types); i++)
		if (openapi_types[i].format == format)
			return openapi_types[i].type;

	return OPENAPI_TYPE_INVALID;
}

static data_for_each_cmd_t _foreach_join_path_str(data_t *data, void *arg)
{
	merge_path_strings_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_PATH);

	if (data_convert_type(data, DATA_TYPE_STRING) != DATA_TYPE_STRING)
		fatal_abort("%s: path must be a string", __func__);

	/* path entry must not contain any of the separators */
	xassert(!xstrstr(data_get_string(data), OPENAPI_PATH_SEP));
	xassert(!xstrstr(data_get_string(data), OPENAPI_PATH_REL));

	xstrfmtcatat(args->path, &args->at, "%s%s",
		     data_get_string(data), OPENAPI_PATH_SEP);

	return DATA_FOR_EACH_CONT;
}

extern char *openapi_fmt_rel_path_str(char **str_ptr, data_t *relative_path)
{
	merge_path_strings_t args = {
		.magic = MAGIC_FOREACH_PATH,
	};

	xassert(data_get_type(relative_path) == DATA_TYPE_LIST);
	if (data_get_type(relative_path) != DATA_TYPE_LIST)
		return NULL;

	/* path always starts with "#/" */
	xstrfmtcatat(args.path, &args.at, "%s%s",
		     OPENAPI_PATH_REL, OPENAPI_PATH_SEP);

	(void) data_list_for_each(relative_path, _foreach_join_path_str, &args);

	if (*str_ptr)
		xfree(*str_ptr);
	*str_ptr = args.path;

	return args.path;
}

extern data_t *openapi_fork_rel_path_list(data_t *relative_path, int index)
{
	data_t *ppath, *ppath_last;

	ppath = data_copy(NULL, relative_path);
	ppath_last = data_get_list_last(ppath);

	/* Use jq style array zero based array notation */
	data_set_string_fmt(ppath_last, "%s[%d]",
			    data_get_string(ppath_last), index);

	return ppath;
}

static int _on_append_rel_path(const char *entry, bool template, void *arg)
{
	append_rel_path_args_t *args = arg;

	xassert(args->magic == APPEND_REL_PATH_ARGS_MAGIC);
	xassert(!template);

	(void) data_set_string(data_list_append(args->dst), entry);
	return SLURM_SUCCESS;
}

extern int openapi_append_rel_path(data_t *relative_path, const char *sub_path)
{
	append_rel_path_args_t args = {
		.magic = APPEND_REL_PATH_ARGS_MAGIC,
		.dst = relative_path,
	};

	if (data_get_type(relative_path) != DATA_TYPE_LIST)
		return ESLURM_DATA_EXPECTED_LIST;

	/* ignore empty sub paths */
	if (!sub_path || !sub_path[0])
		return SLURM_SUCCESS;

	/* If string starts with # then just ignore it */
	if (sub_path[0] == OPENAPI_PATH_REL[0])
		sub_path = &sub_path[1];

	return url_path_walk(sub_path, false, _on_append_rel_path, &args);
}

extern int openapi_error_log_foreach(void *x, void *arg)
{
	openapi_resp_error_t *error_ptr = x;
	xassert(error_ptr);

	error("source: %s, description: %s, rc: %d", error_ptr->source,
	      error_ptr->description, error_ptr->num);

	return SLURM_SUCCESS;
}

extern int openapi_warn_log_foreach(void *x, void *arg)
{
	openapi_resp_warning_t *warn = x;
	xassert(warn);

	warning("source: %s, description: %s", warn->source, warn->description);

	return SLURM_SUCCESS;
}

extern void free_openapi_resp_meta(void *obj)
{
	openapi_resp_meta_t *x = obj;

	if (!obj)
		return;

	xfree(x->client.source);
	xfree(x->command);
	xfree(x->plugin.accounting_storage);
	xfree(x->plugin.data_parser);
	xfree(x->plugin.name);
	xfree(x->plugin.type);
	xfree(x->slurm.cluster);
	xfree(x->slurm.release);
	xfree(x->slurm.version.major);
	xfree(x->slurm.version.micro);
	xfree(x->slurm.version.minor);
	xfree(x);
}

extern void free_openapi_resp_error(void *obj)
{
	openapi_resp_error_t *x = obj;

	if (!obj)
		return;

	xfree(x->description);
	xfree(x->source);
	xfree(x);
}

extern void free_openapi_resp_warning(void *obj)
{
	openapi_resp_warning_t *x = obj;

	if (!obj)
		return;

	xfree(x->description);
	xfree(x->source);
	xfree(x);
}
