/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "math.h"

#include "cras_util.h"
#include "rate_estimator.h"

/* The max rate skew that considered reasonable */
#define MAX_RATE_SKEW 100

static void least_square_reset(struct least_square *lsq)
{
	memset(lsq, 0, sizeof(*lsq));
}

void least_square_add_sample(struct least_square *lsq, double x, double y)
{
	lsq->sum_x += x;
	lsq->sum_y += y;
	lsq->sum_xy += x * y;
	lsq->sum_x2 += x * x;
	lsq->num_samples++;
}

double least_square_best_fit_slope(struct least_square *lsq)
{
	double num, denom;
	num = lsq->num_samples * lsq->sum_xy - lsq->sum_x * lsq->sum_y;
	denom = lsq->num_samples * lsq->sum_x2 - lsq->sum_x * lsq->sum_x;
	return num / denom;
}

void rate_estimator_destroy(struct rate_estimator *re)
{
	if (re)
		free(re);
}

struct rate_estimator *rate_estimator_create(unsigned int rate,
					     const struct timespec *window_size,
					     double smooth_factor)
{
	struct rate_estimator *re;

	re = (struct rate_estimator *)calloc(1, sizeof(*re));
	if (re == NULL)
		return NULL;

	re->window_size = *window_size;
	re->estimated_rate = rate;
	re->smooth_factor = smooth_factor;

	return re;
}

void rate_estimator_add_frames(struct rate_estimator *re, int fr)
{
	re->level_diff += fr;
}

double rate_estimator_get_rate(struct rate_estimator *re)
{
	return re->estimated_rate;
}

void rate_estimator_reset_rate(struct rate_estimator *re, unsigned int rate)
{
	re->estimated_rate = rate;
	least_square_reset(&re->lsq);
	re->window_start_ts.tv_sec = 0;
	re->window_start_ts.tv_nsec = 0;
	re->window_frames = 0;
	re->level_diff = 0;
	re->last_level = 0;
}

int rate_estimator_check(struct rate_estimator *re, int level,
			 struct timespec *now)
{
	struct timespec td;

	/* TODO(hychao) - is this the right thing to do if level is 0? */
	if ((re->window_start_ts.tv_sec == 0) || (level == 0)) {
		re->window_start_ts = *now;
		re->window_frames = 0;
		re->level_diff = 0;
		re->last_level = level;
		return 0;
	}

	subtract_timespecs(now, &re->window_start_ts, &td);
	re->window_frames += abs(re->last_level - level + re->level_diff);
	re->level_diff = 0;
	re->last_level = level;

	least_square_add_sample(&re->lsq,
				td.tv_sec + (double)td.tv_nsec / 1000000000L,
				re->window_frames);
	if (timespec_after(&td, &re->window_size) &&
	    re->lsq.num_samples > 1) {
		double rate = least_square_best_fit_slope(&re->lsq);
		if (fabs(re->estimated_rate - rate) < MAX_RATE_SKEW)
			re->estimated_rate = rate * (1 - re->smooth_factor) +
					re->smooth_factor * re->estimated_rate;
		least_square_reset(&re->lsq);
		re->window_start_ts = *now;
		re->window_frames = 0;
		return 1;
	}
	return 0;
}