/**
 * @file opreport.cpp
 * Implement opreport utility
 *
 * @remark Copyright 2003 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <sstream>
#include <numeric>

#include "op_exception.h"
#include "stream_util.h"
#include "string_manip.h"
#include "file_manip.h"
#include "opreport_options.h"
#include "op_header.h"
#include "profile.h"
#include "populate.h"
#include "arrange_profiles.h"
#include "profile_container.h"
#include "callgraph_container.h"
#include "diff_container.h"
#include "symbol_sort.h"
#include "format_output.h"
#include "xml_utils.h"
#include "image_errors.h"

using namespace std;

namespace {

static size_t nr_classes;

/// storage for a merged file summary
struct summary {
	count_array_t counts;
	string lib_image;

	bool operator<(summary const & rhs) const {
		return options::reverse_sort
		    ? counts[0] < rhs.counts[0] : rhs.counts[0] < counts[0];
	}

	/// add a set of files to a summary
	count_type add_files(list<profile_sample_files> const & files,
	                     size_t pclass);
};


count_type summary::
add_files(list<profile_sample_files> const & files, size_t pclass)
{
	count_type subtotal = 0;

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

	for (; it != end; ++it) {
		count_type count = profile_t::sample_count(it->sample_filename);
		counts[pclass] += count;
		subtotal += count;

		if (!it->cg_files.empty()) {
			throw op_runtime_error("opreport.cpp::add_files(): "
			       "unxpected non empty cg file set");
		}
	}

	return subtotal;
}


/**
 * Summary of an application: a set of image summaries
 * for one application, i.e. an application image and all
 * dependent images such as libraries.
 */
struct app_summary {
	/// total count of us and all dependents
	count_array_t counts;
	/// the main image
	string image;
	/// our dependent images
	vector<summary> deps;

	/// construct and fill in the data
	count_type add_profile(profile_set const & profile, size_t pclass);

	bool operator<(app_summary const & rhs) const {
		return options::reverse_sort 
		    ? counts[0] < rhs.counts[0] : rhs.counts[0] < counts[0];
	}

private:
	/// find a matching summary (including main app summary)
	summary & find_summary(string const & image);
};


summary & app_summary::find_summary(string const & image)
{
	vector<summary>::iterator sit = deps.begin();
	vector<summary>::iterator const send = deps.end();
	for (; sit != send; ++sit) {
		if (sit->lib_image == image)
			return *sit;
	}

	summary summ;
	summ.lib_image = image;
	deps.push_back(summ);
	return deps.back();
}


count_type app_summary::add_profile(profile_set const & profile,
                                size_t pclass)
{
	count_type group_total = 0;

	// first the main image
	summary & summ = find_summary(profile.image);
	count_type app_count = summ.add_files(profile.files, pclass);
	counts[pclass] += app_count;
	group_total += app_count;

	// now all dependent images if any
	list<profile_dep_set>::const_iterator it = profile.deps.begin();
	list<profile_dep_set>::const_iterator const end = profile.deps.end();

	for (; it != end; ++it) {
		summary & summ = find_summary(it->lib_image);
		count_type lib_count = summ.add_files(it->files, pclass);
		counts[pclass] += lib_count;
		group_total += lib_count;
	}

	return group_total;
}


/// all the summaries
struct summary_container {
	summary_container(vector<profile_class> const & pclasses);

	/// all map summaries
	vector<app_summary> apps;
	/// total count of samples for all summaries
	count_array_t total_counts;
};


summary_container::
summary_container(vector<profile_class> const & pclasses)
{
	typedef map<string, app_summary> app_map_t;
	app_map_t app_map;

	for (size_t i = 0; i < pclasses.size(); ++i) {
		list<profile_set>::const_iterator it
			= pclasses[i].profiles.begin();
		list<profile_set>::const_iterator const end
			= pclasses[i].profiles.end();

		for (; it != end; ++it) {
			app_map_t::iterator ait = app_map.find(it->image);
			if (ait == app_map.end()) {
				app_summary app;
				app.image = it->image;
				total_counts[i] += app.add_profile(*it, i);
				app_map[app.image] = app;
			} else {
				total_counts[i]
					+= ait->second.add_profile(*it, i);
			}
		}
	}

	app_map_t::const_iterator it = app_map.begin();
	app_map_t::const_iterator const end = app_map.end();

	for (; it != end; ++it)
		apps.push_back(it->second);

	// sort by count
	stable_sort(apps.begin(), apps.end());
	vector<app_summary>::iterator ait = apps.begin();
	vector<app_summary>::iterator const aend = apps.end();
	for (; ait != aend; ++ait)
		stable_sort(ait->deps.begin(), ait->deps.end());
}


void output_header()
{
	if (!options::show_header)
		return;

	cout << classes.cpuinfo << endl;
	if (!classes.event.empty())
		cout << classes.event << endl;

	for (vector<profile_class>::size_type i = 0;
	     i < classes.v.size(); ++i) {
		cout << classes.v[i].longname << endl;
	}
}


string get_filename(string const & filename)
{
	return options::long_filenames ? filename : op_basename(filename);
}


