#include "ep-rt-config.h"

#ifdef ENABLE_PERFTRACING
#if !defined(EP_INCLUDE_SOURCE_FILES) || defined(EP_FORCE_INCLUDE_SOURCE_FILES)

#define EP_IMPL_BUFFER_GETTER_SETTER
#include "ep.h"
#include "ep-buffer.h"
#include "ep-event.h"
#include "ep-event-instance.h"
#include "ep-event-payload.h"
#include "ep-session.h"

/*
 * EventPipeBuffer.
 */

EventPipeBuffer *
ep_buffer_alloc (
	uint32_t buffer_size,
	EventPipeThread *writer_thread,
	uint32_t event_sequence_number)
{
	EventPipeBuffer *instance = ep_rt_object_alloc (EventPipeBuffer);
	ep_raise_error_if_nok (instance != NULL);

	instance->writer_thread = writer_thread;
	instance->event_sequence_number = event_sequence_number;

	instance->buffer = ep_rt_valloc0 (buffer_size);
	ep_raise_error_if_nok (instance->buffer);

	instance->limit = instance->buffer + buffer_size;
	instance->current = ep_buffer_get_next_aligned_address (instance, instance->buffer);

	instance->creation_timestamp = ep_perf_timestamp_get ();
	EP_ASSERT (instance->creation_timestamp > 0);

	instance->current_read_event = NULL;
	instance->prev_buffer = NULL;
	instance->next_buffer = NULL;

	ep_rt_volatile_store_uint32_t (&instance->state, (uint32_t)EP_BUFFER_STATE_WRITABLE);

ep_on_exit:
	return instance;

ep_on_error:
	ep_buffer_free (instance);
	instance = NULL;
	ep_exit_error_handler ();
}

void
ep_buffer_free (EventPipeBuffer *buffer)
{
	ep_return_void_if_nok (buffer != NULL);

	// We should never be deleting a buffer that a writer thread might still try to write to
	EP_ASSERT (ep_rt_volatile_load_uint32_t (&buffer->state) == (uint32_t)EP_BUFFER_STATE_READ_ONLY);

	ep_rt_vfree (buffer->buffer, buffer->limit - buffer->buffer);
	ep_rt_object_free (buffer);
}

bool
ep_buffer_write_event (
	EventPipeBuffer *buffer,
	ep_rt_thread_handle_t thread,
	EventPipeSession *session,
	EventPipeEvent *ep_event,
	EventPipeEventPayload *payload,
	const uint8_t *activity_id,
	const uint8_t *related_activity_id,
	EventPipeStackContents *stack)
{
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (payload != NULL);
	EP_ASSERT (((size_t)buffer->current % EP_BUFFER_ALIGNMENT_SIZE) == 0);

	// We should never try to write to a buffer that isn't expecting to be written to.
	EP_ASSERT ((EventPipeBufferState)buffer->state == EP_BUFFER_STATE_WRITABLE);

	bool success = true;
	EventPipeEventInstance *instance = NULL;

	// Calculate the location of the data payload.
	uint8_t *data_dest;
	data_dest = (ep_event_payload_get_size (payload) == 0 ? NULL : buffer->current + sizeof (*instance) - sizeof (instance->stack_contents_instance.stack_frames) + ep_stack_contents_get_full_size (stack));

	// Calculate the size of the event.
	uint32_t event_size = sizeof (*instance) - sizeof (instance->stack_contents_instance.stack_frames) + ep_stack_contents_get_full_size (stack) + ep_event_payload_get_size (payload);

	// Make sure we have enough space to write the event.
	if(buffer->current + event_size > buffer->limit)
		ep_raise_error ();

	uint32_t proc_number;
	proc_number = ep_rt_current_processor_get_number ();
	instance = ep_event_instance_init (
		(EventPipeEventInstance *)buffer->current,
		ep_event,
		proc_number,
		ep_rt_thread_id_t_to_uint64_t((thread == NULL) ? ep_rt_current_thread_get_id () : ep_rt_thread_get_id (thread)),
		data_dest,
		ep_event_payload_get_size (payload),
		(thread == NULL) ? NULL : activity_id,
		related_activity_id);
	ep_raise_error_if_nok (instance != NULL);

	// Copy the stack if a separate stack trace was provided.
	if (stack != NULL)
		ep_stack_contents_flatten (stack, ep_event_instance_get_stack_contents_instance_ref (instance));

	// Write the event payload data to the buffer.
	if (ep_event_payload_get_size (payload) > 0)
		ep_event_payload_copy_data (payload, data_dest);

	EP_ASSERT (success);

	// Advance the current pointer past the event.
	buffer->current = ep_buffer_get_next_aligned_address (buffer, buffer->current + event_size);

ep_on_exit:
	return success;

ep_on_error:
	success = false;
	ep_exit_error_handler ();
}

