/* Sampled.c
 *
 * Copyright (C) 1992-2004 Paul Boersma
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * pb 2002/07/16 GPL
 * pb 2004/10/23 made Sampled_getWindowSamples resistant to large times
 * pb 2004/10/24 getValueAtSample
 * pb 2004/10/31 Function ranges
 */

#include <math.h>
#include "Sampled.h"

#include "oo_COPY.h"
#include "Sampled_def.h"
#include "oo_EQUAL.h"
#include "Sampled_def.h"
#include "oo_WRITE_ASCII.h"
#include "Sampled_def.h"
#include "oo_READ_ASCII.h"
#include "Sampled_def.h"
#include "oo_WRITE_BINARY.h"
#include "Sampled_def.h"
#include "oo_READ_BINARY.h"
#include "Sampled_def.h"
#include "oo_DESCRIPTION.h"
#include "Sampled_def.h"

static double getNx (I) { iam (Sampled); return my nx; }
static double getDx (I) { iam (Sampled); return my dx; }
static double getX (I, long ix) { iam (Sampled); return my x1 + (ix - 1) * my dx; }

static double getValueAtSample (I, long isamp, long which, int units) {
	iam (Sampled);
	(void) isamp;
	(void) which;
	(void) units;
	return NUMundefined;
}

static double backToStandardUnits (I, double value, long which, int units) {
	iam (Sampled);
	(void) which;
	(void) units;
	return value;
}

class_methods (Sampled, Function)
	class_method_local (Sampled, copy)
	class_method_local (Sampled, equal)
	class_method_local (Sampled, writeBinary)
	class_method_local (Sampled, readBinary)
	class_method_local (Sampled, writeAscii)
	class_method_local (Sampled, readAscii)
	class_method_local (Sampled, description)
	class_method (getNx)
	class_method (getDx)
	class_method (getX)
	class_method (getValueAtSample)
	class_method (backToStandardUnits)
class_methods_end

double Sampled_indexToX (I, long i) {
	iam (Sampled);
	return my x1 + (i - 1) * my dx;
}

double Sampled_xToIndex (I, double x) {
	iam (Sampled);
	return (x - my x1) / my dx + 1;
}

long Sampled_xToLowIndex (I, double x) {
	iam (Sampled);
	return (long) floor ((x - my x1) / my dx) + 1;
}

long Sampled_xToHighIndex (I, double x) {
	iam (Sampled);
	return (long) ceil ((x - my x1) / my dx) + 1;
}

long Sampled_xToNearestIndex (I, double x) {
	iam (Sampled);
	return (long) floor ((x - my x1) / my dx + 1.5);
}

long Sampled_getWindowSamples (I, double xmin, double xmax, long *ixmin, long *ixmax) {
	iam (Sampled);
	double rixmin = 1.0 + ceil ((xmin - my x1) / my dx);
	double rixmax = 1.0 + floor ((xmax - my x1) / my dx);
	*ixmin = rixmin < 1.0 ? 1 : (long) rixmin;
	*ixmax = rixmax > (double) my nx ? my nx : (long) rixmax;
	if (*ixmin > *ixmax) return 0;
	return *ixmax - *ixmin + 1;
}

int Sampled_init (I, double xmin, double xmax, long nx, double dx, double x1) {
	iam (Sampled);
	my xmin = xmin; my xmax = xmax; my nx = nx; my dx = dx; my x1 = x1;
	return 1;
}

int Sampled_shortTermAnalysis (I, double windowDuration, double timeStep, long *numberOfFrames, double *firstTime) {
	iam (Sampled);
	double myDuration, thyDuration, ourMidTime;
	Melder_assert (windowDuration > 0.0);
	Melder_assert (timeStep > 0.0);
	myDuration = my dx * my nx;
	if (windowDuration > myDuration)
		return Melder_error ("%s shorter than window length.", Thing_className (me)); 
	*numberOfFrames = floor ((myDuration - windowDuration) / timeStep) + 1;
	Melder_assert (*numberOfFrames >= 1);
	ourMidTime = my x1 - 0.5 * my dx + 0.5 * myDuration;
	thyDuration = *numberOfFrames * timeStep;
	*firstTime = ourMidTime - 0.5 * thyDuration + 0.5 * timeStep;
	return 1;
}

double Sampled_getValueAtSample (I, long isamp, long which, int units) {
	iam (Sampled);
	if (isamp < 1 || isamp > my nx) return NUMundefined;
	return our getValueAtSample (me, isamp, which, units);
}

