#include "throughput_monitor.h"
#include "application_definitions.h"

#ifdef SPACE_IMPLEMENTATION
#include <chrono>
extern "C" {
	#include "gt.h"
}
#endif // SPACE_IMPLEMENTATION

static unsigned long generic_count = 0;
static unsigned long static_generic_count() {
	return generic_count++;
}

static void timer_start() { }
static void timer_stop () { }
static void timer_reset() { }

#ifdef SPACE_IMPLEMENTATION
static unsigned long long timer_value() {
	return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
#endif // SPACE_IMPLEMENTATION

#ifdef SPACE_IMPLEMENTATION
throughput_monitor::throughput_monitor()
#else // SPACE_IMPLEMENTATION
throughput_monitor::throughput_monitor(timer_value_cb timer_value)
#endif // SPACE_IMPLEMENTATION
	: m_timer_callbacks({&timer_start, &timer_stop, &timer_reset, timer_value})
	, m_count(&static_generic_count)
{
	m_timer_callbacks.start();
	unsigned long long value0 = m_timer_callbacks.value();
	unsigned long long value1 = m_timer_callbacks.value();
	if (value0 > value1)
		m_access_delay = value0 - value1;
	else
		m_access_delay = value1 - value0;
    reset();
}

throughput_stat throughput_monitor::get_throughput() {
	double throughput = -1.0;
	double delta_time = 0.0;
	unsigned long delta_count = 0;
	unsigned long current_count = m_count();
	unsigned long long current_value = m_timer_callbacks.value();

	if (m_value > current_value || m_last_count > current_count) {
		reset();
	}
	else {
		delta_count = current_count - m_last_count;

		double delta_value;
		if (current_value > m_value + 2 * m_access_delay) {
			delta_value = current_value - m_value - 2 * m_access_delay;
		}
		else {
			delta_value = 0.0;
		}

		if (delta_value > 0.0) {
			delta_time = delta_value / FREQUENCY;
			throughput = delta_count / delta_time;
		}
	}

	if (delta_count > 0) {
		m_value = current_value;
		m_last_count = current_count;
	}

	return {throughput, delta_time};
}

void throughput_monitor::reset() {
	m_timer_callbacks.stop();
	m_timer_callbacks.reset();
	m_timer_callbacks.start();
	m_last_count = m_count();
	m_value = m_timer_callbacks.value();
}
