/*
 * Bluetooth low-complexity, subband codec (SBC) library
 * Copyright (C) 2007-2010  Marcel Holtmann <marcel@holtmann.org>
 * Copyright (C) 2007-2008  Frederic Dalleau <fdalleau@free.fr>
 * Copyright (C) 2008-2010  Nokia Corporation
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sndfile.h>
#include <math.h>
#include <string.h>

#define MAXCHANNELS 2
#define DEFACCURACY 7

static double sampletobits(short sample16, int verbose)
{
	double bits = 0;
	unsigned short bit;
	int i;

	if (verbose)
		printf("===> sampletobits(%hd, %04hX)\n", sample16, sample16);

	/* Bit 0 is MSB */
	if (sample16 < 0)
		bits = -1;

	if (verbose)
		printf("%d", (sample16 < 0) ? 1 : 0);

	/* Bit 15 is LSB */
	for (i = 1; i < 16; i++) {
		bit = (unsigned short) sample16;
		bit >>= 15 - i;
		bit %= 2;

		if (verbose)
			printf("%d", bit);

		if (bit)
			bits += (1.0 / pow(2.0, i));
	}

	if (verbose)
		printf("\n");

	return bits;
}

static int calculate_rms_level(SNDFILE * sndref, SF_INFO * infosref,
				SNDFILE * sndtst, SF_INFO * infostst,
						int accuracy, char *csvname)
{
	short refsample[MAXCHANNELS], tstsample[MAXCHANNELS];
	double refbits, tstbits;
	double rms_accu[MAXCHANNELS];
	double rms_level[MAXCHANNELS];
	double rms_limit = 1.0 / (pow(2.0, accuracy - 1) * pow(12.0, 0.5));
	FILE *csv = NULL;
	int i, j, r1, r2, verdict;

	if (csvname)
		csv = fopen(csvname, "wt");

	if (csv) {
		fprintf(csv, "num;");
		for (j = 0; j < infostst->channels; j++)
			fprintf(csv, "ref channel %d;tst channel %d;", j, j);
		fprintf(csv, "\r\n");
	}

	sf_seek(sndref, 0, SEEK_SET);
	sf_seek(sndtst, 0, SEEK_SET);

	memset(rms_accu, 0, sizeof(rms_accu));
	memset(rms_level, 0, sizeof(rms_level));

	for (i = 0; i < infostst->frames; i++) {
		if (csv)
			fprintf(csv, "%d;", i);

		r1 = sf_read_short(sndref, refsample, infostst->channels);
		if (r1 != infostst->channels) {
			printf("Failed to read reference data: %s "
					"(r1=%d, channels=%d)",
					sf_strerror(sndref), r1,
					infostst->channels);
			if (csv)
				fclose(csv);
			return -1;
		}

		r2 = sf_read_short(sndtst, tstsample, infostst->channels);
		if (r2 != infostst->channels) {
			printf("Failed to read test data: %s "
					"(r2=%d, channels=%d)\n",
					sf_strerror(sndtst), r2,
					infostst->channels);
			if (csv)
				fclose(csv);
			return -1;
		}

		for (j = 0; j < infostst->channels; j++) {
			if (csv)
				fprintf(csv, "%d;%d;", refsample[j],
						tstsample[j]);

			refbits = sampletobits(refsample[j], 0);
			tstbits = sampletobits(tstsample[j], 0);

			rms_accu[j] += pow(tstbits - refbits, 2.0);
		}

		if (csv)
			fprintf(csv, "\r\n");
	}

	printf("Limit: %f\n", rms_limit);

	for (j = 0; j < infostst->channels; j++) {
		printf("Channel %d\n", j);
		printf("Accumulated %f\n", rms_accu[j]);
		rms_accu[j] /= (double) infostst->frames;
		printf("Accumulated / %f = %f\n", (double) infostst->frames,
				rms_accu[j]);
		rms_level[j] = sqrt(rms_accu[j]);
		printf("Level = %f (%f x %f = %f)\n",
				rms_level[j], rms_level[j], rms_level[j],
						rms_level[j] * rms_level[j]);
	}

	verdict = 1;

	for (j = 0; j < infostst->channels; j++) {
		printf("Channel %d: %f\n", j, rms_level[j]);

		if (rms_level[j] > rms_limit)
			verdict = 0;
	}

	printf("%s return %d\n", __FUNCTION__, verdict);

	return verdict;
}