void
ep_buffer_move_next_read_event (EventPipeBuffer *buffer)
{
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (ep_rt_volatile_load_uint32_t (&buffer->state) == (uint32_t)EP_BUFFER_STATE_READ_ONLY);

	// If current_read_event is NULL we've reached the end of the events
	if (buffer->current_read_event != NULL) {
		// Confirm that current_read_event is within the used range of the buffer.
		if (((uint8_t *)buffer->current_read_event < buffer->buffer) || ((uint8_t *)buffer->current_read_event >= buffer->current)) {
			EP_ASSERT (!"Input pointer is out of range.");
			buffer->current_read_event = NULL;
		} else {
			buffer->current_read_event = (EventPipeEventInstance *)ep_buffer_get_next_aligned_address (buffer, (uint8_t *)buffer->current_read_event + ep_event_instance_get_flattened_size (buffer->current_read_event));

			// this may roll over and that is fine
			buffer->event_sequence_number++;

			// Check to see if we've reached the end of the written portion of the buffer.
			if ((uint8_t *)buffer->current_read_event >= buffer->current)
				buffer->current_read_event = NULL;
		}
	}

	// Ensure that the timestamp is valid.  The buffer is zero'd before use, so a zero timestamp is invalid.
#ifdef EP_CHECKED_BUILD
	if (buffer->current_read_event != NULL) {
		ep_timestamp_t next_timestamp = ep_event_instance_get_timestamp (buffer->current_read_event);
		EP_ASSERT (next_timestamp != 0);
	}
#endif
}

EventPipeEventInstance *
ep_buffer_get_current_read_event (const EventPipeBuffer *buffer)
{
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (ep_rt_volatile_load_uint32_t (&buffer->state) == (uint32_t)EP_BUFFER_STATE_READ_ONLY);
	return buffer->current_read_event;
}

uint32_t
ep_buffer_get_current_sequence_number (const EventPipeBuffer *buffer)
{
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (ep_rt_volatile_load_uint32_t (&buffer->state) == (uint32_t)EP_BUFFER_STATE_READ_ONLY);
	return buffer->event_sequence_number;
}

EventPipeBufferState
ep_buffer_get_volatile_state (const EventPipeBuffer *buffer)
{
	EP_ASSERT (buffer != NULL);
	return (EventPipeBufferState)ep_rt_volatile_load_uint32_t (&buffer->state);
}

void
ep_buffer_convert_to_read_only (EventPipeBuffer *buffer)
{
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (buffer->current_read_event == NULL);

	ep_thread_requires_lock_held (buffer->writer_thread);

	ep_rt_volatile_store_uint32_t (&buffer->state, (uint32_t)EP_BUFFER_STATE_READ_ONLY);

	// If this buffer contains an event, select it.
	uint8_t *first_aligned_instance = ep_buffer_get_next_aligned_address (buffer, buffer->buffer);
	if (buffer->current > first_aligned_instance)
		buffer->current_read_event = (EventPipeEventInstance*)first_aligned_instance;
	else
		buffer->current_read_event = NULL;
}

#ifdef EP_CHECKED_BUILD
bool
ep_buffer_ensure_consistency (const EventPipeBuffer *buffer)
{
	EP_ASSERT (buffer != NULL);

	uint8_t *ptr = ep_buffer_get_next_aligned_address (buffer, buffer->buffer);

	// Check to see if the buffer is empty.
	if (ptr == buffer->current)
		// Make sure that the buffer size is greater than zero.
		EP_ASSERT (buffer->buffer != buffer->limit);

	// Validate the contents of the filled portion of the buffer.
	while (ptr < buffer->current) {
		// Validate the event.
		EventPipeEventInstance *instance = (EventPipeEventInstance *)ptr;
		EP_ASSERT (ep_event_instance_ensure_consistency (instance));

		// Validate that payload and length match.
		EP_ASSERT (
			(ep_event_instance_get_data (instance) != NULL && ep_event_instance_get_data_len (instance) > 0) ||
			(ep_event_instance_get_data (instance) == NULL && ep_event_instance_get_data_len (instance) == 0)
		);

		// Skip the event.
		ptr = ep_buffer_get_next_aligned_address (buffer, ptr + ep_event_instance_get_flattened_size (instance));
	}

	// When we're done walking the filled portion of the buffer,
	// ptr should be the same as m_pCurrent.
	EP_ASSERT (ptr == buffer->current);

	// Walk the rest of the buffer, making sure it is properly zeroed.
	while (ptr < buffer->limit) {
		EP_ASSERT (*ptr == 0);
		ptr++;
	}

	return true;
}
#endif

#endif /* !defined(EP_INCLUDE_SOURCE_FILES) || defined(EP_FORCE_INCLUDE_SOURCE_FILES) */
#endif /* ENABLE_PERFTRACING */

#if !defined(ENABLE_PERFTRACING) || (defined(EP_INCLUDE_SOURCE_FILES) && !defined(EP_FORCE_INCLUDE_SOURCE_FILES))
extern const char quiet_linker_empty_file_warning_eventpipe_buffer;
const char quiet_linker_empty_file_warning_eventpipe_buffer = 0;
#endif
