// SPDX-License-Identifier: GPL-2.0-or-later
/*********************************************************************
 * Copyright 2022 Hiroki Shirokura, LINE Corporation
 * Copyright 2022 Masakazu Asama
 * Copyright 2022 6WIND S.A.
 *
 * flex_algo.c: Flexible Algorithm library
 *
 * Authors
 * -------
 * Hiroki Shirokura
 * Masakazu Asama
 * Louis Scalbert
 */

#include "zebra.h"

#include "flex_algo.h"

DEFINE_MTYPE_STATIC(LIB, FLEX_ALGO_DATABASE, "Flex-Algo database");
DEFINE_MTYPE_STATIC(LIB, FLEX_ALGO, "Flex-Algo algorithm information");

static void _flex_algo_delete(struct flex_algos *flex_algos,
			      struct flex_algo *fa);

struct flex_algos *flex_algos_alloc(flex_algo_allocator_t allocator,
				    flex_algo_releaser_t releaser)
{
	struct flex_algos *flex_algos;

	flex_algos =
		XCALLOC(MTYPE_FLEX_ALGO_DATABASE, sizeof(struct flex_algos));
	flex_algos->flex_algos = list_new();
	flex_algos->allocator = allocator;
	flex_algos->releaser = releaser;
	return flex_algos;
}

void flex_algos_free(struct flex_algos *flex_algos)
{
	struct listnode *node, *nnode;
	struct flex_algo *fa;

	for (ALL_LIST_ELEMENTS(flex_algos->flex_algos, node, nnode, fa))
		_flex_algo_delete(flex_algos, fa);
	list_delete(&flex_algos->flex_algos);
	XFREE(MTYPE_FLEX_ALGO_DATABASE, flex_algos);
}

struct flex_algo *flex_algo_alloc(struct flex_algos *flex_algos,
				  uint8_t algorithm, void *arg)
{
	struct flex_algo *fa;

	fa = XCALLOC(MTYPE_FLEX_ALGO, sizeof(struct flex_algo));
	fa->algorithm = algorithm;
	if (flex_algos->allocator)
		fa->data = flex_algos->allocator(arg);
	admin_group_init(&fa->admin_group_exclude_any);
	admin_group_init(&fa->admin_group_include_any);
	admin_group_init(&fa->admin_group_include_all);
	listnode_add(flex_algos->flex_algos, fa);
	return fa;
}

static void _flex_algo_delete(struct flex_algos *flex_algos,
			      struct flex_algo *fa)
{
	if (flex_algos->releaser)
		flex_algos->releaser(fa->data);
	admin_group_term(&fa->admin_group_exclude_any);
	admin_group_term(&fa->admin_group_include_any);
	admin_group_term(&fa->admin_group_include_all);
	listnode_delete(flex_algos->flex_algos, fa);
	XFREE(MTYPE_FLEX_ALGO, fa);
}


void flex_algo_delete(struct flex_algos *flex_algos, uint8_t algorithm)
{
	struct listnode *node, *nnode;
	struct flex_algo *fa;

	for (ALL_LIST_ELEMENTS(flex_algos->flex_algos, node, nnode, fa)) {
		if (fa->algorithm != algorithm)
			continue;
		_flex_algo_delete(flex_algos, fa);
	}
}

/**
 * @brief Look up the local flex-algo object by its algorithm number.
 * @param algorithm flex-algo algorithm number
 * @param area area pointer of flex-algo
 * @return local flex-algo object if exist, else NULL
 */
struct flex_algo *flex_algo_lookup(struct flex_algos *flex_algos,
				   uint8_t algorithm)
{
	struct listnode *node;
	struct flex_algo *fa;

	for (ALL_LIST_ELEMENTS_RO(flex_algos->flex_algos, node, fa))
		if (fa->algorithm == algorithm)
			return fa;
	return NULL;
}

/**
 * @brief Compare two Flex-Algo Definitions (FAD)
 * @param Flex algo 1
 * @param Flex algo 2
 * @return true if the definition is equal, else false
 */
bool flex_algo_definition_cmp(struct flex_algo *fa1, struct flex_algo *fa2)
{
	if (fa1->algorithm != fa2->algorithm)
		return false;
	if (fa1->calc_type != fa2->calc_type)
		return false;
	if (fa1->metric_type != fa2->metric_type)
		return false;
	if (fa1->exclude_srlg != fa2->exclude_srlg)
		return false;
	if (fa1->flags != fa2->flags)
		return false;
	if (fa1->unsupported_subtlv != fa2->unsupported_subtlv)
		return false;

	if (!admin_group_cmp(&fa1->admin_group_exclude_any,
			     &fa2->admin_group_exclude_any))
		return false;
	if (!admin_group_cmp(&fa1->admin_group_include_all,
			     &fa2->admin_group_include_all))
		return false;
	if (!admin_group_cmp(&fa1->admin_group_include_any,
			     &fa2->admin_group_include_any))
		return false;

	return true;
}

/**
 * Check SR Algorithm is Flex-Algo
 * according to RFC9350 section 4
 *
 * @param algorithm SR Algorithm
 */
bool flex_algo_id_valid(uint16_t algorithm)
{
	return algorithm >= SR_ALGORITHM_FLEX_MIN &&
	       algorithm <= SR_ALGORITHM_FLEX_MAX;
}

char *flex_algo_metric_type_print(char *type_str, size_t sz,
				  enum flex_algo_metric_type metric_type)
{
	switch (metric_type) {
	case MT_IGP:
		snprintf(type_str, sz, "igp");
		break;
	case MT_MIN_UNI_LINK_DELAY:
		snprintf(type_str, sz, "delay");
		break;
	case MT_TE_DEFAULT:
		snprintf(type_str, sz, "te");
		break;
	}
	return type_str;
}

bool flex_algo_get_state(struct flex_algos *flex_algos, uint8_t algorithm)
{
	struct flex_algo *fa = flex_algo_lookup(flex_algos, algorithm);

	if (!fa)
		return false;

	return fa->state;
}

void flex_algo_set_state(struct flex_algos *flex_algos, uint8_t algorithm,
			 bool state)
{
	struct flex_algo *fa = flex_algo_lookup(flex_algos, algorithm);

	if (!fa)
		return;

	fa->state = state;
}
