#include <stdint.h>
#include <string.h>

#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>

#include "log.h"
#include "frame_rifo.h"

static void *g_e1d_ctx;
static uint32_t init_next_out_fn;

#define FN_ABS_TO_REL(fn)		 ((int) (((int64_t) (fn)) - ((int64_t) init_next_out_fn)))
#define ABS_DECISION(depth)		 (((long long int) ((depth))) <=			  \
					  ((long long int) (FRAMES_PER_FIFO / 2)))
#define DEPTH_ABS_TO_REL(depth)		 ((ABS_DECISION((depth))) ? ((int) (depth)) :		  \
					  (((int) (depth)) - (FRAMES_PER_FIFO - 1)))
#define ABS_DEPTH_STR(depth)		 ((ABS_DECISION((depth))) ? ("") :			  \
					  ("FRAMES_PER_FIFO - 1 + "))
#define ABS_DEPTH_PRINT(depth)		 ABS_DEPTH_STR((depth)), DEPTH_ABS_TO_REL((depth))
#define FN_PRINT(fn)			 ABS_DEPTH_PRINT(FN_ABS_TO_REL((fn)))

static void rifo_init(struct frame_rifo *rifo)
{
	frame_rifo_init(rifo, 0);
	rifo->next_out_fn = init_next_out_fn;
	rifo->last_in_fn  = init_next_out_fn - 1;
}

static void rifo_in(struct frame_rifo *rifo, uint8_t *frame, uint32_t fn)
{
	int rc = frame_rifo_in(rifo, frame, fn);
	unsigned int depth = frame_rifo_depth(rifo);
	printf("RIFO_IN(%s, start fn + %s%d)=%d [depth=%s%d, frames=%u]\n",
	       osmo_hexdump_nospc(frame, BYTES_PER_FRAME), FN_PRINT(fn), rc,
	       ABS_DEPTH_PRINT(depth), frame_rifo_frames(rifo));
}

static int rifo_out(struct frame_rifo *rifo, uint8_t *out)
{
	int rc = frame_rifo_out(rifo, out);
	printf("RIFO_OUT(%s)=%d [depth=%u, frames=%u]\n", osmo_hexdump_nospc(out, BYTES_PER_FRAME),
		rc, frame_rifo_depth(rifo), frame_rifo_frames(rifo));
	return rc;
}

static void missing_frames(uint8_t modulo)
{
	struct frame_rifo rifo;
	frame_rifo_init(&rifo, 0);
	rifo.next_out_fn = init_next_out_fn;
	rifo.last_in_fn  = init_next_out_fn - 1;

	printf("\nTEST: %s, starting at FN: %u\n", __func__, init_next_out_fn);

	for (int i = 0; i < 10; i++) {
		uint8_t frame[32];
		memset(frame, i, sizeof(frame));

		if (i % 2 == modulo) {
			rifo_in(&rifo, frame, init_next_out_fn + i);
		}
	}

	for (int i = 0; i < 10; i++) {
		uint8_t frame[32];
		memset(frame, 0xff, sizeof(frame));
		rifo_out(&rifo, frame);
	}
}

static void reordered_in(void)
{
	struct frame_rifo rifo;
	rifo_init(&rifo);

	printf("\nTEST: %s, starting at FN: %u\n", __func__, init_next_out_fn);

	const uint8_t in[] = { 0, 1, 4, 3, 5, 2, 6, 7, 8, 9 };
	for (int i = 0; i < sizeof(in); i++) {
		uint8_t frame[32];
		memset(frame, in[i], sizeof(frame));
		rifo_in(&rifo, frame, init_next_out_fn + in[i]);
	}

	for (int i = 0; i < 10; i++) {
		uint8_t frame[32];
		memset(frame, 0xff, sizeof(frame));
		rifo_out(&rifo, frame);
	}
}

