/**
 * @file symbol_sort.cpp
 * Sorting symbols
 *
 * @remark Copyright 2002, 2003 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Philippe Elie
 * @author John Levon
 */

#include "symbol_sort.h"
#include "symbol_functors.h"

#include "name_storage.h"
#include "op_exception.h"

#include <algorithm>
#include <sstream>

using namespace std;

namespace {

bool long_filenames;

int image_compare(image_name_id l, image_name_id r)
{
	if (long_filenames)
		return image_names.name(l).compare(image_names.name(r));
	return image_names.basename(l).compare(image_names.basename(r));
}


int debug_compare(debug_name_id l, debug_name_id r)
{
	if (long_filenames)
		return debug_names.name(l).compare(debug_names.name(r));
	return debug_names.basename(l).compare(debug_names.basename(r));
}


int compare_by(sort_options::sort_order order,
               symbol_entry const & lhs, symbol_entry const & rhs)
{
	switch (order) {
		case sort_options::sample:
			if (lhs.sample.counts[0] < rhs.sample.counts[0])
				return 1;
			if (lhs.sample.counts[0] > rhs.sample.counts[0])
				return -1;
			return 0;

		case sort_options::symbol:
			return symbol_names.demangle(lhs.name).compare(
				symbol_names.demangle(rhs.name));

		case sort_options::image:
			return image_compare(lhs.image_name, rhs.image_name);

		case sort_options::app_name:
			return image_compare(lhs.app_name, rhs.app_name);

		case sort_options::vma:
			if (lhs.sample.vma < rhs.sample.vma)
				return -1;
			if (lhs.sample.vma > rhs.sample.vma)
				return 1;
			return 0;

		case sort_options::debug: {
			file_location const & f1 = lhs.sample.file_loc;
			file_location const & f2 = rhs.sample.file_loc;
			int ret = debug_compare(f1.filename, f2.filename);
			if (ret == 0)
				ret = f1.linenr - f2.linenr;
			return ret;
		}

		default: {
			// static_cast<> to shut up g++ 2.91.66 which warn
			// about ambiguity between <<(int) and <<(long int)
			ostringstream os;
			os << "compare_by(): unknown sort option: "
			   << static_cast<int>(order) << endl;
			throw op_fatal_error(os.str());
		}
	}

	return 0;
}


struct symbol_compare {
	symbol_compare(vector<sort_options::sort_order> const & order,
	               bool reverse)
		: compare_order(order), reverse_sort(reverse) {}

	bool operator()(symbol_entry const * lhs,
			symbol_entry const * rhs) const {
		return operator()(*lhs, *rhs);
	}

	bool operator()(symbol_entry const & lhs,
			symbol_entry const & rhs) const;

protected:
	vector<sort_options::sort_order> const & compare_order;
	bool reverse_sort;
};


bool symbol_compare::operator()(symbol_entry const & lhs,
				symbol_entry const & rhs) const
{
	for (size_t i = 0; i < compare_order.size(); ++i) {
		int ret = compare_by(compare_order[i], lhs, rhs);

		if (reverse_sort)
			ret = -ret;
		if (ret != 0)
			return ret < 0;
	}
	return false;
}


} // anonymous namespace


void sort_options::
sort(symbol_collection & syms, bool reverse_sort, bool lf) const
{
	long_filenames = lf;

	vector<sort_order> sort_option(options);
	for (sort_order cur = first; cur != last; cur = sort_order(cur + 1)) {
		if (find(sort_option.begin(), sort_option.end(), cur) ==
		    sort_option.end())
			sort_option.push_back(cur);
	}

	stable_sort(syms.begin(), syms.end(),
	            symbol_compare(sort_option, reverse_sort));
}


void sort_options::
sort(diff_collection & syms, bool reverse_sort, bool lf) const
{
	long_filenames = lf;

	vector<sort_order> sort_option(options);
	for (sort_order cur = first; cur != last; cur = sort_order(cur + 1)) {
		if (find(sort_option.begin(), sort_option.end(), cur) ==
		    sort_option.end())
			sort_option.push_back(cur);
	}

	stable_sort(syms.begin(), syms.end(),
	            symbol_compare(sort_option, reverse_sort));
}


void sort_options::add_sort_option(string const & name)
{
	if (name == "vma") {
		options.push_back(vma);
	} else if (name == "sample") {
		options.push_back(sample);
	} else if (name == "symbol") {
		options.push_back(symbol);
	} else if (name == "debug") {
		options.push_back(debug);
	} else if (name == "image") {
		options.push_back(image);
	} else if (name == "app-name") {
		options.push_back(app_name);
	} else {
		ostringstream os;
		os << "unknown sort option: " << name << endl;
		throw op_fatal_error(os.str());
	}
}


void sort_options::add_sort_option(sort_options::sort_order order)
{
	options.push_back(order);
}