#include <stdio.h>
#include <errno.h>

#include "fe/fe.h"
#include "feat/feat.h"
#include "util/cmd_ln.h"
#include "util/ckd_alloc.h"

#include "test_macros.h"

int
main(int argc, char *argv[])
{
	static const ps_arg_t fe_args[] = {
		waveform_to_cepstral_command_line_macro(),
		{ NULL, 0, NULL, NULL }
	};
	FILE *raw;
	cmd_ln_t *config;
	fe_t *fe;
	feat_t *fcb;
	int16 buf[2048];
	mfcc_t **cepbuf, **cptr;
	mfcc_t ***featbuf1, ***featbuf2, ***fptr;
	size_t nsamp;
	int32 total_frames, ncep, nfr, i;

	if ((raw = fopen(TESTDATADIR "/chan3.raw", "rb")) == NULL) {
		perror(TESTDATADIR "/chan3.raw");
		return 1;
	}

	config = cmd_ln_parse_r(NULL, fe_args, argc, argv, FALSE);
	fe = fe_init_auto_r(config);
	fcb = feat_init("1s_c_d_dd", CMN_NONE, FALSE, AGC_NONE,
			TRUE, fe_get_output_size(fe));

	/* Determine how much data and how many MFCC frames we need. */
	fseek(raw, 0, SEEK_END);
	nsamp = ftell(raw) / sizeof(int16);
	fe_process_frames(fe, NULL, &nsamp, NULL, &total_frames);
	printf("%ld samples, %d + 1 frames\n", nsamp, total_frames);
	total_frames++; /* For the possible fe_end_utt() frame */
	cepbuf = ckd_calloc_2d(total_frames + 1, fe_get_output_size(fe), sizeof(**cepbuf));
	fseek(raw, 0, SEEK_SET);

	/* Pay close attention, kids.  This is how you use fe_process_frames(). */
	fe_start_utt(fe);
	cptr = cepbuf;
	nfr = total_frames;
	while ((nsamp = fread(buf, sizeof(int16), 2048, raw)) > 0) {
		int16 const *bptr = buf;
		while (nsamp) {
			int32 ncep = nfr;
			fe_process_frames(fe, &bptr, &nsamp, cptr, &ncep);
			cptr += ncep;
			nfr -= ncep;
		}
	}
	fe_end_utt(fe, *cptr, &nfr);

	/* Now test some feature extraction problems. */
	featbuf1 = feat_array_alloc(fcb, total_frames);
	featbuf2 = feat_array_alloc(fcb, total_frames);

	/* Whole utterance: canonical, assumed to be correct. */
	ncep = total_frames;
	TEST_EQUAL(total_frames,
		   feat_s2mfc2feat_live(fcb, cepbuf,
					&ncep, TRUE, TRUE,
					featbuf1));
	TEST_EQUAL(ncep, total_frames);

	/* Process one frame at a time. */
	cptr = cepbuf;
	fptr = featbuf2;
	ncep = 1;
	nfr = feat_s2mfc2feat_live(fcb, cptr, &ncep, TRUE, FALSE, fptr);
	TEST_EQUAL(nfr, 0); /* Not possible to make any frames yet. */
	TEST_EQUAL(ncep, 1); /* But we should have consumed one. */
	cptr += ncep;
	for (i = 1; i < total_frames - 1; ++i) {
		ncep = 1;
		nfr = feat_s2mfc2feat_live(fcb, cptr, &ncep, FALSE, FALSE, fptr);
		cptr += ncep;
		fptr += nfr;
	}
	nfr = feat_s2mfc2feat_live(fcb, cptr, &ncep, FALSE, TRUE, fptr);
	TEST_EQUAL(nfr, 4); /* This should have dumped the trailing window. */
	TEST_EQUAL(ncep, 1); /* And only consumed one frame of MFCCs. */
	cptr += ncep;
	fptr += nfr;
	/* Verify that we actually got the correct number of frames. */
	TEST_EQUAL(cptr - cepbuf, total_frames);
	TEST_EQUAL(fptr - featbuf2, total_frames);

	/* Now verify that the results are equal. */
	for (i = 0; i < total_frames; ++i) {
		int32 j;
		printf("%-4d ", i);
		for (j = 0; (uint32)j < feat_dimension(fcb); ++j) {
			TEST_EQUAL_MFCC(featbuf1[i][0][j], featbuf2[i][0][j]);
		}
		if (i % 10 == 9)
			printf("\n");
	}
	printf("\n");

	/* Process large chunks of frames at once, so as to exceed the
	 * internal ringbuffer size in feat_s2mfc2feat_live(). */
	cptr = cepbuf;
	fptr = featbuf2;
	ncep = total_frames;
	nfr = feat_s2mfc2feat_live(fcb, cptr, &ncep, TRUE, FALSE, fptr);
	TEST_ASSERT(ncep != nfr);
	cptr += ncep;
	fptr += nfr;
	ncep = total_frames - ncep;
	while (ncep) {
		int32 tmp_ncep;
		tmp_ncep = ncep;
		nfr = feat_s2mfc2feat_live(fcb, cptr, &tmp_ncep, FALSE, FALSE, fptr);
		cptr += tmp_ncep;
		fptr += nfr;
		ncep -= tmp_ncep;
	}
	nfr = feat_s2mfc2feat_live(fcb, cptr, &ncep, FALSE, TRUE, fptr);
	cptr += ncep;
	fptr += nfr;
	TEST_EQUAL(cptr - cepbuf, total_frames);
	TEST_EQUAL(fptr - featbuf2, total_frames);

	/* Now verify that the results are equal. */
	for (i = 0; i < total_frames; ++i) {
		int32 j;
		printf("%-4d ", i);
		for (j = 0; (uint32)j < feat_dimension(fcb); ++j)
			TEST_EQUAL_MFCC(featbuf1[i][0][j], featbuf2[i][0][j]);
		if (i % 10 == 9)
			printf("\n");
	}
	printf("\n");

	fclose(raw);
	fe_free(fe);
	feat_array_free(featbuf1);
	feat_array_free(featbuf2);
	feat_free(fcb);
	ckd_free_2d(cepbuf);
	ps_config_free(config);

	return 0;
}
