/*************************************************************************/
/* Copyright (c) 2015 Linas Vepstas                                      */
/* All rights reserved                                                   */
/*                                                                       */
/* Use of the link grammar parsing system is subject to the terms of the */
/* license set forth in the LICENSE file included with this software.    */
/* This license allows free redistribution and use in source and binary  */
/* forms, with or without modification, subject to certain conditions.   */
/*                                                                       */
/*************************************************************************/

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

#if PERFORM_COUNT_HISTOGRAMMING
/* A histogram distribution of the parse counts. */

Count_bin hist_zero(void)
{
	static Count_bin zero
		= {0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0};
	return zero;
}

Count_bin hist_one(void)
{
	static Count_bin one
		= {0, 1, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0};
	return one;
}

#define BIN_WIDTH 0.334

/**
 * Accumulate counts in 'a', adding them to sum.
 * The histogram is shifted by the amount 'cost'.
 * That is, the bins are shifted over by the integer part of the cost
 * (scaled to the bin-width).
 */
void hist_accum(Count_bin* sum, double cost, const Count_bin* a)
{
	unsigned int i;
	unsigned int start;

	// Skip, if nothing to accumulate.
	if (0 == a->total) return;
	sum->total += a->total;

	// The cost tells us how much to offset the histogram in a,
	// before accumulating it.  'base' is the bin number of the first
	// non-empty bin.
	start = (unsigned int) floor (cost / BIN_WIDTH);
	if (0 == sum->bin[0])
	{
		sum->base = start;
		start = 0;
	}

	for (i = start; i < NUM_BINS; i++)
	{
		sum->bin[i] += a->bin[i-start];
	}
	for (i = NUM_BINS-start; i < NUM_BINS; i++)
	{
		sum->overrun += a->bin[i];
	}
	sum->overrun += a->overrun;
}

/** Same as above */
void hist_accumv(Count_bin* sum, double cost, const Count_bin a)
{
	hist_accum(sum, cost, &a);
}

/**
 * Create a product of two histogrammed counts.
 * Observe that doing so requires a kind-of cross-product to
 * be performed, thus, a nested double sum.
 */
void hist_prod(Count_bin* prod, const Count_bin* a, const Count_bin* b)
{
	unsigned int i, k;

	// Skip, if the product is zero.
	if (0 == a->total || 0 == b->total) return;
	prod->total = a->total * b->total;

// #define SLOW_BUT_SIMPLE 1
#ifdef SLOW_BUT_SIMPLE
	/* The below implements the straight-forward concept of the product.
	 * Its not quite optimal, because the initialization loop, and the
	 * if check can be eliminated by re-writing j = k-i.
	 */
	for (i = 0; i < NUM_BINS; i++) prod->bin[i] = 0;
	prod->overrun = 0;
	for (i = 0; i < NUM_BINS; i++)
	{
		for (j = 0; j < NUM_BINS; j++)
		{
			if (i+j < NUM_BINS)
				prod->bin[i+j] += a->bin[i] * b->bin[j];
			else
				prod->overrun += a->bin[i] * b->bin[j];
		}

		prod->overrun += a->bin[i] * b->overrun;
		prod->overrun += a->overrun * b->bin[i];
	}
	prod->overrun += a->overrun * b->overrun;
#else
	/* The below does exactly the same thing as the above, but
	 * ever so slightly more quickly. Some pointless checks get
	 * eliminated.
	 */
	prod->overrun = 0;
	for (k = 0; k < NUM_BINS; k++)
	{
		prod->bin[k] = 0;
		for (i = 0; i <= k; i++)
		{
			prod->bin[k] += a->bin[i] * b->bin[k-i];
		}
		prod->overrun += a->bin[k] * b->overrun;
		prod->overrun += a->overrun * b->bin[k];
	}
	for (k = NUM_BINS; k < 2 * NUM_BINS - 1; k++)
	{
		for (i = k - NUM_BINS + 1; i < NUM_BINS; i++)
		{
			prod->overrun += a->bin[i] * b->bin[k-i];
		}
	}
	prod->overrun += a->overrun * b->overrun;
#endif
}

/**
 * Multiply two histograms 'a' and 'b', and accumulate them into 'acc'.
 * The accumulated histogram is first shifted by 'cost'.
 */
void hist_muladd(Count_bin* acc, const Count_bin* a, double cost, const Count_bin* b)
{
	Count_bin tmp = hist_zero();
	hist_prod(&tmp, a, b);
	hist_accum(acc, cost, &tmp);
}

void hist_muladdv(Count_bin* acc, const Count_bin* a, double cost, const Count_bin b)
{
	hist_muladd(acc, a, cost, &b);
}

double hist_cost_cutoff(Count_bin* hist, int count)
{
	int i;
	w_count_t cnt = 0;

	for (i=0; i<NUM_BINS; i++)
	{
		cnt += hist->bin[i];
		if (count <= cnt)
			return ((double) i + hist->base) * BIN_WIDTH;
	}
	return 1.0e38;
}

w_count_t hist_cut_total(Count_bin* hist, int min_total)
{
	int i;
	w_count_t cnt = 0;

	for (i=0; i<NUM_BINS; i++)
	{
		cnt += hist->bin[i];
		if (min_total <= cnt) return cnt;
	}
	return hist->total;
}

#endif /* PERFORM_COUNT_HISTOGRAMMING */
