/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2024 Brett Sheffield <bacs@librecast.net> */

/* HoneyComb(n) Network
 *
 * honeycomb(0) -  3 routers in a ring
 * honeycomb(1) -  9 routers (honeycomb(0) inside a hexagon of 6 routers)
 * honeycomb(2) - 15 routers (honeycomb(1) inside a hexagon of 6 routers)
 * honeycomb(3) - 21 routers (honeycomb(2) inside a hexagon of 6 routers)
 * etc.
 */

#include "test.h"
#include <librecast_pvt.h>
#include <librecast/router.h>

#define RINGS 3                   /* hex rings */
#define MAX_ROUTERS 3 + 6 * RINGS /* max routers in configuration */

void dump_router_links(lc_router_t *r[], int routers)
{
	test_log("routers: %i\n", routers);
	for (int i = 0; i < routers; i++) {
		fprintf(stderr, "r[%i] %p\n", i, (void *)r[i]);
		for (int p = 0; p < (int)r[i]->ports; p++) {
			if (r[i]->port[p]) {
				lc_router_t *peer = r[i]->port[p]->pair->router;
				fprintf(stderr, "r[%i]p[%i] %p\n", i, p, (void *)peer);
			}
		}
	}
}

/* return true if r1 connects directly to r2 */
static int router_connected(lc_router_t *r1, lc_router_t *r2)
{
	for (int i = 0; i < (int)r1->ports; i++) {
		if (!r1->port[i]) continue;
		for (int j = 0; j < (int)r2->ports; j++) {
			if (!r2->port[j]) continue;
			if (r1->port[i]->pair == r2->port[j])
				return -1;
		}
	}
	return 0;
}

static void ring_connected(lc_router_t *r[], int n)
{
	for (int x = 0; x < n; x++) {
		int y = (x) ? x - 1 : n - 1;
		test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	}
}

static void free_routers(lc_router_t *r[], int n)
{
	for (int i = 0; i < n; i++) {
		lc_router_free(r[i]);
	}
}

int main(void)
{
	lc_ctx_t *lctx;
	lc_router_t *r[MAX_ROUTERS];
	int rc;

	test_name("lc_router_net() - LC_TOPO_HONEYCOMB");

	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) return test_status;

	/* create a honeycomb topology */
	for (int n = 2; n < 7; n++) {
		rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
		if (!test_assert(rc == 0, "lc_router_net()")) {
			perror("lc_router_net");
			goto err_ctx_free;
		}
		dump_router_links(r, n);
		ring_connected(r, n);
		free_routers(r, n);
	}

	int n = 7, x, y;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	for (int x = 6, y = 1; y <= 5; y += 2) {
		test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	}
	free_routers(r, n);

	n = 8;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	ring_connected(&r[6], 2);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 7, y = 5;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

	n = 9;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	ring_connected(&r[6], 3);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 8, y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 7, y = 5;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

	n = 10;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 8, y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

	n = 11;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 8, y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 10, y = 5;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

	n = 12;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 8, y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 10, y = 5;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

	n = 13;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	ring_connected(&r[6], 6);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 8, y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 10, y = 5;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 12, y = 7;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 12, y = 9;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 12, y = 11;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

	n = 14;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	ring_connected(&r[6], 6);
	ring_connected(&r[12], 2);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 8, y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 10, y = 5;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 12, y = 7;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 12, y = 9;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 13, y = 11;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

	n = 15;
	rc = lc_router_net(lctx, r, n, LC_TOPO_HONEYCOMB, 0, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(rc == 0, "lc_router_net()")) {
		perror("lc_router_net");
		goto err_ctx_free;
	}
	dump_router_links(r, n);
	ring_connected(r, 6);
	ring_connected(&r[6], 6);
	ring_connected(&r[12], 3);
	x = 6, y = 1;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 8, y = 3;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 10, y = 5;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 12, y = 7;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 13, y = 11;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	x = 14, y = 9;
	test_assert(router_connected(r[x], r[y]), "r[%i] connects to r[%i]", x, y);
	free_routers(r, n);

err_ctx_free:
	lc_ctx_free(lctx);
	return test_status;
}
