/* file: pnnlist.c	Joe E. Mietus	19 November 2002
			Last revised:   23 February 2003
-------------------------------------------------------------------------------
pnnlist: derive pNNx statistics from an annotation interval list
Copyright (C) 2003 Joe E. Mietus

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.

You may contact the author by e-mail (joe at physionet dot org) or postal mail
(BIDMC Room KB-28, 300 Brookline Ave., Boston, MA 02215 USA).  For updates to
this software, please visit PhysioNet (http://www.physionet.org/).
_______________________________________________________________________________

A detailed description of the application of this algorithm can be
found in: JE Mietus, C-K Peng, I Henry, R L Goldsmith and AL Goldberger.
"The pNNx files: re-examining a widely used heart rate variability measure."
Heart 2002; vol. 88, pp. 378-380. 
*/

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

#define ABS(A) ((A) < 0 ? -(A) : (A))
#define ROFFERR 1e-9

char *pname, *prog_name();

main(argc, argv)
int argc;
char *argv[];
{
    char ann, lastann[2];
    int pflag, sflag, dblcmp();
    double rr, lastrr, inc, nninc, mult, *pnn = NULL;
    long i, maxdat, sum, tot, totn, totp;
    void bsd_qsort(), help();

    pname = prog_name(argv[0]);

    inc = pflag = sflag = 0;
    mult = 1000;

    for (i = 0; ++i < argc && *argv[i] == '-'; ) {
        switch(argv[i][1]) {
	    case 'i':
		if (i < argc-1)
		    inc = atof(argv[++i]);
		else {
		    fprintf(stderr, "%s: increment must follow -i\n", pname);
		    exit(1);
		}
		break;
	    case 'p':
		pflag = 1;
		mult = 100;
		break;
	    case 's':
		sflag = 1;
		break;
            default:
		help();
		exit(1);
	}
    }
    if (inc < 0) {
        fprintf(stderr, "%s -i inc : increment must be greater than 0\n",
		pname);
	exit(1);
    }
    else if (inc > 0) {
      if (pflag)
	  inc /= 100;
      else
	  inc /= 1000;
    }

    if (scanf("%lf %c", &rr, &ann) != 2) {
        fprintf(stderr, "%s : improperly formatted data\n\n", pname);
	help();
        exit(2);
    }

    i = maxdat = 0;
    lastann[0] = 'X';
    lastann[1] = ann;

    while (scanf("%lf %c", &rr, &ann) == 2) {

        if (lastann[0] == 'N' && lastann[1] == 'N' && ann == 'N') {
	    if (i >= maxdat) {
		double *s;

		maxdat += 50000;	/* allow the pnn array to grow (the
					   increment is arbitrary) */
		if ((s = realloc(pnn, maxdat * sizeof(double))) == NULL) {
		    fprintf(stderr,
			    "%s: truncating input after %ld increments\n",
			    pname, i);
		    break;
		}
		pnn = s;
	    }
	    
	    if (pflag)
		pnn[i] = (rr-lastrr)/lastrr;
	    else
		pnn[i] = rr-lastrr;

	    if (!sflag)
		pnn[i] = ABS(pnn[i]);

	    i++;
	}

	lastrr = rr;
	lastann[0] = lastann[1];
	lastann[1] = ann;
    }

    tot = i;
    if (tot <= 0) {
	fprintf(stderr, "%s: no consecutive NN intervals in input\n", pname);
	exit(3);
    }

    bsd_qsort(pnn, tot, sizeof(double), dblcmp);

    if (sflag) { /* signed distributions */
        for (totn = totp = i = 0; i < tot; i++) {
	    if (pnn[i] <= 0)
	        totn++;
	    if (pnn[i] >= 0)
	        totp++;
	}
	if (inc == 0) {
            printf("%g\t0\n", mult*pnn[0]);
            for (sum = i = 1; i < tot; i++) {
	        if (pnn[i] < 0) {
		    if (pnn[i] - pnn[i-1] > ROFFERR)
	                printf("%g\t%g\n", mult*pnn[i], 100*(double)sum/totn);
		    sum++;
	        }
	        else if (pnn[i] == 0 && pnn[i-1] != 0) {
		    printf("%g\t%g\n", mult*pnn[i], 100*(double)sum/totn);
		    sum = 1;
	        }
	        else if (pnn[i] == 0 && pnn[i-1] == 0)
		    sum++;
	        else {
		    if (pnn[i] - pnn[i-1] > ROFFERR)
		        printf("%g\t%g\n", mult*pnn[i-1],
			       100*(1-(double)sum/totp));
		    sum++;
	        }
	    }
	    printf("%g\t%g\n", mult*pnn[i-1], 100*(1-(double)sum/totp));
        }
	else {  /* inc > 0 */
	    for (nninc = 0; nninc > pnn[0]; nninc -= inc)
	        ;
            printf("%g\t0\n", mult*nninc);
	    nninc += inc;
            for (sum = i = 0; i < tot; i++) {
	        if (pnn[i] < 0) {
		    if (pnn[i] - nninc > ROFFERR) {
	                printf("%g\t%g\n", mult*nninc, 100*(double)sum/totn);
			nninc += inc;
			while (pnn[i] - nninc > ROFFERR) {
	                    printf("%g\t%g\n", mult*nninc,
				   100*(double)sum/totn);
			    nninc += inc;
			}
		    }
		    sum++;
	        }
	        else if (pnn[i] == 0 && pnn[i-1] != 0) {
		    printf("%g\t%g\n", mult*nninc, 100*(double)sum/totn);
		    sum = 1;
	        }
	        else if (pnn[i] == 0 && pnn[i-1] == 0)
		    sum++;
	        else {
		    if (pnn[i] - nninc > ROFFERR) {
		        printf("%g\t%g\n", mult*nninc,
			       100*(1-(double)sum/totp));
		        nninc += inc;
		        while (pnn[i] - nninc > ROFFERR) {
		            printf("%g\t%g\n", mult*nninc,
				   100*(1-(double)sum/totp));
		            nninc += inc;
			}
		    }
		    sum++;
	        }
	    }
	    printf("%g\t%g\n", mult*nninc, 100*(1-(double)sum/totp));
	}
    }

    else {  /* unsigned distributions */
        if (inc == 0) {
            for (sum = i = 1; i < tot; i++) {
	        if (pnn[i] - pnn[i-1] > ROFFERR)
	            printf("%g\t%g\n", mult*pnn[i-1], 100*(1-(double)sum/tot));
	        sum++;
            }
            printf("%g\t%g\n", mult*pnn[i-1], 100*(1-(double)sum/tot));
        }
	else {  /* inc > 0 */
            for (nninc = 0, sum = i = 0; i < tot; i++) {
	        if (pnn[i] - nninc > ROFFERR) {
	            printf("%g\t%g\n", mult*nninc, 100*(1-(double)sum/tot));
		    nninc += inc;
		    while (pnn[i] - nninc > ROFFERR) {
	                printf("%g\t%g\n", mult*nninc,
			       100*(1-(double)sum/tot));
		        nninc += inc;
		    }
		}
	        sum++;
            }
            printf("%g\t%g\n", mult*nninc, 100*(1-(double)sum/tot));
	}
    }
    if (pnn) free(pnn);
    exit(0);
}


