/**
 * @file diff_container.cpp
 * Container for diffed symbols
 *
 * @remark Copyright 2005 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Philippe Elie
 * @author John Levon
 */

/* older glibc has C99 INFINITY in _GNU_SOURCE */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "diff_container.h"

#include <cmath>

using namespace std;


namespace {


/// a comparator suitable for diffing symbols
bool rough_less(symbol_entry const & lhs, symbol_entry const & rhs)
{
	if (lhs.image_name != rhs.image_name)
		return lhs.image_name < rhs.image_name;

	if (lhs.app_name != rhs.app_name)
		return lhs.app_name < rhs.app_name;

	if (lhs.name != rhs.name)
		return lhs.name < rhs.name;

	return false;
}


/// possibly add a diff sym
void
add_sym(diff_collection & syms, diff_symbol const & sym,
        profile_container::symbol_choice & choice)
{
	if (choice.match_image
	    && (image_names.name(sym.image_name) != choice.image_name))
		return;

	if (fabs(sym.diffs[0]) < choice.threshold)
		return;

	choice.hints = sym.output_hint(choice.hints);
	syms.push_back(sym);
}


/// add a symbol not present in the new profile
void
symbol_old(diff_collection & syms, symbol_entry const & sym,
           profile_container::symbol_choice & choice)
{
	diff_symbol symbol(sym);
	symbol.diffs.fill(sym.sample.counts.size(), -INFINITY);
	add_sym(syms, symbol, choice);
}


/// add a symbol not present in the old profile
void
symbol_new(diff_collection & syms, symbol_entry const & sym,
           profile_container::symbol_choice & choice)
{
	diff_symbol symbol(sym);
	symbol.diffs.fill(sym.sample.counts.size(), INFINITY);
	add_sym(syms, symbol, choice);
}


/// add a diffed symbol
void symbol_diff(diff_collection & syms,
                 symbol_entry const & sym1, count_array_t const & total1,
                 symbol_entry const & sym2, count_array_t const & total2,
                 profile_container::symbol_choice & choice)
{
	diff_symbol symbol(sym2);

	size_t size = sym2.sample.counts.size();
	for (size_t i = 0; i != size; ++i) {
		double percent1;
		double percent2;
		percent1 = op_ratio(sym1.sample.counts[i], total1[i]);
		percent2 = op_ratio(sym2.sample.counts[i], total2[i]);
		symbol.diffs[i] = op_ratio(percent2 - percent1, percent1);
		symbol.diffs[i] *= 100.0;
	}

	add_sym(syms, symbol, choice);
}


}; // namespace anon


diff_container::diff_container(profile_container const & c1,
                               profile_container const & c2)
	: pc1(c1), pc2(c2),
	  total1(pc1.samples_count()), total2(pc2.samples_count())
{
}


diff_collection const
diff_container::get_symbols(profile_container::symbol_choice & choice) const
{
	diff_collection syms;

	/*
	 * Do a pairwise comparison of the two symbol sets. We're
	 * relying here on the symbol container being sorted such
	 * that rough_less() is suitable for iterating through the
	 * two lists (see less_symbol).
	 */

	symbol_container::symbols_t::iterator it1 = pc1.begin_symbol();
	symbol_container::symbols_t::iterator end1 = pc1.end_symbol();
	symbol_container::symbols_t::iterator it2 = pc2.begin_symbol();
	symbol_container::symbols_t::iterator end2 = pc2.end_symbol();

	while (it1 != end1 && it2 != end2) {
		if (rough_less(*it1, *it2)) {
			symbol_old(syms, *it1, choice);
			++it1;
		} else if (rough_less(*it2, *it1)) {
			symbol_new(syms, *it2, choice);
			++it2;
		} else {
			symbol_diff(syms, *it1, total1, *it2, total2, choice);
			++it1;
			++it2;
		}
	}

	for (; it1 != end1; ++it1)
		symbol_old(syms, *it1, choice);

	for (; it2 != end2; ++it2)
		symbol_new(syms, *it2, choice);

	return syms;
}


count_array_t const diff_container::samples_count() const
{
	return total2;
}