#include <stdio.h>
#include <stdint.h>

#include <osmocom/core/talloc.h>
#include <osmocom/core/application.h>
#include <osmocom/gsm/gsm_utils.h>

#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/bts_sm.h>
#include <osmo-bts/measurement.h>
#include <osmo-bts/rsl.h>

static struct gsm_bts *bts;
struct gsm_bts_trx *trx;

struct fn_sample {
	uint32_t fn;
	uint8_t ts;
	uint8_t ss;
	int rc;
};

#include "sysmobts_fr_samples.h"
#include "meas_testcases.h"


void test_fn_sample(struct fn_sample *s, unsigned int len, uint8_t pchan, uint8_t tsmap)
{
	int rc;
	struct gsm_lchan *lchan;
	unsigned int i;
	unsigned int delta = 0;
	uint8_t tsmap_result = 0;
	uint32_t fn_prev = 0;
	struct gsm_time gsm_time;


	printf("\n\n");
	printf("===========================================================\n");

	for (i = 0; i < len; i++) {

		lchan = &trx->ts[s[i].ts].lchan[s[i].ss];
		trx->ts[s[i].ts].pchan = pchan;
		lchan->meas.num_ul_meas = 1;

		rc = lchan_meas_check_compute(lchan, s[i].fn);
		if (rc) {
			gsm_fn2gsmtime(&gsm_time, s[i].fn);
			fprintf(stdout, "Testing: ts[%i]->lchan[%i], fn=%u=>%s, fn%%104=%u, rc=%i, delta=%i\n", s[i].ts,
				s[i].ss, s[i].fn, osmo_dump_gsmtime(&gsm_time), s[i].fn % 104, rc, s[i].fn - fn_prev);
			fn_prev = s[i].fn;
			tsmap_result |= (1 << s[i].ts);
		} else
			delta++;

		/* If the test data set provides a return
		 * code, we check that as well */
		if (s[i].rc != -1)
			OSMO_ASSERT(s[i].rc == rc);
	}

	/* Make sure that we exactly trigger on the right frames
	 * timeslots must match exactlty to what we expect */
	OSMO_ASSERT(tsmap_result == tsmap);
}

static void reset_lchan_meas(struct gsm_lchan *lchan)
{
	lchan->state = LCHAN_S_ACTIVE;
	memset(&lchan->meas, 0, sizeof(lchan->meas));
}

static void test_meas_compute(const struct meas_testcase *mtc)
{
	struct gsm_lchan *lchan;
	unsigned int i;
	unsigned int fn = 0;

	printf("\n\n");
	printf("===========================================================\n");
	printf("Measurement Compute Test: %s\n", mtc->name);

	lchan = &trx->ts[mtc->ts].lchan[0];
	lchan->ts->pchan = mtc->pchan;
	lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
	reset_lchan_meas(lchan);

	/* feed uplink measurements into the code */
	for (i = 0; i < mtc->ulm_count; i++) {
		lchan_new_ul_meas(lchan, &mtc->ulm[i], fn);
		fn += 1;
	}

	/* compute the results */
	OSMO_ASSERT(lchan_meas_check_compute(lchan, mtc->final_fn) == mtc->res.success);
	if (!mtc->res.success) {
		OSMO_ASSERT(!(lchan->meas.flags & LC_UL_M_F_RES_VALID));
	} else {
		OSMO_ASSERT(lchan->meas.flags & (LC_UL_M_F_RES_VALID|LC_UL_M_F_OSMO_EXT_VALID));
		printf("number of measurements: %u\n",  mtc->ulm_count);
		printf("parameter                | actual | expected\n");
		printf("meas.ext.toa256_min      | %6d | %6d\n",
			lchan->meas.ext.toa256_min, mtc->res.toa256_min);
		printf("meas.ext.toa256_max      | %6d | %6d\n",
			lchan->meas.ext.toa256_max, mtc->res.toa256_max);
		printf("meas.ms_toa256           | %6d | %6d\n",
			lchan->meas.ms_toa256, mtc->res.toa256_mean);
		printf("meas.ext.toa256_std_dev  | %6u | %6u\n",
			lchan->meas.ext.toa256_std_dev, mtc->res.toa256_std_dev);
		printf("meas.ul_res.full.rx_lev  | %6u | %6u\n",
			lchan->meas.ul_res.full.rx_lev, mtc->res.rx_lev_full);
		printf("meas.ul_res.full.rx_qual | %6u | %6u\n",
			lchan->meas.ul_res.full.rx_qual, mtc->res.rx_qual_full);

		if ((lchan->meas.ext.toa256_min != mtc->res.toa256_min) ||
		    (lchan->meas.ext.toa256_max != mtc->res.toa256_max) ||
		    (lchan->meas.ms_toa256 != mtc->res.toa256_mean) ||
		    (lchan->meas.ext.toa256_std_dev != mtc->res.toa256_std_dev) ||
		    (lchan->meas.ul_res.full.rx_lev != mtc->res.rx_lev_full)) {
			fprintf(stderr, "%s: Unexpected measurement result!\n", mtc->name);
			OSMO_ASSERT(false);
		}
	}

}