/// Output a count and a percentage
void output_count(count_type total_count, count_type count)
{
	cout << setw(9) << count << ' ';
	double ratio = op_ratio(count, total_count);
	cout << format_percent(ratio * 100, percent_int_width,
			      percent_fract_width) << ' ';
}


void output_col_headers(bool indent)
{
	if (!options::show_header)
		return;

	if (indent)
		cout << '\t';

	size_t colwidth = 9 + 1 + percent_width;

	for (size_t i = 0; i < classes.v.size(); ++i) {
		string name = classes.v[i].name;
		if (name.length() > colwidth)
			name = name.substr(0, colwidth - 3)
				+ "...";
		io_state state(cout);
		// gcc 2.95 doesn't know right io manipulator
		cout.setf(ios::right, ios::adjustfield);
		// gcc 2.95 doesn't honor setw() for std::string
		cout << setw(colwidth) << name.c_str();
		cout << '|';
	}
	cout << '\n';

	if (indent)
		cout << '\t';

	for (size_t i = 0; i < classes.v.size(); ++i) {
		cout << "  samples| ";
		io_state state(cout);
		// gcc 2.95 doesn't know right io manipulator
		cout.setf(ios::right, ios::adjustfield);
		cout << setw(percent_width) << "%|";
	}

	cout << '\n';

	if (indent)
		cout << '\t';

	for (size_t i = 0; i < classes.v.size(); ++i) {
		cout << "-----------";
		string str(percent_width, '-');
		cout << str;
	}

	cout << '\n';
}


void
output_deps(summary_container const & summaries,
	    app_summary const & app)
{
	// the app summary itself is *always* present
	// (perhaps with zero counts) so this test
	// is correct
	if (app.deps.size() == 1)
		return;

	output_col_headers(true);

	for (size_t j = 0 ; j < app.deps.size(); ++j) {
		summary const & summ = app.deps[j];

		if (summ.counts.zero())
			continue;

		cout << '\t';

		for (size_t i = 0; i < nr_classes; ++i) {
			count_type tot_count = options::global_percent
				? summaries.total_counts[i] : app.counts[i];

			output_count(tot_count, summ.counts[i]);
		}

		cout << get_filename(summ.lib_image);
		cout << '\n';
	}
}


/**
 * Display all the given summary information
 */
void output_summaries(summary_container const & summaries)
{
	output_col_headers(false);

	for (size_t i = 0; i < summaries.apps.size(); ++i) {
		app_summary const & app = summaries.apps[i];

		if ((app.counts[0] * 100.0) / summaries.total_counts[0]
		    < options::threshold) {
			continue;
		}

		for (size_t j = 0; j < nr_classes; ++j)
			output_count(summaries.total_counts[j], app.counts[j]);

		cout << get_filename(app.image) << '\n';

		output_deps(summaries, app);
	}
}


format_flags get_format_flags(column_flags const & cf)
{
	format_flags flags(ff_none);
	flags = format_flags(flags | ff_nr_samples);
	flags = format_flags(flags | ff_percent | ff_symb_name);

	if (options::show_address)
		flags = format_flags(flags | ff_vma);

	if (options::debug_info)
		flags = format_flags(flags | ff_linenr_info);

	if (options::accumulated) {
		flags = format_flags(flags | ff_nr_samples_cumulated);
		flags = format_flags(flags | ff_percent_cumulated);
	}

	if (classes2.v.size())
		flags = format_flags(flags | ff_diff);

	if (cf & cf_image_name)
		flags = format_flags(flags | ff_image_name);

	return flags;
}


void output_symbols(profile_container const & pc, bool multiple_apps)
{
	profile_container::symbol_choice choice;
	choice.threshold = options::threshold;
	symbol_collection symbols = pc.select_symbols(choice);
	options::sort_by.sort(symbols, options::reverse_sort,
	                      options::long_filenames);
	format_output::formatter * out;
	format_output::xml_formatter * xml_out = 0;
	format_output::opreport_formatter * text_out = 0;

	if (options::xml) {
		xml_out = new format_output::xml_formatter(&pc, symbols,
			pc.extra_found_images, options::symbol_filter);
		xml_out->show_details(options::details);
		out = xml_out;
		// for XML always output long filenames
		out->show_long_filenames(true);
	} else {
		text_out = new format_output::opreport_formatter(pc);
		text_out->show_details(options::details);
		out = text_out;
		out->show_long_filenames(options::long_filenames);
	}

	out->set_nr_classes(nr_classes);
	out->show_header(options::show_header);
	out->vma_format_64bit(choice.hints & cf_64bit_vma);
	out->show_global_percent(options::global_percent);

	format_flags flags = get_format_flags(choice.hints);
	if (multiple_apps)
		flags = format_flags(flags | ff_app_name);

	out->add_format(flags);

	if (options::xml) {
		xml_support = new xml_utils(xml_out, symbols, nr_classes,
			pc.extra_found_images);
		xml_out->output(cout);
	} else {
		text_out->output(cout, symbols);
	}
}


