/**
 * @file arrange_profiles.cpp
 * Classify and process a list of candidate sample files
 * into merged sets and classes.
 *
 * @remark Copyright 2003 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 */

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <iterator>
#include <map>
#include <set>

#include "string_manip.h"
#include "op_header.h"
#include "op_exception.h"

#include "arrange_profiles.h"
#include "format_output.h"
#include "xml_utils.h"
#include "parse_filename.h"
#include "locate_images.h"

using namespace std;

namespace {

int numeric_compare(string const & lhs, string const & rhs)
{
	if (lhs == "all" && rhs == "all")
		return 0;
	// we choose an order arbitrarily
	if (lhs == "all")
		return 1;
	if (rhs == "all")
		return -1;
	unsigned int lhsval = op_lexical_cast<unsigned int>(lhs);
	unsigned int rhsval = op_lexical_cast<unsigned int>(rhs);
	if (lhsval == rhsval)
		return 0;
	if (lhsval < rhsval)
		return -1;
	return 1;
}


} // anonymous namespace


// global to fix some C++ obscure corner case.
bool operator<(profile_class const & lhs,
               profile_class const & rhs)
{
	profile_template const & lt = lhs.ptemplate;
	profile_template const & rt = rhs.ptemplate;
	int comp;

	// The profile classes are used to traverse the sample data
	// arrays.  We create XML elements for <process> and <thread>
	// that contain the sample data that can then be divided amongst
	// CPU, event, mask axes so it is more convenient to have the
	// process and thread classes be the outermost nesting level of
	// the sample data arrays
	if (!want_xml) {
		comp = numeric_compare(lt.cpu, rt.cpu);
		if (comp)
			return comp < 0;
	}

	comp = numeric_compare(lt.tgid, rt.tgid);
	if (comp)
		return comp < 0;

	comp = numeric_compare(lt.tid, rt.tid);
	if (comp)
		return comp < 0;

	comp = numeric_compare(lt.unitmask, rt.unitmask);
	if (comp)
		return comp < 0;

	if (want_xml) {
		if (lt.event != rt.event)
			return lt.event < rt.event;
		if (lt.count != rt.count)
			return lt.count < rt.count;

		return numeric_compare(lt.cpu, rt.cpu) < 0;
	} else {
		if (lt.event == rt.event)
			return lt.count < rt.count;
		return lt.event < rt.event;
	}
}

namespace {

struct axis_t {
	string name;
	string suggestion;
} axes[AXIS_MAX] = {
	{ "event", "specify event:, count: or unitmask: (see also --merge=unitmask)" },
	{ "tgid", "specify tgid: or --merge tgid" },
	{ "tid", "specify tid: or --merge tid" },
	{ "cpu", "specify cpu: or --merge cpu" },
};

} // anonymous namespace


bool profile_classes::matches(profile_classes const & classes)
{
	if (v.size() != classes.v.size())
		return false;

	axis_types const axis2 = classes.axis;

	switch (axis) {
		case AXIS_EVENT:
			break;
		case AXIS_TGID:
		case AXIS_TID:
			return axis2 == AXIS_TID || axis2 == AXIS_TGID;
		case AXIS_CPU:
			return axis2 == AXIS_CPU;
		case AXIS_MAX:
			return false;
	}

	// check that the events match (same event, count)

	vector<profile_class>::const_iterator it1 = v.begin();
	vector<profile_class>::const_iterator end1 = v.end();
	vector<profile_class>::const_iterator it2 = classes.v.begin();

	while (it1 != end1) {
		if (it1->ptemplate.event != it2->ptemplate.event)
			return false;
		if (it1->ptemplate.count != it2->ptemplate.count)
			return false;
		// differing unit mask is considered comparable
		++it1;
		++it2;
	}

	return true;
}