static int check_absolute_diff(SNDFILE * sndref, SF_INFO * infosref,
				SNDFILE * sndtst, SF_INFO * infostst,
				int accuracy)
{
	short refsample[MAXCHANNELS], tstsample[MAXCHANNELS];
	short refmax[MAXCHANNELS], tstmax[MAXCHANNELS];
	double refbits, tstbits;
	double rms_absolute = 1.0 / (pow(2, accuracy - 2));
	double calc_max[MAXCHANNELS];
	int calc_count = 0;
	short r1, r2;
	double cur_diff;
	int i, j, verdict;

	memset(&refmax, 0, sizeof(refmax));
	memset(&tstmax, 0, sizeof(tstmax));
	memset(&calc_max, 0, sizeof(calc_max));
	memset(&refsample, 0, sizeof(refsample));
	memset(&tstsample, 0, sizeof(tstsample));

	sf_seek(sndref, 0, SEEK_SET);
	sf_seek(sndtst, 0, SEEK_SET);

	verdict = 1;

	printf("Absolute max: %f\n", rms_absolute);
	for (i = 0; i < infostst->frames; i++) {
		r1 = sf_read_short(sndref, refsample, infostst->channels);

		if (r1 != infostst->channels) {
			printf("Failed to read reference data: %s "
					"(r1=%d, channels=%d)",
					sf_strerror(sndref), r1,
					infostst->channels);
			return -1;
		}

		r2 = sf_read_short(sndtst, tstsample, infostst->channels);
		if (r2 != infostst->channels) {
			printf("Failed to read test data: %s "
					"(r2=%d, channels=%d)\n",
					sf_strerror(sndtst), r2,
					infostst->channels);
			return -1;
		}

		for (j = 0; j < infostst->channels; j++) {
			refbits = sampletobits(refsample[j], 0);
			tstbits = sampletobits(tstsample[j], 0);

			cur_diff = fabs(tstbits - refbits);

			if (cur_diff > rms_absolute) {
				calc_count++;
				/* printf("Channel %d exceeded : fabs(%f - %f) = %f > %f\n", j, tstbits, refbits, cur_diff, rms_absolute); */
				verdict = 0;
			}

			if (cur_diff > calc_max[j]) {
				calc_max[j] = cur_diff;
				refmax[j] = refsample[j];
				tstmax[j] = tstsample[j];
			}
		}
	}

	for (j = 0; j < infostst->channels; j++) {
		printf("Calculated max: %f (%hd-%hd=%hd)\n",
			calc_max[j], tstmax[j], refmax[j],
			tstmax[j] - refmax[j]);
	}

	printf("%s return %d\n", __FUNCTION__, verdict);

	return verdict;
}

static void usage(void)
{
	printf("SBC conformance test ver %s\n", VERSION);
	printf("Copyright (c) 2007-2010  Marcel Holtmann\n");
	printf("Copyright (c) 2007-2008  Frederic Dalleau\n\n");

	printf("Usage:\n"
		"\tsbctester reference.wav checkfile.wav\n"
		"\tsbctester integer\n"
		"\n");

	printf("To test the encoder:\n");
	printf("\tUse a reference codec to encode original.wav to reference.sbc\n");
	printf("\tUse sbcenc to encode original.wav to checkfile.sbc\n");
	printf("\tDecode both file using the reference decoder\n");
	printf("\tRun sbctester with these two wav files to get the result\n\n");

	printf("\tA file called out.csv is generated to use the data in a\n");
	printf("\tspreadsheet application or database.\n\n");
}

int main(int argc, char *argv[])
{
	SNDFILE *sndref = NULL;
	SNDFILE *sndtst = NULL;
	SF_INFO infosref;
	SF_INFO infostst;
	char *ref;
	char *tst;
	int pass_rms, pass_absolute, pass, accuracy;

	if (argc == 2) {
		double db;

		printf("Test sampletobits\n");
		db = sampletobits((short) atoi(argv[1]), 1);
		printf("db = %f\n", db);
		exit(0);
	}

	if (argc < 3) {
		usage();
		exit(1);
	}

	ref = argv[1];
	tst = argv[2];

	printf("opening reference %s\n", ref);

	sndref = sf_open(ref, SFM_READ, &infosref);
	if (!sndref) {
		printf("Failed to open reference file\n");
		exit(1);
	}

	printf("opening testfile %s\n", tst);
	sndtst = sf_open(tst, SFM_READ, &infostst);
	if (!sndtst) {
		printf("Failed to open test file\n");
		sf_close(sndref);
		exit(1);
	}

	printf("reference:\n\t%d frames,\n\t%d hz,\n\t%d channels\n",
		(int) infosref.frames, (int) infosref.samplerate,
		(int) infosref.channels);
	printf("testfile:\n\t%d frames,\n\t%d hz,\n\t%d channels\n",
		(int) infostst.frames, (int) infostst.samplerate,
		(int) infostst.channels);

	/* check number of channels */
	if (infosref.channels > 2 || infostst.channels > 2) {
		printf("Too many channels\n");
		goto error;
	}

	/* compare number of samples */
	if (infosref.samplerate != infostst.samplerate ||
				infosref.channels != infostst.channels) {
		printf("Cannot compare files with different charasteristics\n");
		goto error;
	}

	accuracy = DEFACCURACY;
	printf("Accuracy: %d\n", accuracy);

	/* Condition 1 rms level */
	pass_rms = calculate_rms_level(sndref, &infosref, sndtst, &infostst,
					accuracy, "out.csv");
	if (pass_rms < 0)
		goto error;

	/* Condition 2 absolute difference */
	pass_absolute = check_absolute_diff(sndref, &infosref, sndtst,
						&infostst, accuracy);
	if (pass_absolute < 0)
		goto error;

	/* Verdict */
	pass = pass_rms && pass_absolute;
	printf("Verdict: %s\n", pass ? "pass" : "fail");

	return 0;

error:
	sf_close(sndref);
	sf_close(sndtst);

	exit(1);
}