double Sampled_getValueAtX (I, double x, long which, int units, int interpolate) {
	iam (Sampled);
	if (x < my xmin || x > my xmax) return NUMundefined;
	if (interpolate) {
		double ireal = Sampled_xToIndex (me, x), phase, fnear, ffar;
		long ileft = floor (ireal), inear, ifar;
		phase = ireal - ileft;
		if (phase < 0.5) {
			inear = ileft, ifar = ileft + 1;
		} else {
			ifar = ileft, inear = ileft + 1;
			phase = 1.0 - phase;
		}
		if (inear < 1 || inear > my nx) return NUMundefined;   /* X out of range? */
		fnear = our getValueAtSample (me, inear, which, units);
		if (fnear == NUMundefined) return NUMundefined;   /* Function value not defined? */
		if (ifar < 1 || ifar > my nx) return fnear;   /* At edge? Extrapolate. */
		ffar = our getValueAtSample (me, ifar, which, units);
		if (ffar == NUMundefined) return fnear;   /* Neighbour undefined? Extrapolate. */
		return fnear + phase * (ffar - fnear);   /* Interpolate. */
	}
	return Sampled_getValueAtSample (me, Sampled_xToNearestIndex (me, x), which, units);
}

double Sampled_getQuantile (I, double xmin, double xmax, double quantile, long which, int units) {
	iam (Sampled);
	long i, imin, imax, numberOfDefinedSamples = 0;
	double *values = NUMdvector (1, my nx), result = NUMundefined;
	iferror return NUMundefined;
	Function_unidirectionalAutowindow (me, & xmin, & xmax);
	if (! Function_intersectRangeWithDomain (me, & xmin, & xmax)) return NUMundefined;
	Sampled_getWindowSamples (me, xmin, xmax, & imin, & imax);
	for (i = imin; i <= imax; i ++) {
		double value = our getValueAtSample (me, i, which, units);
		if (NUMdefined (value)) {
			values [++ numberOfDefinedSamples] = value;
		}
	}
	if (numberOfDefinedSamples >= 1) {
		NUMsort_d (numberOfDefinedSamples, values);
		result = NUMquantile_d (numberOfDefinedSamples, values, quantile);
	}
	NUMdvector_free (values, 1);
	return result;
}

static double getMeanWithoutInterpolation (Sampled me, double xmin, double xmax, long which, int units) {
	double definitionRange = 0.0, sum = 0.0;
	double rileft = Sampled_xToIndex (me, xmin), riright = Sampled_xToIndex (me, xmax);
	long ileft = rileft < 0.5 ? 0 : (long) floor (rileft + 0.5);
	long iright = riright >= my nx + 0.5 ? my nx + 1 : (long) floor (riright + 0.5);
	long isamp;
	if (riright < 0.5 || rileft >= my nx + 0.5) return NUMundefined;
	if (ileft == iright) {
		double value = our getValueAtSample (me, ileft, which, units);
		if (NUMdefined (value)) {
			double phase = riright - rileft;
			definitionRange += phase;
			sum += phase * value;
		}
	} else {
		if (ileft >= 1) {
			double value = our getValueAtSample (me, ileft, which, units);
			if (NUMdefined (value)) {
				double phase = ileft - rileft + 0.5;
				definitionRange += phase;
				sum += phase * value;
			}
		}
		if (iright <= my nx) {
			double value = our getValueAtSample (me, iright, which, units);
			if (NUMdefined (value)) {
				double phase = riright - iright + 0.5;
				definitionRange += phase;
				sum += phase * value;
			}
		}
	}
	for (isamp = ileft + 1; isamp < iright; isamp ++) {
		double value = our getValueAtSample (me, isamp, which, units);
		if (NUMdefined (value)) {
			definitionRange += 1.0;
			sum += value;
		}
	}
	return definitionRange == 0.0 ? NUMundefined : sum / definitionRange;
}

