/* gen_records.c: generate the WFDB-x conformance test records using the
   original WFDB library (so that the test data is written by the reference
   implementation).

   Usage: gen_records OUTPUT_DIRECTORY

   Part of WFDB-x, Reza Sameni, Oct 2026.
   GNU General Public License, version 3 or later (see LICENSE). */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wfdb/wfdb.h>
#include <wfdb/ecgcodes.h>

static unsigned long rng = 12345;
static long rnd(long lo, long hi)	/* deterministic LCG, inclusive range */
{
    rng = rng * 1103515245UL + 12345UL;
    rng &= 0x7fffffffUL;
    return lo + (long)(rng % (unsigned long)(hi - lo + 1));
}

/* Write a record with nsig signals in the given format. */
static void gen_fmt(const char *name, int fmt, int nsig, long nframes,
		    int lo, int hi, int with_invalid, int bsize)
{
    WFDB_Siginfo si[8];
    WFDB_Sample v[8];
    char fname[64], desc[8][32];
    int s;
    long t;

    sprintf(fname, "%s.dat", name);
    for (s = 0; s < nsig; s++) {
	si[s].fname = fname;
	sprintf(desc[s], "sig%d fmt%d", s, fmt);
	si[s].desc = desc[s];
	si[s].units = (s == 1) ? "uV" : (s == 2 ? "mmHg" : "mV");
	si[s].gain = 100.0 + 50*s;
	si[s].initval = 0;
	si[s].group = 0;
	si[s].fmt = fmt;
	si[s].spf = 1;
	si[s].bsize = bsize;
	si[s].adcres = 12;
	si[s].adczero = s;
	si[s].baseline = 10*s - 5;
	si[s].nsamp = 0;
	si[s].cksum = 0;
    }
    if (osigfopen(si, nsig) < nsig) exit(1);
    setsampfreq(250.0);
    {
	char bt[] = "12:34:56.789 31/12/1999";
	setbasetime(bt);
    }
    for (s = 0; s < nsig; s++) v[s] = (lo + hi)/2;
    for (t = 0; t < nframes; t++) {
	for (s = 0; s < nsig; s++) {
	    long x = v[s] + rnd(-40, 40);
	    if (fmt == 8) x = v[s] + rnd(-100, 100);
	    if (x < lo) x = lo;
	    if (x > hi) x = hi;
	    v[s] = x;
	    if (t % 97 == 13 && s == 0) v[s] = (t % 2) ? hi : lo;
	}
	if (with_invalid && t % 50 == 7) {
	    WFDB_Sample tmp[8];
	    memcpy(tmp, v, sizeof(tmp));
	    tmp[nsig-1] = WFDB_INVALID_SAMPLE;
	    putvec(tmp);
	}
	else
	    putvec(v);
    }
    newheader((char *)name);
    putinfo("generated by gen_records for wfdb-x conformance tests");
    wfdbquit();
}

/* Multifrequency record: signals with spf 1, 2, and 4. */
static void gen_multifreq(void)
{
    WFDB_Siginfo si[3];
    WFDB_Sample v[7];
    int s, k, spf[3] = { 1, 2, 4 };
    long t;
    static char *desc[3] = { "slow", "medium", "fast" };

    for (s = 0; s < 3; s++) {
	si[s].fname = "mfreq.dat";
	si[s].desc = desc[s];
	si[s].units = "mV";
	si[s].gain = 200.0;
	si[s].initval = 0;
	si[s].group = 0;
	si[s].fmt = 16;
	si[s].spf = spf[s];
	si[s].bsize = 0;
	si[s].adcres = 16;
	si[s].adczero = 0;
	si[s].baseline = 0;
	si[s].nsamp = 0;
	si[s].cksum = 0;
    }
    if (osigfopen(si, 3) < 3) exit(1);
    setsampfreq(100.0);
    for (t = 0; t < 600; t++) {
	for (k = 0; k < 7; k++)
	    v[k] = (int)rnd(-3000, 3000);
	if (t % 40 == 5) v[3] = WFDB_INVALID_SAMPLE;
	putvec(v);
    }
    newheader("mfreq");
    wfdbquit();
}

/* A record with two signal files (groups), in different formats. */
static void gen_twogroups(void)
{
    WFDB_Siginfo si[3];
    WFDB_Sample v[3];
    int s;
    long t;
    static char *desc[3] = { "A", "B", "C" };
    static char *fn[3] = { "twog_a.dat", "twog_b.dat", "twog_b.dat" };
    static int grp[3] = { 0, 1, 1 }, fmt[3] = { 16, 212, 212 };

    for (s = 0; s < 3; s++) {
	si[s].fname = fn[s];
	si[s].desc = desc[s];
	si[s].units = "mV";
	si[s].gain = 200.0;
	si[s].initval = 0;
	si[s].group = grp[s];
	si[s].fmt = fmt[s];
	si[s].spf = 1;
	si[s].bsize = 0;
	si[s].adcres = 12;
	si[s].adczero = 0;
	si[s].baseline = 0;
	si[s].nsamp = 0;
	si[s].cksum = 0;
    }
    if (osigfopen(si, 3) < 3) exit(1);
    setsampfreq(360.0);
    for (t = 0; t < 1001; t++) {
	for (s = 0; s < 3; s++)
	    v[s] = (int)rnd(-2000, 2000);
	putvec(v);
    }
    newheader("twog");
    wfdbquit();
}