namespace {

typedef growable_vector<string> event_array_t;
typedef growable_vector<string>::size_type event_index_t;

bool new_event_index(string event, event_array_t & events, event_index_t & index)
{
	event_index_t sz = events.size();
	for (event_index_t i = 0; i != sz; ++i) {
		if (events[i] == event) {
			index = i;
			return false;
		}
	}

	index = sz;
	events[sz] = event;
	return true;
}


/// We have more than one axis of classification, tell the user.
void report_error(profile_classes const & classes, axis_types newaxis)
{
	string str = "Already displaying results for parameter ";
	str += axes[classes.axis].name;
	str += " with values:\n";
	vector<profile_class>::const_iterator it = classes.v.begin();
	vector<profile_class>::const_iterator const end = classes.v.end();

	// We show error for the first conflicting axis but on this
	// axis we can get only a few different it->name, we display only
	// these different name.
	set <string> name_seen;
	size_t i = 5;
	for (; it != end && i; ++it) {
		if (name_seen.find(it->name) == name_seen.end()) {
			name_seen.insert(it->name);
			str += it->name + ",";
			--i;
		}
	}

	if (!i) {
		str += " and ";
		str += op_lexical_cast<string>(classes.v.size() - 5);
		str += " more,";
	}

	str += "\nwhich conflicts with parameter ";
	str += axes[newaxis].name += ".\n";
	str += "Suggestion: ";
	str += axes[classes.axis].suggestion;
	throw op_fatal_error(str);
}


/**
 * check that two different axes are OK - this is only
 * allowed if they are TGID,TID and for each class,
 * tid == tgid
 */
bool allow_axes(profile_classes const & classes, axis_types newaxis)
{
	// No previous axis - OK
	if (classes.axis == AXIS_MAX)
		return true;

	if (classes.axis != AXIS_TID && classes.axis != AXIS_TGID)
		return false;

	if (newaxis != AXIS_TID && newaxis != AXIS_TGID)
		return false;

	vector<profile_class>::const_iterator it = classes.v.begin();
	vector<profile_class>::const_iterator const end = classes.v.end();

	for (; it != end; ++it) {
		if (it->ptemplate.tgid != it->ptemplate.tid)
			return false;
	}

	return true;
}


/// find the first sample file header in the class
opd_header const get_first_header(profile_class const & pclass)
{
	profile_set const & profile = *(pclass.profiles.begin());

	string file;

	// could be only one main app, with no samples for the main image
	if (profile.files.empty()) {
		profile_dep_set const & dep = *(profile.deps.begin());
		list<profile_sample_files> const & files = dep.files;
		profile_sample_files const & sample_files = *(files.begin());
		if (!sample_files.sample_filename.empty())
			file = sample_files.sample_filename;
		else
			file = *sample_files.cg_files.begin();
	} else {
		profile_sample_files const & sample_files 
			= *(profile.files.begin());
		if (!sample_files.sample_filename.empty())
			file = sample_files.sample_filename;
		else
			file = *sample_files.cg_files.begin();
	}

	return read_header(file);
}

/// merge sample file header in the profile_sample_files
void merge_header(profile_sample_files const & files, opd_header & header)
{
	if (!files.sample_filename.empty()) {
		opd_header const temp = read_header(files.sample_filename);
		header.ctr_um |=  temp.ctr_um;
	}

	list<string>::const_iterator it = files.cg_files.begin();
	list<string>::const_iterator const end = files.cg_files.end();
	for ( ; it != end; ++it) {
		opd_header const temp = read_header(*it);
		header.ctr_um |= temp.ctr_um;
	}
}

/// merge sample file header in the class
opd_header const get_header(profile_class const & pclass,
                            merge_option const & merge_by)
{
	opd_header header = get_first_header(pclass);

	if (!merge_by.unitmask)
		return header;

	profile_set const & profile = *(pclass.profiles.begin());

	typedef list<profile_sample_files>::const_iterator citerator;

	citerator it = profile.files.begin();
	citerator const end = profile.files.end();
	for ( ; it != end; ++it)
		merge_header(*it, header);

	list<profile_dep_set>::const_iterator dep_it = profile.deps.begin();
	list<profile_dep_set>::const_iterator dep_end = profile.deps.end();
	for ( ; dep_it != dep_end; ++dep_it) {
		citerator it = dep_it->files.begin();
		citerator const end = dep_it->files.end();
		for ( ; it != end; ++it)
			merge_header(*it, header);
	}

	return header;
}


/// Give human-readable names to each class.
void name_classes(profile_classes & classes, merge_option const & merge_by)
{
	opd_header header = get_header(classes.v[0], merge_by);

	classes.event = describe_header(header);
	classes.cpuinfo = describe_cpu(header);

	// If we're splitting on event anyway, clear out the
	// global event name
	if (classes.axis == AXIS_EVENT)
		classes.event.erase();

	vector<profile_class>::iterator it = classes.v.begin();
	vector<profile_class>::iterator const end = classes.v.end();

	for (; it != end; ++it) {
		it->name = axes[classes.axis].name + ":";
		switch (classes.axis) {
		case AXIS_EVENT:
			it->name = it->ptemplate.event
				+ ":" + it->ptemplate.count;
			header = get_header(*it, merge_by);
			it->longname = describe_header(header);
			break;
		case AXIS_TGID:
			it->name += it->ptemplate.tgid;
			it->longname = "Processes with a thread group ID of ";
			it->longname += it->ptemplate.tgid;
			break;
		case AXIS_TID:
			it->name += it->ptemplate.tid;
			it->longname = "Processes with a thread ID of ";
			it->longname += it->ptemplate.tid;
			break;
		case AXIS_CPU:
			it->name += it->ptemplate.cpu;
			it->longname = "Samples on CPU " + it->ptemplate.cpu;
			break;
		case AXIS_MAX:;
		}
	}
}


/**
 * Name and verify classes.
 */
void identify_classes(profile_classes & classes,
                      merge_option const & merge_by)
{
	profile_template & ptemplate = classes.v[0].ptemplate;
	bool changed[AXIS_MAX] = { false, };

	vector<profile_class>::iterator it = classes.v.begin();
	++it;
	vector<profile_class>::iterator end = classes.v.end();

	// only one class, name it after the event
	if (it == end)
		changed[AXIS_EVENT] = true;

	for (; it != end; ++it) {
		if (it->ptemplate.event != ptemplate.event
		    ||  it->ptemplate.count != ptemplate.count
		    // unit mask are mergeable
		    || (!merge_by.unitmask
			&& it->ptemplate.unitmask != ptemplate.unitmask))
			changed[AXIS_EVENT] = true;

		// we need the merge checks here because each
		// template is filled in from the first non
		// matching profile, so just because they differ
		// doesn't mean it's the axis we care about
		if (!merge_by.tgid && it->ptemplate.tgid != ptemplate.tgid)
			changed[AXIS_TGID] = true;

		if (!merge_by.tid && it->ptemplate.tid != ptemplate.tid)
			changed[AXIS_TID] = true;

		if (!merge_by.cpu && it->ptemplate.cpu != ptemplate.cpu)
			changed[AXIS_CPU] = true;
	}

	classes.axis = AXIS_MAX;

	for (size_t i = 0; i < AXIS_MAX; ++i) {
		if (!changed[i])
			continue;

		if (!allow_axes(classes, axis_types(i)))
			report_error(classes, axis_types(i));
		classes.axis = axis_types(i);
		/* do this early for report_error */
		name_classes(classes, merge_by);
	}

	if (classes.axis == AXIS_MAX) {
		cerr << "Internal error - no equivalence class axis" << endl;
		abort();
	}
}

void identify_xml_classes(profile_classes & classes, merge_option const & merge_by)
{
	opd_header header = get_header(classes.v[0], merge_by);

	vector<profile_class>::iterator it = classes.v.begin();
	vector<profile_class>::iterator end = classes.v.end();

	event_index_t event_num;
	event_index_t event_max = 0;
	event_array_t event_array;
	size_t nr_cpus = 0;
	bool has_nonzero_mask = false;

	ostringstream event_setup;

	// fill in XML identifying each event, and replace event name by event_num
	for (; it != end; ++it) {
		string mask = it->ptemplate.unitmask;
		if (mask.find_first_of("x123456789abcdefABCDEF") != string::npos)
			has_nonzero_mask = true;
		if (new_event_index(it->ptemplate.event, event_array, event_num)) {
			// replace it->ptemplate.event with the event_num string
			// this is the first time we've seen this event
			header = get_header(*it, merge_by);
			event_setup << describe_header(header);
			event_max = event_num;
		}
		if (it->ptemplate.cpu != "all") {
			size_t cpu = atoi(it->ptemplate.cpu.c_str());
			if (cpu > nr_cpus) nr_cpus = cpu;
		}

		ostringstream str;
		str << event_num;
		it->ptemplate.event = str.str();
	}
	xml_utils::set_nr_cpus(++nr_cpus);
	xml_utils::set_nr_events(event_max+1);
	if (has_nonzero_mask)
		xml_utils::set_has_nonzero_masks();
	classes.event = event_setup.str();
	classes.cpuinfo = describe_cpu(header);
}

/// construct a class template from a profile
profile_template const
template_from_profile(parsed_filename const & parsed,
                      merge_option const & merge_by)
{
	profile_template ptemplate;

	ptemplate.event = parsed.event;
	ptemplate.count = parsed.count;

	if (!merge_by.unitmask)
		ptemplate.unitmask = parsed.unitmask;
	if (!merge_by.tgid)
		ptemplate.tgid = parsed.tgid;
	if (!merge_by.tid)
		ptemplate.tid = parsed.tid;
	if (!merge_by.cpu)
		ptemplate.cpu = parsed.cpu;
	return ptemplate;
}


/**
 * Find a matching class the sample file could go in, or generate
 * a new class if needed.
 * This is the heart of the merging and classification process.
 * The returned value is non-const reference but the ptemplate member
 * must be considered as const
 */
profile_class & find_class(set<profile_class> & classes,
                           parsed_filename const & parsed,
                           merge_option const & merge_by)
{
	profile_class cls;
	cls.ptemplate = template_from_profile(parsed, merge_by);

	pair<set<profile_class>::iterator, bool> ret = classes.insert(cls);

	return const_cast<profile_class &>(*ret.first);
}

/**
 * Sanity check : we can't overwrite sample_filename, if we abort here it means
 * we fail to detect that parsed sample filename for two distinct samples
 * filename must go in two distinct profile_sample_files. This assumption is
 * false for callgraph samples files so this function is only called for non cg
 * files.
 */
void sanitize_profile_sample_files(profile_sample_files const & sample_files,
    parsed_filename const & parsed)
{
	// We can't allow to overwrite sample_filename.
	if (!sample_files.sample_filename.empty()) {
		ostringstream out;
		out << "sanitize_profile_sample_files(): sample file "
		    << "parsed twice ?\nsample_filename:\n"
		    << sample_files.sample_filename << endl
		    << parsed << endl;
		throw op_fatal_error(out.str());
	}
}


/**
 * Add a sample filename (either cg or non cg files) to this profile.
 */
void
add_to_profile_sample_files(profile_sample_files & sample_files,
    parsed_filename const & parsed)
{
	if (parsed.cg_image.empty()) {
		// We can't allow to overwrite sample_filename.
		sanitize_profile_sample_files(sample_files, parsed);

		sample_files.sample_filename = parsed.filename;
	} else {
		sample_files.cg_files.push_back(parsed.filename);
	}
}


/**
 * we need to fix cg filename: a callgraph filename can occur before the binary
 * non callgraph samples filename occur so we must search.
 */
profile_sample_files &
find_profile_sample_files(list<profile_sample_files> & files,
			  parsed_filename const & parsed,
			  extra_images const & extra)
{
	list<profile_sample_files>::iterator it;
	list<profile_sample_files>::iterator const end = files.end();
	for (it = files.begin(); it != end; ++it) {
		if (!it->sample_filename.empty()) {
			parsed_filename psample_filename =
			  parse_filename(it->sample_filename, extra);
			if (psample_filename.lib_image == parsed.lib_image &&
			    psample_filename.image == parsed.image &&
			    psample_filename.profile_spec_equal(parsed))
				return *it;
		}

		list<string>::const_iterator cit;
		list<string>::const_iterator const cend = it->cg_files.end();
		for (cit = it->cg_files.begin(); cit != cend; ++cit) {
			parsed_filename pcg_filename =
				parse_filename(*cit, extra);
			if (pcg_filename.lib_image == parsed.lib_image &&
			    pcg_filename.image == parsed.image &&
			    pcg_filename.profile_spec_equal(parsed))
				return *it;
		}
	}

	// not found, create a new one
	files.push_back(profile_sample_files());
	return files.back();
}


/**
 * Add a profile to particular profile set. If the new profile is
 * a dependent image, it gets added to the dep list, or just placed
 * on the normal list of profiles otherwise.
 */
void
add_to_profile_set(profile_set & set, parsed_filename const & parsed,
		   bool merge_by_lib, extra_images const & extra)
{
	if (parsed.image == parsed.lib_image && !merge_by_lib) {
		profile_sample_files & sample_files =
			find_profile_sample_files(set.files, parsed, extra);
		add_to_profile_sample_files(sample_files, parsed);
		return;
	}

	list<profile_dep_set>::iterator it = set.deps.begin();
	list<profile_dep_set>::iterator const end = set.deps.end();

	for (; it != end; ++it) {
		if (it->lib_image == parsed.lib_image && !merge_by_lib &&
				parsed.jit_dumpfile_exists == false) {
			profile_sample_files & sample_files =
				find_profile_sample_files(it->files, parsed,
							  extra);
			add_to_profile_sample_files(sample_files, parsed);
			return;
		}
	}

	profile_dep_set depset;
	depset.lib_image = parsed.lib_image;
	profile_sample_files & sample_files =
		find_profile_sample_files(depset.files, parsed, extra);
	add_to_profile_sample_files(sample_files, parsed);
	set.deps.push_back(depset);
}


/**
 * Add a profile to a particular equivalence class. The previous matching
 * will have ensured the profile "fits", so now it's just a matter of
 * finding which sample file list it needs to go on.
 */
void add_profile(profile_class & pclass, parsed_filename const & parsed,
		 bool merge_by_lib, extra_images const & extra)
{
	list<profile_set>::iterator it = pclass.profiles.begin();
	list<profile_set>::iterator const end = pclass.profiles.end();

	for (; it != end; ++it) {
		if (it->image == parsed.image) {
			add_to_profile_set(*it, parsed, merge_by_lib, extra);
			return;
		}
	}

	profile_set set;
	set.image = parsed.image;
	add_to_profile_set(set, parsed, merge_by_lib, extra);
	pclass.profiles.push_back(set);
}

}  // anon namespace