static void test_is_meas_complete_single(struct gsm_lchan *lchan,
					 uint32_t fn_end, uint8_t intv_len)
{
	unsigned int i;
	unsigned int k;
	int rc;
	uint32_t offset;

	/* Walk through multiple measurement intervals and make sure that the
	 * interval end is detected only in the expected location */
	for (k = 0; k < 100; k++) {
		offset = intv_len * k;
		for (i = 0; i < intv_len; i++) {
			rc = is_meas_complete(lchan, i + offset);
			if (rc)
				OSMO_ASSERT(i + offset == fn_end + offset);
		}
	}
}

static void test_is_meas_complete(void)
{
	struct gsm_lchan *lchan;
	printf("\n\n");
	printf("===========================================================\n");
	printf("Testing is_meas_complete()\n");

	/* Test interval end detection on TCH/F TS0-TS7 */
	lchan = &trx->ts[0].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 12, 104);

	lchan = &trx->ts[1].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 25, 104);

	lchan = &trx->ts[2].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 38, 104);

	lchan = &trx->ts[3].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 51, 104);

	lchan = &trx->ts[4].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 64, 104);

	lchan = &trx->ts[5].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 77, 104);

	lchan = &trx->ts[6].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 90, 104);

	lchan = &trx->ts[7].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	test_is_meas_complete_single(lchan, 103, 104);

	/* Test interval end detection on TCH/H TS0-TS7 */
	lchan = &trx->ts[0].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 12, 104);

	lchan = &trx->ts[1].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 12, 104);

	lchan = &trx->ts[0].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 25, 104);

	lchan = &trx->ts[1].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 25, 104);

	lchan = &trx->ts[2].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 38, 104);

	lchan = &trx->ts[3].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 38, 104);

	lchan = &trx->ts[2].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 51, 104);

	lchan = &trx->ts[3].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 51, 104);

	lchan = &trx->ts[4].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 64, 104);

	lchan = &trx->ts[5].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 64, 104);

	lchan = &trx->ts[4].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 77, 104);

	lchan = &trx->ts[5].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 77, 104);

	lchan = &trx->ts[6].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 90, 104);

	lchan = &trx->ts[7].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 90, 104);

	lchan = &trx->ts[6].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 103, 104);

	lchan = &trx->ts[7].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_TCH_H;
	test_is_meas_complete_single(lchan, 103, 104);

	/* Test interval end detection on SDCCH/8 SS0-SS7 */
	lchan = &trx->ts[0].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 66, 102);

	lchan = &trx->ts[0].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 70, 102);

	lchan = &trx->ts[0].lchan[2];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 74, 102);

	lchan = &trx->ts[0].lchan[3];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 78, 102);

	lchan = &trx->ts[0].lchan[4];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 98, 102);

	lchan = &trx->ts[0].lchan[5];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 0, 102);

	lchan = &trx->ts[0].lchan[6];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 4, 102);

	lchan = &trx->ts[0].lchan[7];
	lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
	test_is_meas_complete_single(lchan, 8, 102);

	/* Test interval end detection on SDCCH/4 SS0-SS3 */
	lchan = &trx->ts[0].lchan[0];
	lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
	test_is_meas_complete_single(lchan, 88, 102);

	lchan = &trx->ts[0].lchan[1];
	lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
	test_is_meas_complete_single(lchan, 92, 102);

	lchan = &trx->ts[0].lchan[2];
	lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
	test_is_meas_complete_single(lchan, 6, 102);

	lchan = &trx->ts[0].lchan[3];
	lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
	test_is_meas_complete_single(lchan, 10, 102);
}

/* This tests the robustness of lchan_meas_process_measurement(). This is the
 * function that is called from l1_sap.c each time a measurement indication is
 * received. The process must still go on when measurement indications (blocks)
 * are lost or otherwise spaced out. Even the complete absence of the
 * measurement indications from the SACCH which are used to detect the interval
 * end must not keep the interval from being processed. */