static void putann1(long t, int type, int sub, int chan, int num,
		    const char *aux)
{
    WFDB_Annotation a;
    static unsigned char buf[300];

    a.time = t;
    a.anntyp = type;
    a.subtyp = sub;
    a.chan = chan;
    a.num = num;
    if (aux) {
	buf[0] = strlen(aux);
	strcpy((char *)buf+1, aux);
	a.aux = buf;
    }
    else
	a.aux = NULL;
    putann(0, &a);
}

/* An annotation file exercising all annotation word types. */
static void gen_annotations(void)
{
    WFDB_Anninfo ai;
    long t;
    int i;

    ai.name = "tst";
    ai.stat = WFDB_WRITE;
    if (annopen("fmt16", &ai, 1) < 0) exit(1);
    putann1(0, NOTE, 0, 0, 0, "start");
    putann1(5, NORMAL, 0, 0, 0, NULL);
    putann1(5, NORMAL, 0, 1, 0, NULL);
    putann1(5, NORMAL, 0, 1, 2, NULL);
    putann1(300, PVC, 3, 0, 0, NULL);
    putann1(301, RHYTHM, 0, 0, 0, "(AFIB");
    putann1(2000, NOISE, -1, 2, 1, "odd");
    putann1(70000, ARFCT, 0, 0, 0, NULL);
    putann1(70001, NOTE, 0, 0, -3, "a note with spaces");
    for (i = 0, t = 70010; i < 40; i++) {
	t += rnd(1, 3000);
	putann1(t, (int)rnd(1, 41), (int)rnd(-2, 5), (int)rnd(0, 3),
		(int)rnd(-1, 2), (i % 7 == 3) ? "x" : NULL);
    }
    putann1(5000000000L, NORMAL, 0, 0, 0, NULL);
    wfdbquit();

    /* An annotation file with a modified mnemonic table and a time
       resolution record. */
    ai.name = "mod";
    setannstr(NORMAL, "Q!");
    setanndesc(NORMAL, "Custom normal beat");
    setannstr(45, "X45");
    setanndesc(45, "user-defined");
    setafreq(1000.0);
    if (annopen("fmt16", &ai, 1) < 0) exit(1);
    for (t = 100; t < 5000; t += 731)
	putann1(t, (t % 2) ? NORMAL : 45, 0, 0, 0, NULL);
    wfdbquit();
    setafreq(0.0);
}

#ifdef FLAC_RECORDS
/* FLAC records (requires a WFDB library built with FLAC support). */
static void gen_flac(void)
{
    WFDB_Siginfo si[2];
    WFDB_Sample v[4];
    int s;
    long t;
    static char *desc[2] = { "fa", "fb" };

    gen_fmt("flac8", 508, 2, 5000, -127, 127, 1, 0);
    gen_fmt("flac16", 516, 3, 9000, -32767, 32767, 1, 0);
    gen_fmt("flac24", 524, 1, 4500, -8388607, 8388607, 1, 0);
    for (s = 0; s < 2; s++) {
	si[s].fname = "flacmf.dat";
	si[s].desc = desc[s];
	si[s].units = "mV";
	si[s].gain = 200.0;
	si[s].initval = 0;
	si[s].group = 0;
	si[s].fmt = 516;
	si[s].spf = 2;
	si[s].bsize = 0;
	si[s].adcres = 16;
	si[s].adczero = 0;
	si[s].baseline = 0;
	si[s].nsamp = 0;
	si[s].cksum = 0;
    }
    if (osigfopen(si, 2) < 2) exit(1);
    setsampfreq(200.0);
    for (t = 0; t < 4321; t++) {
	for (s = 0; s < 4; s++)
	    v[s] = (int)(1000*s + (t*(s+3)) % 777 - 300);
	putvec(v);
    }
    newheader("flacmf");
    wfdbquit();
}
#endif

int main(int argc, char **argv)
{
    if (argc < 2 || chdir(argv[1]) != 0) {
	fprintf(stderr, "usage: %s OUTPUT_DIRECTORY\n", argv[0]);
	exit(1);
    }
    setwfdb(".");
    gen_fmt("fmt8", 8, 2, 1500, -2000, 2000, 0, 0);
    gen_fmt("fmt16", 16, 3, 1500, -32767, 32767, 1, 0);
    gen_fmt("fmt61", 61, 2, 1500, -32767, 32767, 1, 0);
    gen_fmt("fmt80", 80, 2, 1500, -127, 127, 1, 0);
    gen_fmt("fmt160", 160, 2, 1500, -32767, 32767, 1, 0);
    gen_fmt("fmt212", 212, 3, 1501, -2047, 2047, 1, 0);
    gen_fmt("fmt212b", 212, 1, 1001, -2047, 2047, 0, 0);
    gen_fmt("fmt310", 310, 2, 1501, -511, 511, 1, 0);
    gen_fmt("fmt310b", 310, 1, 1000, -511, 511, 0, 0);
    gen_fmt("fmt311", 311, 2, 1501, -511, 511, 1, 0);
    gen_fmt("fmt311b", 311, 1, 1000, -511, 511, 0, 0);
    gen_fmt("fmt24", 24, 2, 1500, -8388607, 8388607, 1, 0);
    gen_fmt("fmt32", 32, 2, 1500, -2147483647, 2147483647, 1, 0);
    gen_fmt("fmtblk", 16, 2, 1000, -32767, 32767, 0, 512);
    gen_multifreq();
    gen_twogroups();
    gen_annotations();
#ifdef FLAC_RECORDS
    gen_flac();
#endif
    return 0;
}