profile_classes const
arrange_profiles(list<string> const & files, merge_option const & merge_by,
		 extra_images const & extra)
{
	set<profile_class> temp_classes;

	list<string>::const_iterator it = files.begin();
	list<string>::const_iterator const end = files.end();

	for (; it != end; ++it) {
		parsed_filename parsed = parse_filename(*it, extra);

		if (parsed.lib_image.empty())
			parsed.lib_image = parsed.image;

		// This simplifies the add of the profile later,
		// if we're lib-merging, then the app_image cannot
		// matter. After this, any non-dependent has
		// image == lib_image
		if (merge_by.lib)
			parsed.image = parsed.lib_image;

		profile_class & pclass =
			find_class(temp_classes, parsed, merge_by);
		add_profile(pclass, parsed, merge_by.lib, extra);
	}

	profile_classes classes;
	copy(temp_classes.begin(), temp_classes.end(),
	     back_inserter(classes.v));

	if (classes.v.empty())
		return classes;

	// sort by template for nicely ordered columns
	stable_sort(classes.v.begin(), classes.v.end());

	if (want_xml)
		identify_xml_classes(classes, merge_by);
	else
		identify_classes(classes, merge_by);

	classes.extra_found_images = extra;

	return classes;
}


ostream & operator<<(ostream & out, profile_sample_files const & sample_files)
{
	out << "sample_filename: " << sample_files.sample_filename << endl;
	out << "callgraph filenames:\n";
	copy(sample_files.cg_files.begin(), sample_files.cg_files.end(),
	     ostream_iterator<string>(out, "\n"));
	return out;
}

