package com.smppw.utils;

import java.util.ArrayList;
import java.util.List;

/**
 * 
 * @author Yu Liu
 * @version 2010-11-15
 */
public class Statistics {
	public static double sum(List<Double> ts) {
		return sum(ts, ts.size());
	}

	private static double sum(List<Double> ts, int size) {
		return sum(ts, 0, size);
	}

	private static double sum(List<Double> ts, int start, int end) {
		double sum = 0;
		if (start < end && end <= ts.size()) {
			for (int i = start; i < end; ++i) {
				sum += ts.get(i);
			}
		}
		return sum;
	}

	public static double mean(List<Double> ts) {
		return mean(ts, ts.size());
	}

	private static double mean(List<Double> ts, int size) {
		if (size <= 0)
			return 0.0;

		return sum(ts, size) / size;
	}

	private static double mean(List<Double> ts, int start, int end) {
		if (start <= end)
			return 0.0;

		return sum(ts, start, end) / (end - start);
	}

	private static double stdDev(List<Double> ts, int type) {
		double avg = mean(ts);
		return stdDev(ts, avg, type);
	}

	private static double stdDev(List<Double> ts, int size, int type) {
		double avg = mean(ts, size);
		return stdDev(ts, size, avg, type);
	}

	private static double stdDev(List<Double> ts, int start, int end, int type) {
		double avg = mean(ts, start, end);
		return stdDev(ts, start, end, avg, type);
	}

	private static double stdDev(List<Double> ts, double avg, int type) {
		if (ts.size() <= 1)
			return 0.0;

		return stdDev(ts, ts.size(), avg, type);
	}

	private static double stdDev(List<Double> ts, int size, double avg, int type) {
		return stdDev(ts, 0, size, avg, type);
	}

	private static double stdDev(List<Double> ts, int start, int end, double avg, int type) {
		double result = 0;
		if (start < end && end <= ts.size()) {
			for (int i = start; i < end; ++i) {
				result += (ts.get(i) - avg) * (ts.get(i) - avg);
			}

			result = Math.sqrt(result / (end - start - type));
		}

		return result;
	}

	public static double stdDevUnbiased(double[] ts) {
		List<Double> list = new ArrayList<>();
		for (double unit : ts) {
			list.add(unit);
		}
		return stdDev(list, 1);
	}

	// Unbiased
	public static double stdDevUnbiased(List<Double> ts) {
		return stdDev(ts, 1);
	}

	// Unbiased
	public static double stdDevUnbiased(List<Double> ts, int size) {
		return stdDev(ts, size, 1);
	}

	// Unbiased
	public static double stdDevUnbiased(List<Double> ts, int start, int end) {
		return stdDev(ts, start, end, 1);
	}

	// Biased
	public static double stdDevBiased(List<Double> ts) {
		return stdDev(ts, 0);
	}

	// Biased
	public static double stdDevBiased(List<Double> ts, int size) {
		return stdDev(ts, size, 0);
	}

	// Biased
	@SuppressWarnings("unused")
	private static double stdDevBiased(List<Double> ts, int start, int end) {
		return stdDev(ts, start, end, 0);
	}

	static class SumStdDev {
		double sum;
		double stdDev;
	}

	@SuppressWarnings("unused")
	private static SumStdDev sumAndStdDevBiased(List<Double> ts) {
		SumStdDev res = new SumStdDev();
		if (ts.size() <= 1)
			return res;

		double sum = sum(ts);
		double avg = sum / ts.size();
		double stdDev = 0;
		for (int i = 0; i < ts.size(); ++i) {
			stdDev += (ts.get(i) - avg) * (ts.get(i) - avg);
		}

		// I am biased.
		stdDev = Math.sqrt(stdDev / ts.size());

		res.sum = sum;
		res.stdDev = stdDev;

		return res;
	}

	public static double correlation(List<Double> ts1, List<Double> ts2) {
		return correlation(ts1, ts2, Math.min(ts1.size(), ts2.size()));
	}

	private static double correlation(List<Double> ts1, List<Double> ts2, int size) {
		return correlation(ts1, ts2, 0, size);
	}

	private static double correlation(List<Double> ts1, List<Double> ts2, int start, int end) {
		if (end == start)
			return 0.0;
		int n = end - start;
		double sum1 = sum(ts1, start, end);
		double sum2 = sum(ts2, start, end);
		// The performance is low. TODO change to sumAndStdDevBiased.
		// double stdDev1 = stdDevBiased(ts1, ts.size());
		// double stdDev2 = stdDevBiased(ts2, ts.size());
		double stdDev1 = stdDev(ts1, start, end, sum1 / n, 0);
		double stdDev2 = stdDev(ts2, start, end, sum2 / n, 0);
		if (stdDev1 == 0 || stdDev2 == 0) {
			return 0.0;
		}
		double temp = 0;
		for (int i = start; i < end; ++i)
			temp += ts1.get(i) * ts2.get(i);
		double cov = temp / n - sum1 / n * sum2 / n;
		double cor = cov / stdDev1 / stdDev2;

		return Math.min(1.0, cor);
	}

	public static List<Double> correlationPeriod(List<Double> ts1, List<Double> ts2, int period) {
		if (period < 2)
			return null;

		// This is independence block with its size is period.
		/*
		 * int num = Math.min(ts1.size(), ts2.size())/period; List<Double> cor =
		 * new ArrayList<Double>(); for (int i=0; i<num; ++i)
		 * cor.add(correlation(ts1, ts2, i*period, (i+1)*period));
		 */

		int num = Math.min(ts1.size(), ts2.size());
		if (num < period)
			return null;
		// The block is increase step is 1 and its size is period.
		num = num - period + 1;
		List<Double> cor = new ArrayList<Double>();
		for (int i = 0; i < num; ++i)
			cor.add(correlation(ts1, ts2, i, i + period));

		return cor;
	}

	public static List<Double> correlationGrowth(List<Double> ts1, List<Double> ts2) {
		int num = Math.min(ts1.size(), ts2.size()) - 1;
		List<Double> cor = new ArrayList<Double>();
		for (int i = 0; i < num; ++i)
			cor.add(correlation(ts1, ts2, i));

		return cor;
	}
}
