// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */

//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------
#include <linux/slab.h>

#include "mtk_dmx_common.h"
#include "mtk_dmx_graph.h"

//-------------------------------------------------------------------------------------------------
//  Debug Constants and Macros
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Constants
//-------------------------------------------------------------------------------------------------
#define MTK_DMX_GRAPH_FACTORIAL_START (2)

//-------------------------------------------------------------------------------------------------
//  Macros
//-------------------------------------------------------------------------------------------------
#define MTK_DMX_GRAPH_MALLOC(size) kmalloc(size, GFP_KERNEL)
#define MTK_DMX_GRAPH_CALLOC(n, size) kcalloc(n, size, GFP_KERNEL)
#define MTK_DMX_GRAPH_FREE kfree
#define MTK_DMX_GRAPH_PRINT MTK_DMX_INFO

//-------------------------------------------------------------------------------------------------
//  Structs and Enums
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Variables
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Internal Functions
//-------------------------------------------------------------------------------------------------
static u64 graph_factorial(int n);

static struct graph *graph_allocate_graph(
		int max_count);
static bool graph_free_graph(
		struct graph *graph);
static struct graph *graph_construct_graph(
		struct edges *edges,
		int max_count);
static bool graph_print_graph(
		struct graph *pstGraph,
		node_type_2_str pfNodeType2Str);
static struct edges * graph_allocate_edges(
		int iMaxCount);
static bool graph_free_edges(
		struct edges *pstEdges);

static u64 graph_factorial(int n)
{
	int i = 0;
	u64 result = 1;

	for (i = MTK_DMX_GRAPH_FACTORIAL_START; i <= n; i++)
		result *= i;

	return result;
}

static struct graph *graph_allocate_graph(
		int max_count)
{
	struct graph *graph = NULL;
	unsigned i;

	graph = (struct graph *) MTK_DMX_GRAPH_MALLOC(sizeof(struct graph));
	graph->max_count = max_count;
	graph->head =
		MTK_DMX_GRAPH_CALLOC(
			graph->max_count,
			sizeof(struct graph_node *));

	// initialize head pointer for all vertices
	for (i = 0; i < graph->max_count; i++)
		graph->head[i] = NULL;

	return graph;
}

static bool graph_free_graph(
		struct graph *graph)
{
	int i = 0;
	struct graph_node *ptr = NULL;
	struct graph_node *next_ptr = NULL;

	if (graph == NULL)
		return false;

	for (i = 0; i < graph->max_count; i++) {
		ptr = graph->head[i];

		while (ptr != NULL) {
			next_ptr = ptr->next;
			MTK_DMX_GRAPH_FREE(ptr);
			ptr = next_ptr;
		}
	}

	MTK_DMX_GRAPH_FREE(graph->head);
	MTK_DMX_GRAPH_FREE(graph);

	return true;
}

// Function to create an adjacency list from specified edges
static struct graph *graph_construct_graph(
		struct edges *edges,
		int max_count)
{
	struct graph *graph = NULL;
	int i;

	graph = graph_allocate_graph(max_count);

	// add edges to the directed graph one by one
	for (i = 0; i < edges->valid_count; i++) {
		// get the source and destination vertex
		int src = edges->edge[i].src;
		int dest = edges->edge[i].dest;

		// allocate a new node of adjacency list from `src` to `dest`
		struct graph_node *new_node =
			(struct graph_node *) MTK_DMX_GRAPH_MALLOC(sizeof(struct graph_node));
		new_node->dest = dest;

		// point new node to the current head
		new_node->next = graph->head[src];

		// point head pointer to the new node
		graph->head[src] = new_node;
	}

	return graph;
}

// Function to print adjacency list representation of a graph
static bool graph_print_graph(
		struct graph *pstGraph,
		node_type_2_str pfNodeType2Str)
{
	int i = 0;
	struct graph_node *pstNode = NULL;
	bool bIsNotEmpty = false;

	for (i = 0; i < pstGraph->max_count; i++) {
		// print current vertex and all its neighbors
		pstNode = pstGraph->head[i];
		bIsNotEmpty = false;

		while (pstNode != NULL) {
			MTK_DMX_GRAPH_PRINT(
				"(%s -> %s)",
				(*pfNodeType2Str)(i),
				(*pfNodeType2Str)(pstNode->dest));

			pstNode = pstNode->next;
			bIsNotEmpty = true;
		}

		if (bIsNotEmpty)
			MTK_DMX_GRAPH_PRINT("\n");
	}

	return true;
}

static struct edges * graph_allocate_edges(
		int iMaxCount)
{
	struct edges *pstEdges = NULL;

	pstEdges =
		MTK_DMX_GRAPH_MALLOC(sizeof(*pstEdges));

	pstEdges->max_count = iMaxCount;
	pstEdges->valid_count = 0;
	pstEdges->edge =
		MTK_DMX_GRAPH_CALLOC(
			pstEdges->max_count,
			sizeof(*pstEdges->edge));

	return pstEdges;
}

static bool graph_free_edges(struct edges *pstEdges)
{
	if (pstEdges == NULL)
		return false;

	MTK_DMX_GRAPH_FREE(pstEdges->edge);
	MTK_DMX_GRAPH_FREE(pstEdges);

	return true;
}

//-------------------------------------------------------------------------------------------------
//  Public Functions
//-------------------------------------------------------------------------------------------------
u64 mtk_dmx_graph_permutation(int n, int r)
{
	int i = 0;
	u64 result = 1;

	if (n <= r) {
		MTK_DMX_GRAPH_PRINT(
			"n %d <= r %d\n",
			n,
			r);

		return 1;
	}

	if (n <= 1)
		return 1;

	for (i = (n - r + 1); i <= n; i++)
		result *= i;

	return result;
}

u64 mtk_dmx_graph_combination(int n, int r)
{
	if (n <= r) {
		MTK_DMX_GRAPH_PRINT(
			"n %d <= r %d\n",
			n,
			r);

		return 1;
	}

	if (n <= 1)
		return 1;

	return mtk_dmx_graph_permutation(n, r) / graph_factorial(r);
}

void mtk_dmx_graph_print(struct graph_context stGraphContext)
{
	struct edges *pstEdges = NULL;
	struct graph *pstGraph = NULL;

	do {
		pstEdges =
			graph_allocate_edges(
				stGraphContext.edge_max_count);
		if (pstEdges == NULL)
			break;

		if (!(*stGraphContext.pfEdgesPrepare)(
				stGraphContext,
				pstEdges))
			break;

		pstGraph =
			graph_construct_graph(
				pstEdges,
				stGraphContext.vertex_max_count);
		if (pstGraph == NULL)
			break;

		graph_print_graph(
			pstGraph,
			stGraphContext.pfNodeType2Str);
	} while (0);

	graph_free_edges(pstEdges);
	graph_free_graph(pstGraph);
}