void test_lchan_meas_process_measurement(bool no_sacch, bool dropouts)
{
	struct gsm_lchan *lchan = &trx->ts[2].lchan[0];
	unsigned int i;
	unsigned int k = 0;
	unsigned int fn = 0;
	unsigned int fn104;
	struct bts_ul_meas ulm;
	int rc;

	printf("\n\n");
	printf("===========================================================\n");
	printf("Testing lchan_meas_process_measurement()\n");
	if (no_sacch)
		printf(" * SACCH blocks not generated.\n");
	if (dropouts)
		printf
		    (" * Simulate dropouts by leaving out every 4th measurement\n");

	ulm.ber10k = 0;
	ulm.ta_offs_256bits = 256;
	ulm.ci_cb = 0;
	ulm.is_sub = 0;
	ulm.inv_rssi = 90;

	lchan->ts->pchan = GSM_PCHAN_TCH_F;
	reset_lchan_meas(lchan);

	/* feed uplink measurements into the code */
	for (i = 0; i < 100; i++) {

		fn104 = fn % 104;
		ulm.is_sub = 0;

		if (fn104 >= 52 && fn104 <= 59) {
			ulm.is_sub = 1;
		}

		if (dropouts == false || i % 4) {
			if (ulm.is_sub == 1)
				printf("(now adding SUB measurement sample %u)\n", fn);
			rc = lchan_meas_process_measurement(lchan, &ulm, fn);
			OSMO_ASSERT(rc == 0);
		} else if (ulm.is_sub == 1)
			printf("(leaving out SUB measurement sample for frame number %u)\n", fn);
		else
			printf("(leaving out measurement sample for frame number %u)\n", fn);

		fn += 4;
		if (k == 2) {
			fn++;
			k = 0;
		} else
			k++;

		if (fn % 104 == 39 && no_sacch == false) {
			printf("(now adding SUB measurement sample for SACCH block at frame number %u)\n", fn);
			ulm.is_sub = 1;
			rc = lchan_meas_process_measurement(lchan, &ulm, fn - 1);
			OSMO_ASSERT(rc);
		} else if (fn % 104 == 39 && no_sacch == true)
			printf("(leaving out SUB measurement sample for SACCH block at frame number %u)\n", fn);
	}
}

static bool test_ts45008_83_is_sub_is_sub(const struct gsm_lchan *lchan, uint32_t fn)
{
	fn = fn % 104;

	switch (lchan->type) {
	case GSM_LCHAN_TCH_F:
		/* block {52, 53, 54, 55, 56, 57, 58, 59} */
		return fn == 52;
	case GSM_LCHAN_TCH_H:
		if (fn == 0)	/* H0 block { 0,  2,  4,  6} */
			return true;
		if (fn == 52)	/* H0 block {52, 54, 56, 58} */
			return true;
		if (fn == 14)	/* H1 block {14, 16, 18, 20} */
			return true;
		if (fn == 66)	/* H1 block {66, 68, 70, 72} */
			return true;
		return false;
	default:
		return false;
	}
}

static void test_ts45008_83_is_sub_single(uint8_t ts, uint8_t ss, bool fr)
{
	struct gsm_lchan *lchan;
	bool rc;
	unsigned int i;

	lchan = &trx->ts[ts].lchan[ss];

	printf("Checking: ");

	if (fr) {
		printf("TCH/F");
		lchan->type = GSM_LCHAN_TCH_F;
		lchan->ts->pchan = GSM_PCHAN_TCH_F;
		lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
	} else {
		printf("TCH/H");
		lchan->type = GSM_LCHAN_TCH_H;
		lchan->ts->pchan = GSM_PCHAN_TCH_H;
		lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
	}

	printf(" TS=%u SS=%u\n", ts, ss);

	/* Walk trough the first 100 intervals and check for unexpected
	 * results (false positive and false negative) */
	for (i = 0; i < 104 * 100; i++) {
		rc = ts45008_83_is_sub(lchan, i);
		if (rc != test_ts45008_83_is_sub_is_sub(lchan, i)) {
			printf("  ==> ts45008_83_is_sub(fn=%u) yields %s, expected %s\n",
			       i, rc ? "true" : "false", !rc ? "true" : "false");
		}
	}
}

