/**
 * @file op_header.cpp
 * various free function acting on a sample file header
 *
 * @remark Copyright 2004 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 * @Modifications Daniel Hansel
 */

#include <cstring>
#include <iostream>
#include <cstdlib>
#include <iomanip>
#include <set>
#include <sstream>
#include <cstring>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "op_config.h"
#include "op_exception.h"
#include "odb.h"
#include "op_cpu_type.h"
#include "op_file.h"
#include "op_header.h"
#include "op_events.h"
#include "string_manip.h"
#include "format_output.h"
#include "xml_utils.h"
#include "cverb.h"

using namespace std;

extern verbose vbfd;

void op_check_header(opd_header const & h1, opd_header const & h2,
		     string const & filename)
{
	if (h1.mtime != h2.mtime) {
		ostringstream os;
		os << "header timestamps are different ("
		   << h1.mtime << ", " << h2.mtime << ") for "
		   << filename << "\n";
		throw op_fatal_error(os.str());
	}

	if (h1.is_kernel != h2.is_kernel) {
		ostringstream os;
		os << "header is_kernel flags are different for "
		   << filename << "\n";
		throw op_fatal_error(os.str());
	}
	
	// Note that in the generated ELF file for anonymous code the vma
	// of the symbol is exaclty the same vma as the code had during sampling.
	
	// Note that we don't check CPU speed since that can vary
	// freely on the same machine
}


namespace {

set<string> warned_files;

}

bool is_jit_sample(string const & filename)
{
	// suffix for JIT sample files (see FIXME in check_mtime() below)
	string suf = ".jo";
	
	string::size_type pos;
	pos = filename.rfind(suf);
	// for JIT sample files do not output the warning to stderr.
	if (pos != string::npos && pos == filename.size() - suf.size())
		return true;
	else
		return false;
}

void check_mtime(string const & file, opd_header const & header)
{
	time_t const newmtime = op_get_mtime(file.c_str());

	if (newmtime == header.mtime)
		return;

	if (warned_files.find(file) != warned_files.end())
		return;

	warned_files.insert(file);

	// Files we couldn't get mtime of have zero mtime
	if (!header.mtime) {
		// FIXME: header.mtime for JIT sample files is 0. The problem could be that
		//        in opd_mangling.c:opd_open_sample_file() the call of fill_header()
		//        think that the JIT sample file is not a binary file.
		if (is_jit_sample(file)) {
			cverb << vbfd << "warning: could not check that the binary file "
			      << file << " has not been modified since "
			      "the profile was taken. Results may be inaccurate.\n";
		} else {
			cerr << "warning: could not check that the binary file "
			     << file << " has not been modified since "
			     "the profile was taken. Results may be inaccurate.\n";
		}
	} else {
		static bool warned_already = false;

#ifdef ANDROID
		// Android symbol files may not have the same timestamp as the stripped
		// files deployed to the device.  Suppress spurious warnings.
		if (file.find("/symbols/") == string::npos) {
#endif

		cerr << "warning: the last modified time of the binary file "
		     "does not match that of the sample file for " << file
		     << "\n";

		if (!warned_already) {
			cerr << "Either this is the wrong binary or the binary "
			"has been modified since the sample file was created.\n";
			warned_already = true;
		}

#ifdef ANDROID
		}
#endif
	}
}


opd_header const read_header(string const & sample_filename)
{
	int fd = open(sample_filename.c_str(), O_RDONLY);
	if (fd < 0)
		throw op_fatal_error("Can't open sample file:" +
				     sample_filename);

	opd_header header;
	if (read(fd, &header, sizeof(header)) != sizeof(header)) {
		close(fd);
		throw op_fatal_error("Can't read sample file header:" +
				     sample_filename);
	}

	if (memcmp(header.magic, OPD_MAGIC, sizeof(header.magic))) {
		throw op_fatal_error("Invalid sample file, "
				     "bad magic number: " +
				     sample_filename);
		close(fd);
	}

	close(fd);

	return header;
}


namespace {

string const op_print_event(op_cpu cpu_type, u32 type, u32 um, u32 count)
{
	string str;

	if (cpu_type == CPU_TIMER_INT) {
		str += "Profiling through timer interrupt";
		return str;
	}

	struct op_event * event = op_find_event(cpu_type, type, um);

	if (!event) {
		event = op_find_event_any(cpu_type, type);
		if (!event) { 
			cerr << "Could not locate event " << int(type) << endl;
			str = "Unknown event";
			return str;
		}
	}

	char const * um_desc = 0;

	for (size_t i = 0; i < event->unit->num; ++i) {
		if (event->unit->um[i].value == um)
			um_desc = event->unit->um[i].desc;
	}

	str += string("Counted ") + event->name;
	str += string(" events (") + event->desc + ")";

	if (cpu_type != CPU_RTC) {
		str += " with a unit mask of 0x";

		ostringstream ss;
		ss << hex << setw(2) << setfill('0') << unsigned(um);
		str += ss.str();

		str += " (";
		str += um_desc ? um_desc : "multiple flags";
		str += ")";
	}

	str += " count " + op_lexical_cast<string>(count);
	return str;
}

string const op_xml_print_event(op_cpu cpu_type, u32 type, u32 um, u32 count)
{
	string unit_mask;

	if (cpu_type == CPU_TIMER_INT || cpu_type == CPU_RTC)
		return xml_utils::get_timer_setup((size_t)count);

	struct op_event * event = op_find_event(cpu_type, type, um);
	if (!event) {
		event = op_find_event_any(cpu_type, type);
		if (!event) { 
			cerr << "Could not locate event " << int(type) << endl;
			return "";
		}
	}

	if (cpu_type != CPU_RTC) {
		ostringstream str_out;
		str_out << um;
		unit_mask = str_out.str();
	}

	return xml_utils::get_event_setup(string(event->name),
		(size_t)count, unit_mask);
}

}

string const describe_header(opd_header const & header)
{
	op_cpu cpu = static_cast<op_cpu>(header.cpu_type);

	if (want_xml)
		return op_xml_print_event(cpu, header.ctr_event,
	                      header.ctr_um, header.ctr_count);
	else
		return op_print_event(cpu, header.ctr_event,
	                      header.ctr_um, header.ctr_count);
}


string const describe_cpu(opd_header const & header)
{
	op_cpu cpu = static_cast<op_cpu>(header.cpu_type);

	string str;
	if (want_xml) {
		string cpu_name = op_get_cpu_name(cpu);

		str = xml_utils::get_profile_header(cpu_name, header.cpu_speed);
	} else {
		str += string("CPU: ") + op_get_cpu_type_str(cpu);
		str += ", speed ";

		ostringstream ss;
		ss << header.cpu_speed;
		str += ss.str() + " MHz (estimated)";
	}
	return str;
}