int dblcmp(y1, y2)
double *y1, *y2;
{

    if (*y1 < *y2)
        return(-1);
    else if (*y1 > *y2)
        return(1);
    else
        return(0);

}

char *prog_name(s)
char *s;
{
    char *p = s + strlen(s);

#ifdef MSDOS
    while (p >= s && *p != '\\' && *p != ':') {
	if (*p == '.')
	    *p = '\0';		/* strip off extension */
	if ('A' <= *p && *p <= 'Z')
	    *p += 'a' - 'A';	/* convert to lower case */
	p--;
    }
#else
    while (p >= s && *p != '/')
	p--;
#endif
    return (p+1);
}

static char *help_strings[] = {
    "usage: %s [OPTIONS ...]\n",
 " Takes as standard input an annotation interval list, containing intervals",
 " in seconds and the (beat and non-beat) annotations that terminate each",
 " interval; and outputs on standard output each unique NN increment (x) in",
 " milliseconds, and the percentage of NN interval increments (pNNx) greater",
 " than x.",
 " ",
 "OPTIONS may include:",
 " -h      print this usage summary",
 " -i INC  compute and output pNNx for x = 0, INC, 2*INC, ... milliseconds",
 " -p      compute and output increments as percentage of initial intervals",
 " -s      compute and output separate distributions of positive and negative",
 "          intervals",
 " ",
 "For further information, see http://www.physionet.org/physiotools/pNNx/ .",
NULL
};

void help()
{
    int i;

    (void)fprintf(stderr, help_strings[0], pname);
    for (i = 1; help_strings[i] != NULL; i++)
	(void)fprintf(stderr, "%s\n", help_strings[i]);
}