static void correct_order(void)
{
	struct frame_rifo rifo;
	rifo_init(&rifo);

	printf("\nTEST: %s, starting at FN: %u\n", __func__, init_next_out_fn);

	const uint8_t in[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	for (int i = 0; i < sizeof(in); i++) {
		uint8_t frame[32];
		memset(frame, in[i], sizeof(frame));
		rifo_in(&rifo, frame, init_next_out_fn + in[i]);
	}

	for (int i = 0; i < 10; i++) {
		uint8_t frame[32];
		memset(frame, 0xff, sizeof(frame));
		rifo_out(&rifo, frame);
	}
}

static void too_old_frames(void)
{
	struct frame_rifo rifo;
	rifo_init(&rifo);

	printf("\nTEST: %s, starting at FN: %u\n", __func__, init_next_out_fn);

	// Put 10 frames at absolute frame numbers FRAMES_PER_FIFO+50
	// to FRAMES_PER_FIFO+60 (to get outside of the
	// FRAMES_PER_FIFO frame buffer)
	const uint8_t in[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	for (int i = 0; i < sizeof(in); i++) {
		uint8_t frame[32];
		memset(frame, in[i], sizeof(frame));
		rifo_in(&rifo, frame, init_next_out_fn + in[i] + FRAMES_PER_FIFO + 50);
	}

	// Skip the first FRAMES_PER_FIFO + 50 frames
	for (int i = 0; i < FRAMES_PER_FIFO + 50; i++) {
		uint8_t frame[32];
		memset(frame, 0xff, sizeof(frame));
		// Note: frame_rifo_out instead of rifo_out
		// (just to ignore the output)
		frame_rifo_out(&rifo, frame);
	}

	// Try to read the 10 real frames (which shouldn't be in the buffer)
	for (int i = 0; i < 10; i++) {
		uint8_t frame[32];
		memset(frame, 0xff, sizeof(frame));
		rifo_out(&rifo, frame);
	}
}

static void bound_check(void)
{
	uint8_t frame[32];
	struct frame_rifo rifo;
	rifo_init(&rifo);

	printf("\nTEST: %s, starting at FN: %u\n", __func__, init_next_out_fn);

	// Put 1 frame and get it
	memset(frame, 0xa5, sizeof(frame));
	frame_rifo_in(&rifo, frame, init_next_out_fn);
	frame_rifo_out(&rifo, frame);

	// Put 11 frames at absolute frame numbers FRAMES_PER_FIFO -
	// 10 + 1 to FRAMES_PER_FIFO + 1
	const uint8_t in[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	for (int i = 0; i < sizeof(in); i++) {
		memset(frame, in[i], sizeof(frame));
		rifo_in(&rifo, frame, init_next_out_fn + in[i] + FRAMES_PER_FIFO - 10 + 1);
	}

	// Add frame at start offset
	memset(frame, 0xa5, sizeof(frame));
	rifo_in(&rifo, frame, init_next_out_fn);

	// Skip the first FRAMES_PER_FIFO - 10 frames
	for (int i = 0; i < FRAMES_PER_FIFO - 10; i++) {
		memset(frame, 0xff, sizeof(frame));
		// Note: frame_rifo_out instead of rifo_out
		// (just to ignore the output)
		frame_rifo_out(&rifo, frame);
	}

	// Try to read the 10 real frames
	for (int i = 0; i < 10; i++) {
		memset(frame, 0xff, sizeof(frame));
		rifo_out(&rifo, frame);
	}
}

void run_all_tests(void)
{
	missing_frames(0);
	missing_frames(1);
	reordered_in();
	correct_order();
	too_old_frames();
	bound_check();
}

int main(int argc, char **argv)
{
	g_e1d_ctx = talloc_named_const(NULL, 0, "osmo-e1d");
	osmo_init_logging2(g_e1d_ctx, &log_info);

	// run all tests starting with a framenumber of 0
	init_next_out_fn = 0;
	run_all_tests();

	// re-run all tests at the edge of a framenumber rollover
	init_next_out_fn = 0xFFFFFFFF - 5;
	run_all_tests();
}