ostream & operator<<(ostream & out, profile_dep_set const & pdep_set)
{
	out << "lib_image: " << pdep_set.lib_image << endl;

	list<profile_sample_files>::const_iterator it;
	list<profile_sample_files>::const_iterator const end =
		pdep_set.files.end();
	size_t i = 0;
	for (it = pdep_set.files.begin(); it != end; ++it)
		out << "profile_sample_files #" << i++ << ":\n" << *it;

	return out;
}

ostream & operator<<(ostream & out, profile_set const & pset)
{
	out << "image: " << pset.image << endl;

	list<profile_sample_files>::const_iterator it;
	list<profile_sample_files>::const_iterator const end =
		pset.files.end();
	size_t i = 0;
	for (it = pset.files.begin(); it != end; ++it)
		out << "profile_sample_files #" << i++ << ":\n" << *it;

	list<profile_dep_set>::const_iterator cit;
	list<profile_dep_set>::const_iterator const cend = pset.deps.end();
	i = 0;
	for (cit = pset.deps.begin(); cit != cend; ++cit)
		out << "profile_dep_set #" << i++ << ":\n" << *cit;

	return out;
}

ostream & operator<<(ostream & out, profile_template const & ptemplate)
{
	out << "event: " << ptemplate.event << endl
	    << "count: " << ptemplate.count << endl
	    << "unitmask: " << ptemplate.unitmask << endl
	    << "tgid: " << ptemplate.tgid << endl
	    << "tid: " << ptemplate.tid << endl
	    << "cpu: " << ptemplate.cpu << endl;
	return out;
}

