package org.spview.peakfinding;
/*
	Copyright (C) 2001, 2006 by Simon Dixon

	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 (the file gpl.txt); if not, download it from
	http://www.gnu.org/licenses/gpl.txt or write to the
	Free Software Foundation, Inc.,
	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

import java.util.LinkedList;

public class Peaks {

	public static boolean debug = false;
	public static int pre = 3;
	public static int post = 1;

	/**
	 * General peak picking method for finding n local maxima in an array
	 * 
	 * @param data  input data
	 * @param peaks list of peak indexes
	 * @param width minimum distance between peaks
	 * @return The number of peaks found
	 */
	public static int findPeaks(double[] data, int[] peaks, int width) {
		int peakCount = 0;
		int maxp = 0;
		int mid = 0;
		int end = data.length;
		while (mid < end) {
			int i = mid - width;
			if (i < 0)
				i = 0;
			int stop = mid + width + 1;
			if (stop > data.length)
				stop = data.length;
			maxp = i;
			for (i++; i < stop; i++)
				if (data[i] > data[maxp])
					maxp = i;
			if (maxp == mid) {
				int j;
				for (j = peakCount; j > 0; j--) {
					if (data[maxp] <= data[peaks[j - 1]])
						break;
					else if (j < peaks.length)
						peaks[j] = peaks[j - 1];
				}
				if (j != peaks.length)
					peaks[j] = maxp;
				if (peakCount != peaks.length)
					peakCount++;
			}
			mid++;
		}
		return peakCount;
	} // findPeaks()

	/**
	 * General peak picking method for finding local maxima in an array
	 * 
	 * @param data      input data
	 * @param width     minimum distance between peaks
	 * @param threshold minimum value of peaks
	 * @return list of peak indexes
	 */
	public static LinkedList<Integer> findPeaks(double[] data, int width, double threshold) {
		return findPeaks(data, width, threshold, 0, false);
	} // findPeaks()

	/**
	 * General peak picking method for finding local maxima in an array
	 * 
	 * @param data       input data
	 * @param width      minimum distance between peaks
	 * @param threshold  minimum value of peaks
	 * @param decayRate  how quickly previous peaks are forgotten
	 * @param isRelative minimum value of peaks is relative to local average
	 * @return list of peak indexes
	 */
	public static LinkedList<Integer> findPeaks(double[] data, int width, double threshold, double decayRate,
			boolean isRelative) {
		LinkedList<Integer> peaks = new LinkedList<Integer>();
		int maxp = 0;
		int mid = 0;
		int end = data.length;
		double av = data[0];
		while (mid < end) {
			av = decayRate * av + (1 - decayRate) * data[mid];
			if (av < data[mid])
				av = data[mid];
			int i = mid - width;
			if (i < 0)
				i = 0;
			int stop = mid + width + 1;
			if (stop > data.length)
				stop = data.length;
			maxp = i;
			for (i++; i < stop; i++)
				if (data[i] > data[maxp])
					maxp = i;
			if (maxp == mid) {
				if (overThreshold(data, maxp, width, threshold, isRelative, av)) {
					if (debug)
						System.out.println(" peak");
					peaks.add(new Integer(maxp));
				} else if (debug)
					System.out.println();
			}
			mid++;
		}
		return peaks;
	} // findPeaks()

	public static double expDecayWithHold(double av, double decayRate, double[] data, int start, int stop) {
		while (start < stop) {
			av = decayRate * av + (1 - decayRate) * data[start];
			if (av < data[start])
				av = data[start];
			start++;
		}
		return av;
	} // expDecayWithHold()

	public static boolean overThreshold(double[] data, int index, int width, double threshold, boolean isRelative,
			double av) {
		if (debug)
			System.out.printf("%4d : %6.3f     Av1: %6.3f    ", index, data[index], av);
		if (data[index] < av)
			return false;
		if (isRelative) {
			int iStart = index - pre * width;
			if (iStart < 0)
				iStart = 0;
			int iStop = index + post * width;
			if (iStop > data.length)
				iStop = data.length;
			double sum = 0;
			int count = iStop - iStart;
			while (iStart < iStop)
				sum += data[iStart++];
			if (debug)
				System.out.printf("    %6.3f    %6.3f   ", sum / count, data[index] - sum / count - threshold);
			return (data[index] > sum / count + threshold);
		} else
			return (data[index] > threshold);
	} // overThreshold()

	public static void normalise(double[] data) {
		double sx = 0;
		double sxx = 0;
		for (int i = 0; i < data.length; i++) {
			sx += data[i];
			sxx += data[i] * data[i];
		}
		double mean = sx / data.length;
		double sd = Math.sqrt((sxx - sx * mean) / data.length);
		if (sd == 0)
			sd = 1; // all data[i] == mean -> 0; avoids div by 0
		for (int i = 0; i < data.length; i++) {
			data[i] = (data[i] - mean) / sd;
		}
	} // normalise()

	/**
	 * Uses an n-point linear regression to estimate the slope of data.
	 * 
	 * @param data  input data
	 * @param hop   spacing of data points
	 * @param n     length of linear regression
	 * @param slope output data
	 */
	public static void getSlope(double[] data, double hop, int n, double[] slope) {
		int i = 0, j = 0;
		double t;
		double sx = 0, sxx = 0, sy = 0, sxy = 0;
		for (; i < n; i++) {
			t = i * hop;
			sx += t;
			sxx += t * t;
			sy += data[i];
			sxy += t * data[i];
		}
		double delta = n * sxx - sx * sx;
		for (; j < n / 2; j++)
			slope[j] = (n * sxy - sx * sy) / delta;
		for (; j < data.length - (n + 1) / 2; j++, i++) {
			slope[j] = (n * sxy - sx * sy) / delta;
			sy += data[i] - data[i - n];
			sxy += hop * (n * data[i] - sy);
		}
		for (; j < data.length; j++)
			slope[j] = (n * sxy - sx * sy) / delta;
	} // getSlope()

	public static double min(double[] arr) {
		return arr[imin(arr)];
	}

	public static double max(double[] arr) {
		return arr[imax(arr)];
	}

	public static int imin(double[] arr) {
		int i = 0;
		for (int j = 1; j < arr.length; j++)
			if (arr[j] < arr[i])
				i = j;
		return i;
	} // imin()

	public static int imax(double[] arr) {
		int i = 0;
		for (int j = 1; j < arr.length; j++)
			if (arr[j] > arr[i])
				i = j;
		return i;
	} // imax()

} // class Peaks