static void test_ts45008_83_is_sub(void)
{
	unsigned int i;

	printf("\n\n");
	printf("===========================================================\n");
	printf("Testing ts45008_83_is_sub()\n");

	for (i = 0; i < 7; i++)
		test_ts45008_83_is_sub_single(i, 0, true);
	for (i = 0; i < 7; i++)
		test_ts45008_83_is_sub_single(i, 0, false);
	for (i = 0; i < 7; i++)
		test_ts45008_83_is_sub_single(i, 1, false);
}

int main(int argc, char **argv)
{
	void *tall_bts_ctx;

	tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
	msgb_talloc_ctx_init(tall_bts_ctx, 0);

	osmo_init_logging2(tall_bts_ctx, &bts_log_info);
	log_set_log_level(osmo_stderr_target, LOGL_DEBUG);
	log_set_print_category_hex(osmo_stderr_target, 0);
	log_set_print_category(osmo_stderr_target, 0);
	log_set_print_level(osmo_stderr_target, 1);
	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
	log_set_use_color(osmo_stderr_target, 0);
	log_parse_category_mask(osmo_stderr_target, "DMEAS,1:");

	g_bts_sm = gsm_bts_sm_alloc(tall_bts_ctx);
	if (!g_bts_sm) {
		fprintf(stderr, "Failed to create BTS Site Manager structure\n");
		exit(1);
	}
	bts = gsm_bts_alloc(g_bts_sm, 0);
	if (!bts) {
		fprintf(stderr, "Failed to create BTS structure\n");
		exit(1);
	}
	if (bts_init(bts) < 0) {
		fprintf(stderr, "unable to init BTS\n");
		exit(1);
	}

	trx = gsm_bts_trx_alloc(bts);
	if (!trx) {
		fprintf(stderr, "Failed to alloc TRX structure\n");
		exit(1);
	}

	printf("\n");
	printf("***********************\n");
	printf("*** FULL RATE TESTS ***\n");
	printf("***********************\n");

	/* Test full rate */
	test_fn_sample(test_fn_tch_f_ts_2_3, ARRAY_SIZE(test_fn_tch_f_ts_2_3), GSM_PCHAN_TCH_F, (1 << 2) | (1 << 3));
	test_fn_sample(test_fn_tch_f_ts_4_5, ARRAY_SIZE(test_fn_tch_f_ts_4_5), GSM_PCHAN_TCH_F, (1 << 4) | (1 << 5));
	test_fn_sample(test_fn_tch_f_ts_6_7, ARRAY_SIZE(test_fn_tch_f_ts_6_7), GSM_PCHAN_TCH_F, (1 << 6) | (1 << 7));

	printf("\n");
	printf("***********************\n");
	printf("*** HALF RATE TESTS ***\n");
	printf("***********************\n");

	/* Test half rate */
	test_fn_sample(test_fn_tch_h_ts_2_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_2_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 2));
	test_fn_sample(test_fn_tch_h_ts_3_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_3_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 3));
	test_fn_sample(test_fn_tch_h_ts_4_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_4_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 4));
	test_fn_sample(test_fn_tch_h_ts_5_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_5_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 5));
	test_fn_sample(test_fn_tch_h_ts_6_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_6_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 6));
	test_fn_sample(test_fn_tch_h_ts_7_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_7_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 7));

	test_meas_compute(&mtc1);
	test_meas_compute(&mtc2);
	test_meas_compute(&mtc3);
	test_meas_compute(&mtc4);
	test_meas_compute(&mtc5);
	test_meas_compute(&mtc_tch_f_complete);
	test_meas_compute(&mtc_tch_f_dtx_with_lost_subs);
	test_meas_compute(&mtc_tch_f_dtx);
	test_meas_compute(&mtc_tch_h_complete);
	test_meas_compute(&mtc_tch_h_dtx_with_lost_subs);
	test_meas_compute(&mtc_tch_h_dtx);
	test_meas_compute(&mtc_overrun);
	test_meas_compute(&mtc_sdcch4_complete);
	test_meas_compute(&mtc_sdcch8_complete);

	printf("\n");
	printf("***************************************************\n");
	printf("*** MEASUREMENT INTERVAL ENDING DETECTION TESTS ***\n");
	printf("***************************************************\n");

	test_is_meas_complete();
	test_lchan_meas_process_measurement(false, false);
	test_lchan_meas_process_measurement(true, false);
	test_lchan_meas_process_measurement(false, true);
	test_lchan_meas_process_measurement(true, true);
	test_ts45008_83_is_sub();

	printf("Success\n");

	return 0;
}