ostream & operator<<(ostream & out, profile_class const & pclass)
{
	out << "name: " << pclass.name << endl
	    << "longname: " << pclass.longname << endl
	    << "ptemplate:\n" << pclass.ptemplate;

	size_t i = 0;
	list<profile_set>::const_iterator it;
	list<profile_set>::const_iterator const end = pclass.profiles.end();
	for (it = pclass.profiles.begin(); it != end; ++it)
		out << "profiles_set #" << i++ << ":\n" << *it;

	return out;
}

ostream & operator<<(ostream & out, profile_classes const & pclasses)
{
	out << "event: " << pclasses.event << endl
	    << "cpuinfo: " << pclasses.cpuinfo << endl;

	for (size_t i = 0; i < pclasses.v.size(); ++i)
		out << "class #" << i << ":\n" << pclasses.v[i];

	return out;
}


namespace {

/// add the files to group of image sets
void add_to_group(image_group_set & group, string const & app_image,
                  list<profile_sample_files> const & files)
{
	image_set set;
	set.app_image = app_image;
	set.files = files;
	group.push_back(set);
}


typedef map<string, inverted_profile> app_map_t;


inverted_profile &
get_iprofile(app_map_t & app_map, string const & image, size_t nr_classes)
{
	app_map_t::iterator ait = app_map.find(image);
	if (ait != app_map.end())
		return ait->second;

	inverted_profile ip;
	ip.image = image;
	ip.groups.resize(nr_classes);
	app_map[image] = ip;
	return app_map[image];
}


/// Pull out all the images, removing any we can't access.
void
verify_and_fill(app_map_t & app_map, list<inverted_profile> & plist,
		extra_images const & extra)
{
	app_map_t::iterator it = app_map.begin();
	app_map_t::iterator const end = app_map.end();

	for (; it != end; ++it) {
		plist.push_back(it->second);
		inverted_profile & ip = plist.back();
		extra.find_image_path(ip.image, ip.error, false);
	}
}

} // anon namespace


list<inverted_profile> const
invert_profiles(profile_classes const & classes)
{
	app_map_t app_map;

	size_t nr_classes = classes.v.size();

	for (size_t i = 0; i < nr_classes; ++i) {
		list<profile_set>::const_iterator pit
			= classes.v[i].profiles.begin();
		list<profile_set>::const_iterator pend
			= classes.v[i].profiles.end();

		for (; pit != pend; ++pit) {
			// files can be empty if samples for a lib image
			// but none for the main image. Deal with it here
			// rather than later.
			if (pit->files.size()) {
				inverted_profile & ip = get_iprofile(app_map,
					pit->image, nr_classes);
				add_to_group(ip.groups[i], pit->image, pit->files);
			}

			list<profile_dep_set>::const_iterator dit
				= pit->deps.begin();
			list<profile_dep_set>::const_iterator const dend
				= pit->deps.end();

			for (;  dit != dend; ++dit) {
				inverted_profile & ip = get_iprofile(app_map,
					dit->lib_image, nr_classes);
				add_to_group(ip.groups[i], pit->image,
				             dit->files);
			}
		}
	}

	list<inverted_profile> inverted_list;

	verify_and_fill(app_map, inverted_list, classes.extra_found_images);

	return inverted_list;
}