/**
 * @file opreport_options.cpp
 * Options for opreport tool
 *
 * @remark Copyright 2003 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <fstream>

#include "profile_spec.h"
#include "arrange_profiles.h"
#include "opreport_options.h"
#include "popt_options.h"
#include "string_filter.h"
#include "file_manip.h"
#include "xml_output.h"
#include "xml_utils.h"
#include "cverb.h"

using namespace std;

profile_classes classes;
profile_classes classes2;

namespace options {
	demangle_type demangle = dmt_normal;
	bool symbols;
	bool callgraph;
	bool debug_info;
	bool details;
	bool exclude_dependent;
	string_filter symbol_filter;
	sort_options sort_by;
	merge_option merge_by;
	bool show_header = true;
	bool long_filenames;
	bool show_address;
	bool accumulated;
	bool reverse_sort;
	bool global_percent;
	bool xml;
	string xml_options;
}


namespace {

string outfile;
vector<string> mergespec;
vector<string> sort;
vector<string> exclude_symbols;
vector<string> include_symbols;
string demangle_option = "normal";

popt::option options_array[] = {
	popt::option(options::callgraph, "callgraph", 'c',
	             "show call graph"),
	popt::option(options::details, "details", 'd',
		     "output detailed samples for each symbol"),
	popt::option(options::symbols, "symbols", 'l',
		     "list all symbols"),

	popt::option(outfile, "output-file", 'o',
	             "output to the given filename", "file"),

	popt::option(sort, "sort", 's',
		     "sort by", "sample,image,app-name,symbol,debug,vma"),
	popt::option(options::reverse_sort, "reverse-sort", 'r',
		     "use reverse sort"),
	popt::option(mergespec, "merge", 'm',
		     "comma separated list", "cpu,lib,tid,tgid,unitmask,all"),
	popt::option(options::exclude_dependent, "exclude-dependent", 'x',
		     "exclude libs, kernel, and module samples for applications"),
	popt::option(exclude_symbols, "exclude-symbols", 'e',
		     "exclude these comma separated symbols", "symbols"),
	popt::option(include_symbols, "include-symbols", 'i',
		     "include these comma separated symbols", "symbols"),
	popt::option(options::threshold_opt, "threshold", 't',
		     "minimum percentage needed to produce output",
		     "percent"),

	popt::option(demangle_option, "demangle", 'D',
		     "demangle GNU C++ symbol names (default normal)",
	             "none|normal|smart"),
	// PP:5
	popt::option(options::debug_info, "debug-info", 'g',
		     "add source file and line number to output"),
	popt::option(options::show_header, "no-header", 'n',
		     "remove all headers from output"),
	popt::option(options::show_address, "show-address", 'w',
	             "show VMA address of each symbol"),
	popt::option(options::long_filenames, "long-filenames", 'f',
		     "show the full path of filenames"),
	popt::option(options::accumulated, "accumulated", 'a',
		     "percentage field show accumulated count"),
	popt::option(options::global_percent, "global-percent", '%',
		     "percentage are not relative to symbol count or image "
		     "count but total sample count"),

	popt::option(options::xml, "xml", 'X',
		     "XML output"),

};


void handle_sort_option()
{
	if (options::xml && !sort.empty()) {
		cerr << "warning: sort options ignored because they "
		     << "are incompatible with --xml" << endl;
		// don't allow any other sorting, except the default below,
		// to mess up symbol traversal for XML
		sort.clear();
	}

	if (sort.empty() || options::xml) {
		// PP:5.14 sort default to sample
		if (options::xml) {
			// implicitly sort by app-name,image so that in the
			// symbol traversal all library module symbols are
			// grouped together with their application
			sort.push_back("app-name");
			sort.push_back("image");
		} else
			sort.push_back("sample");
	}

	vector<string>::const_iterator cit = sort.begin();
	vector<string>::const_iterator end = sort.end();

	for (; cit != end; ++cit)
		options::sort_by.add_sort_option(*cit);
}


void handle_output_file()
{
	if (outfile.empty())
		return;

	static ofstream os(outfile.c_str());
	if (!os) {
		cerr << "Couldn't open \"" << outfile
		     << "\" for writing." << endl;
		exit(EXIT_FAILURE);
	}

	cout.rdbuf(os.rdbuf());
}


///  Check incompatible or meaningless options.
void check_options(bool diff)
{
	using namespace options;

	bool do_exit = false;

	if (callgraph) {
		symbols = true;
		if (details) {
			cerr << "--callgraph is incompatible with --details" << endl;
			do_exit = true;
		}

		if (diff) {
			cerr << "differential profiles are incompatible with --callgraph" << endl;
			do_exit = true;
		}
	}

	if (xml) {
		if (accumulated) {
			cerr << "--accumulated is incompatible with --xml" << endl;
			do_exit = true;
		}

		if (global_percent) {
			cerr << "--global_percent is incompatible with --xml" << endl;
			do_exit = true;
		}
	}


	if (details && diff) {
		cerr << "differential profiles are incompatible with --details" << endl;
		do_exit = true;
	}

	if (!symbols) {
		if (diff) {
			cerr << "different profiles are meaningless "
				"without --symbols" << endl;
			do_exit = true;
		}

		if (show_address) {
			cerr << "--show-address is meaningless "
				"without --symbols" << endl;
			do_exit = true;
		}

		if (debug_info || accumulated) {
			cerr << "--debug-info and --accumulated are "
			     << "meaningless without --symbols" << endl;
			do_exit = true;
		}

		if (!exclude_symbols.empty() || !include_symbols.empty()) {
			cerr << "--exclude-symbols and --include-symbols are "
			     << "meaningless without --symbols" << endl;
			do_exit = true;
		}

		if (find(sort_by.options.begin(), sort_by.options.end(), 
			 sort_options::vma) != sort_by.options.end()) {
			cerr << "--sort=vma is "
			     << "meaningless without --symbols" << endl;
			do_exit = true;
		}
	}

	if (global_percent && symbols && !(details || callgraph)) {
		cerr << "--global-percent is meaningless with --symbols "
		        "and without --details or --callgraph" << endl;
		do_exit = true;
	}

	if (do_exit)
		exit(EXIT_FAILURE);
}


/// process a spec into classes
void process_spec(profile_classes & classes, list<string> const & spec)
{
	using namespace options;

	copy(spec.begin(), spec.end(),
	     ostream_iterator<string>(cverb << vsfile, " "));
	cverb << vsfile << "\n\n";

	profile_spec const pspec =
		profile_spec::create(spec, options::image_path,
				     options::root_path);

	list<string> sample_files = pspec.generate_file_list(exclude_dependent,
	                                                     !options::callgraph);

	cverb << vsfile << "Archive: " << pspec.get_archive_path() << endl;

	cverb << vsfile << "Matched sample files: " << sample_files.size()
	      << endl;
	copy(sample_files.begin(), sample_files.end(),
	     ostream_iterator<string>(cverb << vsfile, "\n"));

	classes = arrange_profiles(sample_files, merge_by,
				   pspec.extra_found_images);

	cverb << vsfile << "profile_classes:\n" << classes << endl;

	if (classes.v.empty()) {
		cerr << "error: no sample files found: profile specification "
		     "too strict ?" << endl;
		exit(EXIT_FAILURE);
	}
}


} // namespace anon


void handle_options(options::spec const & spec)
{
	using namespace options;

	if (details) {
		symbols = true;
		show_address = true;
	}

	if (options::xml) {
		if (spec.common.size() != 0)
			xml_utils::add_option(SESSION, spec.common);
		if (debug_info)
			xml_utils::add_option(DEBUG_INFO, true);
		if (details)
			xml_utils::add_option(DETAILS, true);
		if (!image_path.empty())
			xml_utils::add_option(IMAGE_PATH, image_path);
		if (!mergespec.empty())
			xml_utils::add_option(MERGE, mergespec);
		if (exclude_dependent)
			xml_utils::add_option(EXCLUDE_DEPENDENT, true);
		if (!exclude_symbols.empty())
			xml_utils::add_option(EXCLUDE_SYMBOLS, exclude_symbols);
		if (!include_symbols.empty())
			xml_utils::add_option(INCLUDE_SYMBOLS, include_symbols);
	}

	handle_sort_option();
	merge_by = handle_merge_option(mergespec, true, exclude_dependent);
	handle_output_file();
	demangle = handle_demangle_option(demangle_option);
	check_options(spec.first.size());

	symbol_filter = string_filter(include_symbols, exclude_symbols);

	if (!spec.first.size()) {
		process_spec(classes, spec.common);
	} else {
		if (options::xml) {
			cerr << "differential profiles are incompatible with --xml" << endl;
			exit(EXIT_FAILURE);
		}
		cverb << vsfile << "profile spec 1:" << endl;
		process_spec(classes, spec.first);
		cverb << vsfile << "profile spec 2:" << endl;
		process_spec(classes2, spec.second);

		if (!classes.matches(classes2)) {
			cerr << "profile classes are incompatible" << endl;
			exit(EXIT_FAILURE);
		}
	}
}