double Sampled_getMean (I, double xmin, double xmax, long which, int units, int interpolate) {
	/*
		This function computes the area under the linearly interpolated curve between xmin and xmax.
		Outside [x1-dx/2, xN+dx/2], the curve is undefined and neither times nor values are counted.
		In [x1-dx/2,x1] and [xN,xN+dx/2], the curve is linearly extrapolated.
	*/
	iam (Sampled);
	long imin, imax, isamp;
	double sum = 0.0;
	double leftEdge, rightEdge, definitionRange = 0.0;
	Function_unidirectionalAutowindow (me, & xmin, & xmax);
	if (! Function_intersectRangeWithDomain (me, & xmin, & xmax)) return NUMundefined;
	if (! interpolate) return getMeanWithoutInterpolation (me, xmin, xmax, which, units);
	Sampled_getWindowSamples (me, xmin, xmax, & imin, & imax);   /* If this returns 0, we will catch it later. */
	for (isamp = imin; isamp <= imax; isamp ++) {
		double value = our getValueAtSample (me, isamp, which, units);   /* A fast way to integrate a linearly interpolated curve; works everywhere except at the edges. */
		if (NUMdefined (value)) {
			definitionRange += 1.0;
			sum += value;
		}
	}
	if (definitionRange == 0.0) {
		/*
		 * This can occur in two situations:
		 * 1. No sample centres between tmin and tmax (i.e. Sampled_getWindowSamples returned 0).
		 *    Try to return the mean of the interpolated values at these two points.
		 *    Thus, small (tmin,tmax) ranges give the same values as the (tmin+tmax)/2 points.
		 * 2. No defined sample centres between tmin and tmax.
		 *    There could be defined values at the edges, though, during 0.3 samples or so.
		 * In both cases, the following will work.
		 */
		double valueLeft = Sampled_getValueAtX (me, xmin, which, units, 1);
		double valueRight = Sampled_getValueAtX (me, xmax, which, units, 1);
		return valueLeft == NUMundefined ? valueRight : valueRight == NUMundefined ? valueLeft : 0.5 * (valueLeft + valueRight);
	}
	/*
	 * Corrections within the first and last sampling intervals.
	 */
	leftEdge = my x1 - 0.5 * my dx, rightEdge = leftEdge + my nx * my dx;
	if (xmin <= leftEdge) {
		;   /* Constant extrapolation over 0.5 sample is OK. */
	} else {
		double phase = (my x1 + (imin - 1) * my dx - xmin) / my dx;   /* This fraction of sampling interval is still to be determined. */
		double rightValue = our getValueAtSample (me, imin, which, units);
		if (NUMdefined (rightValue)) {
			definitionRange -= 0.5;   /* Delete constant extrapolation over 0.5 sample. */
			sum -= 0.5 * rightValue;
			if (imin == 1) {
				/* Add constant extrapolation over current fraction. If we are here, phase must be <= 0.5. */
				definitionRange += phase;   /* Add current fraction. */
				sum += phase * rightValue;
			} else {
				double leftValue = our getValueAtSample (me, imin - 1, which, units);
				if (NUMdefined (leftValue)) {
					definitionRange += phase;   /* Add current fraction. */
					sum += phase * (rightValue + 0.5 * phase * (leftValue - rightValue));   /* Interpolate to outside sample. */
				} else {
					if (phase > 0.5) phase = 0.5;
					definitionRange += phase;   /* Add current fraction, but never more than 0.5. */
					sum += phase * rightValue;
				}
			}
		} else if (phase > 0.5 && imin > 1) {
			double leftValue = our getValueAtSample (me, imin - 1, which, units);
			if (NUMdefined (leftValue)) {
				definitionRange += phase - 0.5;
				sum += (phase - 0.5) * leftValue;
			}
		}
	}
	if (xmax >= rightEdge) {
		;   /* Constant extrapolation is OK. */
	} else {
		double phase = (xmax - (my x1 + (imax - 1) * my dx)) / my dx;   /* This fraction of sampling interval is still to be determined. */
		double leftValue = our getValueAtSample (me, imax, which, units);
		if (NUMdefined (leftValue)) {
			definitionRange -= 0.5;   /* Delete constant extrapolation over 0.5 sample. */
			sum -= 0.5 * leftValue;
			if (imax == my nx) {
				/* Add constant extrapolation over current fraction. If we are here, phase must be <= 0.5. */
				definitionRange += phase;   /* Add current fraction. */
				sum += phase * leftValue;
			} else {
				double rightValue = our getValueAtSample (me, imax + 1, which, units);
				if (NUMdefined (rightValue)) {
					definitionRange += phase;   /* Add current fraction. */
					sum += phase * (leftValue + 0.5 * phase * (rightValue - leftValue));   /* Interpolate to outside sample. */
				} else {
					if (phase > 0.5) phase = 0.5;
					definitionRange += phase;   /* Add current fraction, but never more than 0.5. */
					sum += phase * leftValue;
				}
			}
		} else if (phase > 0.5 && imax < my nx) {
			double rightValue = our getValueAtSample (me, imax + 1, which, units);
			if (NUMdefined (rightValue)) {
				definitionRange += phase - 0.5;
				sum += (phase - 0.5) * rightValue;
			}
		}
	}
	return sum / definitionRange;
}