void output_diff_symbols(profile_container const & pc1,
                         profile_container const & pc2, bool multiple_apps)
{
	diff_container dc(pc1, pc2);

	profile_container::symbol_choice choice;
	choice.threshold = options::threshold;

	diff_collection symbols = dc.get_symbols(choice);

	format_flags flags = get_format_flags(choice.hints);
	if (multiple_apps)
		flags = format_flags(flags | ff_app_name);

	// With diff profile we output only filename coming from the first
	// profile session, internally we use only name derived from the sample
	// filename so image name can match.
	format_output::diff_formatter out(dc, pc1.extra_found_images);

	out.set_nr_classes(nr_classes);
	out.show_long_filenames(options::long_filenames);
	out.show_header(options::show_header);
	out.show_global_percent(options::global_percent);
	out.vma_format_64bit(choice.hints & cf_64bit_vma);
	out.add_format(flags);

	options::sort_by.sort(symbols, options::reverse_sort,
	                      options::long_filenames);

	out.output(cout, symbols);
}


void output_cg_symbols(callgraph_container const & cg, bool multiple_apps)
{
	column_flags output_hints = cg.output_hint();

	symbol_collection symbols = cg.get_symbols();

	options::sort_by.sort(symbols, options::reverse_sort,
	                      options::long_filenames);

	format_output::formatter * out;
	format_output::xml_cg_formatter * xml_out = 0;
	format_output::cg_formatter * text_out = 0;

	if (options::xml) {
		xml_out = new format_output::xml_cg_formatter(cg, symbols,
			options::symbol_filter);
		xml_out->show_details(options::details);
		out = xml_out;
		// for XML always output long filenames
		out->show_long_filenames(true);
	} else {
		text_out = new format_output::cg_formatter(cg);
		out = text_out;
		out->show_long_filenames(options::long_filenames);
	}

	out->set_nr_classes(nr_classes);
	out->show_header(options::show_header);
	out->vma_format_64bit(output_hints & cf_64bit_vma);
	out->show_global_percent(options::global_percent);

	format_flags flags = get_format_flags(output_hints);
	if (multiple_apps)
		flags = format_flags(flags | ff_app_name);

	out->add_format(flags);

	if (options::xml) {
		xml_support = new xml_utils(xml_out, symbols, nr_classes,
			cg.extra_found_images);
		xml_out->output(cout);
	} else {
		text_out->output(cout, symbols);
	}

}


int opreport(options::spec const & spec)
{
	want_xml = options::xml;

	handle_options(spec);

	nr_classes = classes.v.size();

	if (!options::symbols && !options::xml) {
		summary_container summaries(classes.v);
		output_header();
		output_summaries(summaries);
		return 0;
	}

	bool multiple_apps = false;

	for (size_t i = 0; i < classes.v.size(); ++i) {
		if (classes.v[i].profiles.size() > 1)
			multiple_apps = true;
	}

	list<inverted_profile> iprofiles = invert_profiles(classes);

	report_image_errors(iprofiles, classes.extra_found_images);

	if (options::xml) {
		xml_utils::output_xml_header(options::command_options,
		                             classes.cpuinfo, classes.event);
	} else {
		output_header();
	}

	if (classes2.v.size()) {
		for (size_t i = 0; i < classes2.v.size(); ++i) {
			if (classes2.v[i].profiles.size() > 1)
				multiple_apps |= true;
		}

		profile_container pc1(options::debug_info, options::details,
				      classes.extra_found_images);

		list<inverted_profile>::iterator it = iprofiles.begin();
		list<inverted_profile>::iterator const end = iprofiles.end();

		for (; it != end; ++it)
			populate_for_image(pc1, *it,
					   options::symbol_filter, 0);

		list<inverted_profile> iprofiles2 = invert_profiles(classes2);

		report_image_errors(iprofiles2, classes2.extra_found_images);

		profile_container pc2(options::debug_info, options::details,
				      classes2.extra_found_images);

		list<inverted_profile>::iterator it2 = iprofiles2.begin();
		list<inverted_profile>::iterator const end2 = iprofiles2.end();

		for (; it2 != end2; ++it2)
			populate_for_image(pc2, *it2,
					   options::symbol_filter, 0);

		output_diff_symbols(pc1, pc2, multiple_apps);
	} else if (options::callgraph) {
		callgraph_container cg_container;
		cg_container.populate(iprofiles, classes.extra_found_images,
			options::debug_info, options::threshold,
			options::merge_by.lib, options::symbol_filter);

		output_cg_symbols(cg_container, multiple_apps);
	} else {
		profile_container samples(options::debug_info,
			options::details, classes.extra_found_images);

		list<inverted_profile>::iterator it = iprofiles.begin();
		list<inverted_profile>::iterator const end = iprofiles.end();

		for (; it != end; ++it)
			populate_for_image(samples, *it,
					   options::symbol_filter, 0);

		output_symbols(samples, multiple_apps);
	}

	return 0;
}

}  // anonymous namespace


int main(int argc, char const * argv[])
{
	cout.tie(0);
	return run_pp_tool(argc, argv, opreport);
}