#include "ds-rt-config.h"

#ifdef ENABLE_PERFTRACING
#if !defined(DS_INCLUDE_SOURCE_FILES) || defined(DS_FORCE_INCLUDE_SOURCE_FILES)

#define DS_IMPL_PROFILER_PROTOCOL_GETTER_SETTER
#include "ds-protocol.h"
#include "ds-profiler-protocol.h"
#include "ds-server.h"
#include "ds-rt.h"

#ifdef PROFILING_SUPPORTED

/*
 * Forward declares of all static functions.
 */
static
uint8_t *
attach_profiler_command_try_parse_payload (
	uint8_t *buffer,
	uint16_t buffer_len);

static
uint8_t *
startup_profiler_command_try_parse_payload (
	uint8_t *buffer,
	uint16_t buffer_len);

static
bool
profiler_protocol_helper_attach_profiler (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
profiler_protocol_helper_startup_profiler (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
profiler_protocol_helper_unknown_command (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

#ifdef FEATURE_PROFAPI_ATTACH_DETACH

/*
* DiagnosticsAttachProfilerCommandPayload
*/

static
uint8_t *
attach_profiler_command_try_parse_payload (
	uint8_t *buffer,
	uint16_t buffer_len)
{
	EP_ASSERT (buffer != NULL);

	uint8_t * buffer_cursor = buffer;
	uint32_t buffer_cursor_len = buffer_len;

	DiagnosticsAttachProfilerCommandPayload *instance = ds_attach_profiler_command_payload_alloc ();
	ep_raise_error_if_nok (instance != NULL);

	instance->incoming_buffer = buffer;

	if (!ds_ipc_message_try_parse_uint32_t (&buffer_cursor, &buffer_cursor_len, &instance->attach_timeout ) ||
		!ds_ipc_message_try_parse_value (&buffer_cursor, &buffer_cursor_len, (uint8_t *)&instance->profiler_guid, (uint32_t)ARRAY_SIZE (instance->profiler_guid)) ||
		!ds_ipc_message_try_parse_string_utf16_t (&buffer_cursor, &buffer_cursor_len, &instance->profiler_path) ||
		!ds_ipc_message_try_parse_uint32_t (&buffer_cursor, &buffer_cursor_len, &instance->client_data_len) ||
		!(buffer_cursor_len <= instance->client_data_len))
		ep_raise_error ();

	instance->client_data = buffer_cursor;

ep_on_exit:
	return (uint8_t *)instance;

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

DiagnosticsAttachProfilerCommandPayload *
ds_attach_profiler_command_payload_alloc (void)
{
	return ep_rt_object_alloc (DiagnosticsAttachProfilerCommandPayload);
}

void
ds_attach_profiler_command_payload_free (DiagnosticsAttachProfilerCommandPayload *payload)
{
	ep_return_void_if_nok (payload != NULL);
	ep_rt_byte_array_free (payload->incoming_buffer);
	ep_rt_object_free (payload);
}

static
bool
profiler_protocol_helper_attach_profiler (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	if (!stream)
		return false;

    bool result = false;
    DiagnosticsAttachProfilerCommandPayload *payload = NULL;

    if (!ep_rt_is_running ()) {
        ds_ipc_message_send_error (stream, DS_IPC_E_NOT_YET_AVAILABLE);
        ep_raise_error ();
    }

	payload = (DiagnosticsAttachProfilerCommandPayload *)ds_ipc_message_try_parse_payload (message, attach_profiler_command_try_parse_payload);

	if (!payload) {
		ds_ipc_message_send_error (stream, DS_IPC_E_BAD_ENCODING);
		ep_raise_error ();
	}

	ds_ipc_result_t ipc_result;
	ipc_result = ds_rt_profiler_attach (payload);
	if (ipc_result != DS_IPC_S_OK) {
		ds_ipc_message_send_error (stream, ipc_result);
		ep_raise_error ();
	} else {
		ds_ipc_message_send_success (stream, ipc_result);
	}

	result = true;

ep_on_exit:
	ds_attach_profiler_command_payload_free (payload);
    ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ep_exit_error_handler ();
}

#endif // FEATURE_PROFAPI_ATTACH_DETACH

DiagnosticsStartupProfilerCommandPayload *
ds_startup_profiler_command_payload_alloc (void)
{
	return ep_rt_object_alloc (DiagnosticsStartupProfilerCommandPayload);
}

void
ds_startup_profiler_command_payload_free (DiagnosticsStartupProfilerCommandPayload *payload)
{
	ep_return_void_if_nok (payload != NULL);
	ep_rt_byte_array_free (payload->incoming_buffer);
	ep_rt_object_free (payload);
}

/*
 * DiagnosticsProfilerProtocolHelper.
 */

static
bool
profiler_protocol_helper_unknown_command (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	DS_LOG_WARNING_1 ("Received unknown request type (%d)", ds_ipc_header_get_commandset (ds_ipc_message_get_header_ref (message)));
	ds_ipc_message_send_error (stream, DS_IPC_E_UNKNOWN_COMMAND);
	return true;
}

static
uint8_t *
startup_profiler_command_try_parse_payload (
	uint8_t *buffer,
	uint16_t buffer_len)
{
	EP_ASSERT (buffer != NULL);

	uint8_t * buffer_cursor = buffer;
	uint32_t buffer_cursor_len = buffer_len;

	DiagnosticsStartupProfilerCommandPayload *instance = ds_startup_profiler_command_payload_alloc ();
	ep_raise_error_if_nok (instance != NULL);

	instance->incoming_buffer = buffer;

	if (!ds_ipc_message_try_parse_value (&buffer_cursor, &buffer_cursor_len, (uint8_t *)&instance->profiler_guid, (uint32_t)ARRAY_SIZE (instance->profiler_guid)) ||
		!ds_ipc_message_try_parse_string_utf16_t (&buffer_cursor, &buffer_cursor_len, &instance->profiler_path))
		ep_raise_error ();

ep_on_exit:
	return (uint8_t *)instance;

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

bool
profiler_protocol_helper_startup_profiler (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	if (!stream)
		return false;

    bool result = false;
    DiagnosticsStartupProfilerCommandPayload *payload = NULL;

	if (!ds_server_is_paused_in_startup()) {
		ds_ipc_message_send_error (stream, DS_IPC_E_INVALIDARG);
		ep_raise_error ();
	}

	payload = (DiagnosticsStartupProfilerCommandPayload *)ds_ipc_message_try_parse_payload (message, startup_profiler_command_try_parse_payload);

	if (!payload) {
		ds_ipc_message_send_error (stream, DS_IPC_E_BAD_ENCODING);
		ep_raise_error ();
	}

	ds_ipc_result_t ipc_result;
	ipc_result = ds_rt_profiler_startup (payload);
	if (ipc_result != DS_IPC_S_OK) {
		ds_ipc_message_send_error (stream, ipc_result);
		ep_raise_error ();
	} else {
		ds_ipc_message_send_success (stream, ipc_result);
	}

	result = true;

ep_on_exit:
	ds_startup_profiler_command_payload_free (payload);
    ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ep_exit_error_handler ();
}

bool
ds_profiler_protocol_helper_handle_ipc_message (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	bool result = false;

	switch ((DiagnosticsProfilerCommandId)ds_ipc_header_get_commandid (ds_ipc_message_get_header_ref (message))) {
#ifdef FEATURE_PROFAPI_ATTACH_DETACH
	case DS_PROFILER_COMMANDID_ATTACH_PROFILER:
		result = profiler_protocol_helper_attach_profiler (message, stream);
		break;
#endif // FEATURE_PROFAPI_ATTACH_DETACH
	case DS_PROFILER_COMMANDID_STARTUP_PROFILER:
		result = profiler_protocol_helper_startup_profiler (message, stream);
		break;
 	default:
		result = profiler_protocol_helper_unknown_command (message, stream);
		break;
	}

	return result;
}
#else // PROFILING_SUPPORTED
bool
ds_profiler_protocol_helper_handle_ipc_message (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	DS_LOG_WARNING_0 ("Profiler support not enabled in this runtime");
	ds_ipc_message_send_error (stream, DS_IPC_E_NOTSUPPORTED);
    ds_ipc_stream_free (stream);

	return true;
}
#endif // PROFILING_SUPPORTED

#endif /* !defined(DS_INCLUDE_SOURCE_FILES) || defined(DS_FORCE_INCLUDE_SOURCE_FILES) */
#endif /* ENABLE_PERFTRACING */

#if !defined(ENABLE_PERFTRACING) || (defined(DS_INCLUDE_SOURCE_FILES) && !defined(DS_FORCE_INCLUDE_SOURCE_FILES))
extern const char quiet_linker_empty_file_warning_diagnostics_profiler_protocol;
const char quiet_linker_empty_file_warning_diagnostics_profiler_protocol = 0;
#endif