double Sampled_getMean_standardUnits (I, double xmin, double xmax, long which, int averagingUnits, int interpolate) {
	iam (Sampled);
	return our backToStandardUnits (me, Sampled_getMean (me, xmin, xmax, which, averagingUnits, interpolate), which, averagingUnits);
}

void Sampled_getMinimumAndX (I, double xmin, double xmax, long which, int units, int interpolate,
	double *return_minimum, double *return_xOfMinimum)
{
	iam (Sampled);
	long imin, imax, i;
	double minimum = 1e301, xOfMinimum = 0.0;
	if (xmin == NUMundefined || xmax == NUMundefined) {
		minimum = xOfMinimum = NUMundefined;
		goto end;
	}
	Function_unidirectionalAutowindow (me, & xmin, & xmax);
	if (! Function_intersectRangeWithDomain (me, & xmin, & xmax)) {
		minimum = xOfMinimum = NUMundefined;   /* Requested range and logical domain do not intersect. */
		goto end;
	}
	if (! Sampled_getWindowSamples (me, xmin, xmax, & imin, & imax)) {
		/*
		 * No sample centres between xmin and xmax.
		 * Try to return the lesser of the values at these two points.
		 */
		double fleft = Sampled_getValueAtX (me, xmin, which, units, interpolate);
		double fright = Sampled_getValueAtX (me, xmax, which, units, interpolate);
		if (NUMdefined (fleft) && fleft < minimum) minimum = fleft, xOfMinimum = xmin;
		if (NUMdefined (fright) && fright < minimum) minimum = fright, xOfMinimum = xmax;
	} else {
		for (i = imin; i <= imax; i ++) {
			double fmid = our getValueAtSample (me, i, which, units);
			if (fmid == NUMundefined) continue;
			if (interpolate == FALSE) {
				if (fmid < minimum) minimum = fmid, xOfMinimum = i;
			} else {
				/*
				 * Try an interpolation, possibly even taking into account a sample just outside the selection.
				 */
				double fleft = i <= 1 ? NUMundefined : our getValueAtSample (me, i - 1, which, units);
				double fright = i >= my nx ? NUMundefined : our getValueAtSample (me, i + 1, which, units);
				if (fleft == NUMundefined || fright == NUMundefined) {
					if (fmid < minimum) minimum = fmid, xOfMinimum = i;
				} else if (fmid < fleft && fmid <= fright) {
					double y [4], i_real, localMinimum;
					y [1] = fleft, y [2] = fmid, y [3] = fright;
					localMinimum = NUMimproveMinimum_d (y, 3, 2, NUM_PEAK_INTERPOLATE_PARABOLIC, & i_real);
					if (localMinimum < minimum)
						minimum = localMinimum, xOfMinimum = i_real + i - 2;
				}
			}
		}
		xOfMinimum = my x1 + (xOfMinimum - 1) * my dx;   /* From index plus phase to time. */
		/* Check boundary values. */
		if (interpolate) {
			double fleft = Sampled_getValueAtX (me, xmin, which, units, TRUE);
			double fright = Sampled_getValueAtX (me, xmax, which, units, TRUE);
			if (NUMdefined (fleft) && fleft < minimum) minimum = fleft, xOfMinimum = xmin;
			if (NUMdefined (fright) && fright < minimum) minimum = fright, xOfMinimum = xmax;
		}
		if (xOfMinimum < xmin) xOfMinimum = xmin;
		if (xOfMinimum > xmax) xOfMinimum = xmax;
	}
	if (minimum == 1e301) minimum = xOfMinimum = NUMundefined;
end:
	if (return_minimum) *return_minimum = minimum;
	if (return_xOfMinimum) *return_xOfMinimum = xOfMinimum;
}

double Sampled_getMinimum (I, double xmin, double xmax, long which, int units, int interpolate) {
	iam (Sampled);
	double minimum;
	Sampled_getMinimumAndX (me, xmin, xmax, which, units, interpolate, & minimum, NULL);
	return minimum;
}

