/*
 * Copyright 2010, Intel Corporation
 *
 * This file is part of PowerTOP
 *
 * This program file is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program in a file named COPYING; if not, write to the
 * Free Software Foundation, Inc,
 * 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 * or just google for it.
 *
 * Authors:
 *	Arjan van de Ven <arjan@linux.intel.com>
 */
#include <iostream>
#include <malloc.h>
#include <algorithm>
#include <cstring>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

#include "perf_bundle.h"
#include "perf_event.h"
#include "perf.h"

#include "../cpu/cpu.h"

#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L)
# define USE_DECLTYPE
#endif

class perf_bundle_event: public perf_event
{
public:
	perf_bundle_event(void);
	virtual void handle_event(const struct perf_event_header *header, void *cookie) override;
};

perf_bundle_event::perf_bundle_event(void) : perf_event()
{
}


void perf_bundle_event::handle_event(const struct perf_event_header *header, void *cookie)
{
	unsigned char *buffer;
	std::vector<void *> *vector;

	buffer = (unsigned char *)malloc(header->size);
	memcpy(buffer, header, header->size);

#ifdef USE_DECLTYPE
	vector = (decltype(vector))cookie;
#else
	vector = (typeof(vector))cookie;
#endif
	vector->push_back(buffer);
}


void perf_bundle::release(void)
{
	for (auto &ev : events)
		ev->clear();
	events.clear();

	for (auto record : records)
		free(record);
	records.clear();
}

bool perf_bundle::add_event(const std::string &system_name, const std::string &event_name)
{
	unsigned int i;
	int event_added = false;

	for (i = 0; i < all_cpus.size(); i++) {

		if (!all_cpus[i])
			continue;

		auto ev = std::make_unique<perf_bundle_event>();

		ev->set_event_name(system_name, event_name);
		ev->set_cpu(i);

		if ((int)ev->trace_type >= 0) {
			events.push_back(std::move(ev));
			event_added = true;
		}
	}
	return event_added;
}

void perf_bundle::start(void)
{
	for (auto &ev : events)
		ev->start();
}
void perf_bundle::stop(void)
{
	for (auto &ev : events)
		ev->stop();
}
void perf_bundle::clear(void)
{
	for (auto &ev : events)
		ev->clear();

	for (auto record : records)
		free(record);
	records.clear();
}


struct trace_entry {
	uint64_t		time;
	uint32_t		cpu;
	uint32_t		res;
	__u32			size;
} __attribute__((packed));;


struct perf_sample {
	struct perf_event_header        header;
	struct trace_entry		trace;
	unsigned char			data[0];
} __attribute__((packed));

static uint64_t timestamp(const perf_event_header *event)
{
	struct perf_sample *sample;

	if (event->type != PERF_RECORD_SAMPLE)
		return 0;

	sample = (struct perf_sample *)event;

#if 0
	int i;
	unsigned char *x;

	printf("header:\n");
	printf("	type  is %x \n", sample->header.type);
	printf("	misc  is %x \n", sample->header.misc);
	printf("	size  is %i \n", sample->header.size);
	printf("sample:\n");
	printf("	time  is %llx \n", sample->trace.time);
	printf("	cpu   is %i / %x \n", sample->trace.cpu, sample->trace.cpu);
	printf("	res   is %i / %x \n", sample->trace.res, sample->trace.res);
	printf("	size  is %i / %x \n", sample->trace.size, sample->trace.size);
	printf("	type  is %i / %x \n", sample->trace.type, sample->trace.type);
	printf("	flags is %i / %x \n", sample->trace.flags, sample->trace.flags);
	printf("	p/c   is %i / %x \n", sample->trace.preempt_count, sample->trace.preempt_count);
	printf("	pid   is %i / %x \n", sample->trace.pid, sample->trace.pid);
	printf("	lock dept  is %i / %x \n", sample->trace.lock_depth, sample->trace.lock_depth);

	x = (unsigned char *)sample;
	for (i = 0; i < sample->header.size; i++)
		printf("%02x ", *(x+i));
	printf("\n");
#endif
	return sample->trace.time;

}

static bool event_sort_function (const void *i, const void *j)
{
	const struct perf_event_header *I, *J;

	I = (const struct perf_event_header *) i;
	J = (const struct perf_event_header *) j;
	return (timestamp(I)<timestamp(J));
}

/*
 * sample's PERF_SAMPLE_CPU cpu nr is a raw_smp_processor_id() by the
 * time of perf_event_output(), which may differ from struct perf_event
 * cpu, thus we need to fix sample->trace.cpu.
 */
static void fixup_sample_trace_cpu(struct perf_sample *sample)
{
	struct tep_event *event;
	struct tep_record rec;
	unsigned long long cpu_nr;
	int type;
	int ret;

	rec.data = &sample->data;
	type = tep_data_type(perf_event::tep, &rec);
	event = tep_find_event(perf_event::tep, type);
	if (!event)
		return;
	/** don't touch trace if event does not contain cpu_id field*/
	ret = tep_get_field_val(nullptr, event, "cpu_id", &rec, &cpu_nr, 0);
	if (ret < 0)
		return;
	sample->trace.cpu = cpu_nr;
}

void perf_bundle::process(void)
{
	/* fixme: reserve enough space in the array in one go */
	for (auto &ev : events)
		ev->process(&records);
	std::sort(records.begin(), records.end(), event_sort_function);

	for (auto record : records) {
		struct perf_sample *sample = (struct perf_sample *)record;
		if (!sample)
			continue;

		if (sample->header.type != PERF_RECORD_SAMPLE)
			continue;

		fixup_sample_trace_cpu(sample);
		handle_trace_point(&sample->data, sample->trace.cpu, sample->trace.time);
	}
}

void perf_bundle::handle_trace_point([[maybe_unused]] void *trace, [[maybe_unused]] int cpu, [[maybe_unused]] uint64_t time)
{
	fprintf(stderr, _("abstract handle_trace_point called\n"));
}
