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

#include "../../src/util/memory.h"
#include "../../src/util/error.h"

// LINKER_OPTIONS -lm

#define statistics_IMPORT
#include "statistics.h"

struct statistics_Type {
	int n;
	double min_x;
	double max_x;
	double sum_x;
	double sum_x_2;
	int hist_enabled; // is histogram enabled?
	int hist_number_of_intervals;
	double hist_min, hist_max; // accounts values in [hist_min,hist_max[.
	int hist_samples; // no. of samples in the histogram
	int *hist_intervals; // histogram's bars
};


static void statistics_destruct(void *p)
{
	statistics_Type *this = p;
	memory_dispose(this->hist_intervals);
}


statistics_Type * statistics_new()
{
	statistics_Type *this = memory_allocate(sizeof(*this), statistics_destruct);
	this->n = 0;
	this->min_x = 0;
	this->max_x = 0;
	this->sum_x = 0;
	this->sum_x_2 = 0;
	this->hist_enabled = 0;
	this->hist_intervals = NULL;
	return this;
}


void statistics_put(statistics_Type *this, double x)
{
	if( this->n == 0 ){
		this->min_x = x;
		this->max_x = x;
	} else {
		if( x < this->min_x )
			this->min_x = x;
		else if( x > this->max_x )
			this->max_x = x;
	}
	this->sum_x += x;
	this->sum_x_2 += x * x;
	this->n++;
	if( this->n <= 0 )
		error_internal("counter overflow -- too much data", 0);
	if( this->hist_enabled ){
		double interval_number = floor( (x - this->hist_min) / (this->hist_max - this->hist_min) * this->hist_number_of_intervals );
		if( 0 <= interval_number && interval_number < this->hist_number_of_intervals ){
			this->hist_intervals[(int) interval_number]++;
			this->hist_samples++;
		}
	}
}


int statistics_count(statistics_Type *this)
{
	return this->n;
}


double statistics_min(statistics_Type *this)
{
	if( this->n == 0 )
		error_internal("no sample", 0);
	return this->min_x;
}


double statistics_max(statistics_Type *this)
{
	if( this->n == 0 )
		error_internal("no sample", 0);
	return this->max_x;
}


double statistics_mean(statistics_Type *this)
{
	if( this->n == 0 )
		error_internal("no sample", 0);
	return this->sum_x / this->n;
}


double statistics_variance(statistics_Type *this)
{
	if( this->n < 2 )
		error_internal("less than 2 samples available", 0);
	double m = statistics_mean(this);
	return (this->sum_x_2 + this->n * m * m
		- 2.0 * m * this->sum_x) / this->n;
}


double statistics_deviation(statistics_Type *this)
{
	return sqrt( statistics_variance(this) );
}


// Buffer for statistics_toString().
static char *statistics_s;


static void statistics_cleanup()
{
	memory_dispose(statistics_s);
}

char * statistics_toString(statistics_Type *this)
{
	if( statistics_count(this) == 0 )
		return "n=0";
	
	if( statistics_s == NULL ){
		statistics_s = memory_allocate(200, NULL);
		memory_registerCleanup(statistics_cleanup);
	}
	
	snprintf(statistics_s, 200, "n=%d min=%g max=%g mean=%g",
		statistics_count(this),
		statistics_min(this),
		statistics_max(this),
		statistics_mean(this));
	
	if( statistics_count(this) >= 2 ){
		int len = strlen(statistics_s);
		snprintf(statistics_s + len, 200 - len, " dev=%g",
			statistics_deviation(this));
	}

	return statistics_s;
}


void statistics_histogramEnable(statistics_Type *this, double min, double max, int number_of_intervals)
{
	if( this->hist_enabled )
		error_internal("histogram already enabled", 0);
	if( this->n > 0 )
		error_internal("cannot enable histogram: there ar already samples!", 0);
	if( !(min < max) )
		error_internal("invalid range [%g,%g]", min, max);
	if( number_of_intervals < 1 )
		error_internal("invalid number of intervals: %d", number_of_intervals);
	this->hist_enabled = 1;
	this->hist_min = min;
	this->hist_max = max;
	this->hist_samples = 0;
	this->hist_number_of_intervals = number_of_intervals;
	this->hist_intervals = memory_allocate(number_of_intervals * sizeof(int), NULL);
	memset(this->hist_intervals, 0, number_of_intervals * sizeof(int));
}


/**
 * Returns the number of samples that fell in the range of the histogram.
 * Fatal error if histogram not enabled.
 * @param this Samples collector.
 * @return Number of samples that fell in the range of the histogram.
 */
int statistics_histogramNumberOfSamples(statistics_Type *this)
{
	if( ! this->hist_enabled )
		error_internal("histogram not enabled", 0);
	return this->hist_samples;
}


/**
 * Returns the number of samples that fell in the i-th interval of the histogram.
 * Fatal error if histogram not enabled.
 * Fatal error if the index is out of the range [0,number_of_intervals[.
 * @param this Samples collector.
 * @param i Index of the interval.
 * @return Number of samples that fell in the i-th interval of the histogram.
 */
int statistics_histogramBar(statistics_Type *this, int i)
{
	if( ! this->hist_enabled )
		error_internal("histogram not enabled", 0);
	if( !(0 <= i && i < this->hist_number_of_intervals) )
		error_internal("bar index out of the range: %d", i);
	return this->hist_intervals[i];
}


void statistics_histogramPrint(statistics_Type *this)
{
	if( ! this->hist_enabled )
		return;
	int max = 0;
	int i;
	for(i = 0; i < this->hist_number_of_intervals; i++)
		if( this->hist_intervals[i] > max )
			max = this->hist_intervals[i];
	double k = 0;
	if( max > 0 )
		k = 60.9 / max;
	double k_bar = 0;
	if( this->n > 0 )
		k_bar = 100.0 / this->n;
	printf("%g\n", this->hist_min);
	for(i = 0; i < this->hist_number_of_intervals; i++){
		printf("|");
		int bar_len = k * this->hist_intervals[i];
		int j;
		for(j = 0; j < bar_len; j++)
			printf("-");
		printf(" %g%%\n", k_bar * this->hist_intervals[i]);
	}
	printf("%g\n", this->hist_max);
}