double Sampled_getXOfMinimum (I, double xmin, double xmax, long which, int units, int interpolate) {
	iam (Sampled);
	double time;
	Sampled_getMinimumAndX (me, xmin, xmax, which, units, interpolate, NULL, & time);
	return time;
}

void Sampled_getMaximumAndX (I, double xmin, double xmax, long which, int units, int interpolate,
	double *return_maximum, double *return_xOfMaximum)
{
	iam (Sampled);
	long imin, imax, i;
	double maximum = -1e301, xOfMaximum = 0.0;
	if (xmin == NUMundefined || xmax == NUMundefined) {
		maximum = xOfMaximum = NUMundefined;
		goto end;
	}
	Function_unidirectionalAutowindow (me, & xmin, & xmax);
	if (! Function_intersectRangeWithDomain (me, & xmin, & xmax)) {
		maximum = xOfMaximum = NUMundefined;   /* Requested range and logical domain do not intersect. */
		goto end;
	}
	if (! Sampled_getWindowSamples (me, xmin, xmax, & imin, & imax)) {
		/*
		 * No sample centres between tmin and tmax.
		 * Try to return the greater of the values at these two points.
		 */
		double fleft = Sampled_getValueAtX (me, xmin, which, units, interpolate);
		double fright = Sampled_getValueAtX (me, xmax, which, units, interpolate);
		if (NUMdefined (fleft) && fleft > maximum) maximum = fleft, xOfMaximum = xmin;
		if (NUMdefined (fright) && fright > maximum) maximum = fright, xOfMaximum = xmax;
	} else {
		for (i = imin; i <= imax; i ++) {
			double fmid = our getValueAtSample (me, i, which, units);
			if (fmid == NUMundefined) continue;
			if (interpolate == FALSE) {
				if (fmid > maximum) maximum = fmid, xOfMaximum = i;
			} else {
				/*
				 * Try an interpolation, possibly even taking into account a sample just outside the selection.
				 */
				double fleft = i <= 1 ? NUMundefined : our getValueAtSample (me, i - 1, which, units);
				double fright = i >= my nx ? NUMundefined : our getValueAtSample (me, i + 1, which, units);
				if (fleft == NUMundefined || fright == NUMundefined) {
					if (fmid > maximum) maximum = fmid, xOfMaximum = i;
				} else if (fmid > fleft && fmid >= fright) {
					double y [4], i_real, localMaximum;
					y [1] = fleft, y [2] = fmid, y [3] = fright;
					localMaximum = NUMimproveMaximum_d (y, 3, 2, NUM_PEAK_INTERPOLATE_PARABOLIC, & i_real);
					if (localMaximum > maximum)
						maximum = localMaximum, xOfMaximum = i_real + i - 2;
				}
			}
		}
		xOfMaximum = my x1 + (xOfMaximum - 1) * my dx;   /* From index plus phase to time. */
		/* Check boundary values. */
		if (interpolate) {
			double fleft = Sampled_getValueAtX (me, xmin, which, units, TRUE);
			double fright = Sampled_getValueAtX (me, xmax, which, units, TRUE);
			if (NUMdefined (fleft) && fleft > maximum) maximum = fleft, xOfMaximum = xmin;
			if (NUMdefined (fright) && fright > maximum) maximum = fright, xOfMaximum = xmax;
		}
		if (xOfMaximum < xmin) xOfMaximum = xmin;
		if (xOfMaximum > xmax) xOfMaximum = xmax;
	}
	if (maximum == -1e301) maximum = xOfMaximum = NUMundefined;
end:
	if (return_maximum) *return_maximum = maximum;
	if (return_xOfMaximum) *return_xOfMaximum = xOfMaximum;
}

double Sampled_getMaximum (I, double xmin, double xmax, long which, int units, int interpolate) {
	iam (Sampled);
	double maximum;
	Sampled_getMaximumAndX (me, xmin, xmax, which, units, interpolate, & maximum, NULL);
	return maximum;
}

double Sampled_getXOfMaximum (I, double xmin, double xmax, long which, int units, int interpolate) {
	iam (Sampled);
	double time;
	Sampled_getMaximumAndX (me, xmin, xmax, which, units, interpolate, NULL, & time);
	return time;
}

/* End of file Sampled.c */
