#include "util.js.h"

#include "bip39.h"
#include "log.h"
#include "mem.h"
#include "task.h"
#include "trace.h"

#include "backtrace.h"
#include "picohttpparser.h"
#include "sodium/utils.h"
#include "uv.h"

#include <string.h>

#if defined(__ANDROID__)
#include <unwind.h>
#elif !defined(_WIN32) && !defined(__HAIKU__)
#include <execinfo.h>
#endif

#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif

#if defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IPHONE)
#define TF_IS_MOBILE 1
#else
#define TF_IS_MOBILE 0
#endif

static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	size_t length = 0;
	const char* value = JS_ToCStringLen(context, &length, argv[0]);
	JSValue typed_array = tf_util_new_uint8_array(context, (const uint8_t*)value, length);
	JS_FreeCString(context, value);
	return typed_array;
}

static JSValue _util_utf8_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	JSValue result = JS_NULL;
	size_t length;
	if (JS_IsString(argv[0]))
	{
		result = JS_DupValue(context, argv[0]);
	}
	else
	{
		uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
		if (array)
		{
			result = JS_NewStringLen(context, (const char*)array, length);
		}
		else
		{
			size_t offset = 0;
			size_t element_size = 0;
			JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
			size_t size = 0;
			if (!JS_IsException(buffer))
			{
				array = tf_util_try_get_array_buffer(context, &size, buffer);
				if (array)
				{
					result = JS_NewStringLen(context, (const char*)array, size);
				}
			}
			JS_FreeValue(context, buffer);
		}
	}
	return result;
}

JSValue tf_util_utf8_decode(JSContext* context, JSValue value)
{
	return _util_utf8_decode(context, JS_NULL, 1, &value);
}

static JSValue _util_base64_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	JSValue result = JS_UNDEFINED;
	JSValue buffer = JS_UNDEFINED;
	size_t length = 0;
	uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
	if (!array)
	{
		size_t offset;
		size_t element_size;
		buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
		if (!JS_IsException(buffer))
		{
			array = tf_util_try_get_array_buffer(context, &length, buffer);
		}
	}

	if (array)
	{
		char* encoded = tf_malloc(length * 4);
		int r = tf_base64_encode(array, length, encoded, length * 4);
		if (r >= 0)
		{
			result = JS_NewStringLen(context, encoded, r);
		}
		tf_free(encoded);
	}
	else
	{
		const char* value = JS_ToCStringLen(context, &length, argv[0]);
		char* encoded = tf_malloc(length * 4);
		int r = tf_base64_encode((const uint8_t*)value, length, encoded, length * 4);
		if (r >= 0)
		{
			result = JS_NewStringLen(context, encoded, r);
		}
		tf_free(encoded);
		JS_FreeCString(context, value);
	}
	JS_FreeValue(context, buffer);
	return result;
}

static JSValue _util_base64_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	JSValue result = JS_UNDEFINED;
	size_t length = 0;
	const char* value = JS_ToCStringLen(context, &length, argv[0]);
	uint8_t* decoded = tf_malloc(length);

	int r = tf_base64_decode(value, length, decoded, length);
	if (r >= 0)
	{
		result = tf_util_new_uint8_array(context, decoded, r);
	}

	tf_free(decoded);
	JS_FreeCString(context, value);
	return result;
}

static JSValue _util_bip39_words(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	JSValue result = JS_UNDEFINED;
	JSValue buffer = JS_UNDEFINED;
	size_t length;
	uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
	if (!array)
	{
		size_t offset;
		size_t element_size;
		buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
		if (!JS_IsException(buffer))
		{
			array = tf_util_try_get_array_buffer(context, &length, buffer);
		}
	}

	char words[2048] = "";
	if (array && tf_bip39_bytes_to_words(array, length, words, sizeof(words)))
	{
		result = JS_NewString(context, words);
	}
	JS_FreeValue(context, buffer);
	return result;
}

static JSValue _util_bip39_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	const char* words = JS_ToCString(context, argv[0]);
	uint8_t bytes[32] = { 0 };
	bool success = tf_bip39_words_to_bytes(words, bytes, sizeof(bytes));
	JS_FreeCString(context, words);
	if (success)
	{
		return tf_util_new_uint8_array(context, bytes, sizeof(bytes));
	}
	return JS_UNDEFINED;
}

uint8_t* tf_util_try_get_array_buffer(JSContext* context, size_t* psize, JSValueConst obj)
{
	uint8_t* result = JS_GetArrayBuffer(context, psize, obj);
	JS_FreeValue(context, JS_GetException(context));
	return result;
}

JSValue tf_util_try_get_typed_array_buffer(JSContext* context, JSValueConst obj, size_t* pbyte_offset, size_t* pbyte_length, size_t* pbytes_per_element)
{
	JSValue result = JS_GetTypedArrayBuffer(context, obj, pbyte_offset, pbyte_length, pbytes_per_element);
	JS_FreeValue(context, JS_GetException(context));
	return result;
}

static JSValue _util_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	tf_task_t* task = JS_GetContextOpaque(context);
	if (task)
	{
		tf_printf("Task[%p:%s]>", task, tf_task_get_name(task));
		tf_task_print(task, argc, argv);
	}
	for (int i = 0; i < argc; ++i)
	{
		if (JS_IsNull(argv[i]))
		{
			tf_printf(" null");
		}
		else
		{
			const char* value = JS_ToCString(context, argv[i]);
			tf_printf(" %s", value);
			JS_FreeCString(context, value);
		}
	}
	tf_printf("\n");
	return JS_NULL;
}

bool tf_util_report_error(JSContext* context, JSValue value)
{
	bool is_error = false;
	tf_task_t* task = tf_task_get(context);
	if (JS_IsError(context, value))
	{
		const char* string = JS_ToCString(context, value);
		tf_printf("ERROR: %s\n", string);
		JS_FreeCString(context, string);

		JSValue stack = JS_GetPropertyStr(context, value, "stack");
		if (!JS_IsUndefined(stack))
		{
			const char* stack_str = JS_ToCString(context, stack);
			tf_printf("%s\n", stack_str);
			JS_FreeCString(context, stack_str);
		}
		JS_FreeValue(context, stack);

		tf_task_send_error_to_parent(task, value);
		is_error = true;
	}
	else if (JS_IsException(value))
	{
		if (!tf_task_send_error_to_parent(task, value))
		{
			JSValue exception = JS_GetException(context);
			tf_util_report_error(context, exception);
			JS_FreeValue(context, exception);
		}
		is_error = true;
	}
	tf_task_check_jobs(task);
	return is_error;
}

static JSValue _util_parseHttpResponse(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	JSValue result = JS_UNDEFINED;
	int status = 0;
	int minor_version = 0;
	const char* message = NULL;
	size_t message_length = 0;
	struct phr_header headers[100];
	size_t header_count = sizeof(headers) / sizeof(*headers);
	int previous_length = 0;
	JS_ToInt32(context, &previous_length, argv[1]);

	JSValue buffer = JS_UNDEFINED;
	size_t length;
	uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
	if (!array)
	{
		size_t offset;
		size_t element_size;
		buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
		if (!JS_IsException(buffer))
		{
			array = tf_util_try_get_array_buffer(context, &length, buffer);
		}
	}

	if (array)
	{
		int parse_result = phr_parse_response((const char*)array, length, &minor_version, &status, &message, &message_length, headers, &header_count, previous_length);
		if (parse_result > 0)
		{
			result = JS_NewObject(context);
			JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result));
			JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version));
			JS_SetPropertyStr(context, result, "status", JS_NewInt32(context, status));
			JS_SetPropertyStr(context, result, "message", JS_NewStringLen(context, message, message_length));
			JSValue header_object = JS_NewObject(context);
			for (int i = 0; i < (int)header_count; i++)
			{
				char name[256];
				snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name);
				JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len));
			}
			JS_SetPropertyStr(context, result, "headers", header_object);
		}
		else
		{
			result = JS_NewInt32(context, parse_result);
		}
	}
	else
	{
		result = JS_ThrowTypeError(context, "Could not convert argument to array.");
	}

	JS_FreeValue(context, buffer);

	return result;
}

static const char* k_kind_name[] = {
	[k_kind_bool] = "bool",
	[k_kind_int] = "int",
	[k_kind_string] = "string",
};

typedef struct _setting_value_t
{
	tf_setting_kind_t kind;
	union
	{
		bool bool_value;
		int int_value;
		const char* string_value;
	};
} setting_value_t;

typedef struct _setting_t
{
	const char* name;
	const char* type;
	const char* description;
	setting_value_t default_value;
} setting_t;

static const setting_t k_settings[] = {
	{ .name = "code_of_conduct", .type = "textarea", .description = "Code of conduct presented at sign-in.", .default_value = { .kind = k_kind_string, .string_value = NULL } },
	{ .name = "ssb_port",
		.type = "integer",
		.description = "Port on which to listen for SSB secure handshake connections.",
		.default_value = { .kind = k_kind_int, .int_value = 8008 } },
	{ .name = "http_local_only",
		.type = "boolean",
		.description = "Whether to bind http(s) to the loopback address.  Otherwise any.",
		.default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE ? true : false } },
	{ .name = "http_port", .type = "integer", .description = "Port on which to listen for HTTP connections.", .default_value = { .kind = k_kind_int, .int_value = 12345 } },
	{ .name = "https_port", .type = "integer", .description = "Port on which to listen for secure HTTP connections.", .default_value = { .kind = k_kind_int, .int_value = 0 } },
	{ .name = "out_http_port_file", .type = "hidden", .description = "File to which to write bound HTTP port.", .default_value = { .kind = k_kind_string, .string_value = NULL } },
	{ .name = "blob_fetch_age_seconds",
		.type = "integer",
		.description = "Only blobs mentioned more recently than this age will be automatically fetched.",
		.default_value = { .kind = k_kind_int, .int_value = TF_IS_MOBILE ? (int)(0.5f * 365 * 24 * 60 * 60) : -1 } },
	{ .name = "blob_expire_age_seconds",
		.type = "integer",
		.description = "Blobs older than this will be automatically deleted.",
		.default_value = { .kind = k_kind_int, .int_value = TF_IS_MOBILE ? (int)(1.0f * 365 * 24 * 60 * 60) : -1 } },
	{ .name = "fetch_hosts",
		.type = "string",
		.description = "Comma-separated list of host names to which HTTP fetch requests are allowed.  None if empty.",
		.default_value = { .kind = k_kind_string, .string_value = NULL } },
	{ .name = "http_redirect",
		.type = "string",
		.description = "If connecting by HTTP and HTTPS is configured, Location header prefix (ie, \"http://example.com\")",
		.default_value = { .kind = k_kind_string, .string_value = NULL } },
	{ .name = "index", .type = "string", .description = "Default path.", .default_value = { .kind = k_kind_string, .string_value = "/~core/intro/" } },
	{ .name = "index_map",
		.type = "textarea",
		.description = "Mappings from hostname to redirect path, one per line, as in: \"www.tildefriends.net=/~core/index/\"",
		.default_value = { .kind = k_kind_string, .string_value = NULL } },
	{ .name = "peer_exchange",
		.type = "boolean",
		.description = "Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.",
		.default_value = { .kind = k_kind_bool, .bool_value = false } },
	{ .name = "replicator", .type = "boolean", .description = "Enable message and blob replication.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
	{ .name = "room", .type = "boolean", .description = "Enable peers to tunnel through this instance as a room.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
	{ .name = "room_name", .type = "string", .description = "Name of the room.", .default_value = { .kind = k_kind_string, .string_value = "tilde friends tunnel" } },
	{ .name = "seeds_host",
		.type = "string",
		.description = "Hostname for seed connections.",
		.default_value = { .kind = k_kind_string, .string_value = "seeds.tildefriends.net" } },
	{ .name = "account_registration", .type = "boolean", .description = "Allow registration of new accounts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
	{ .name = "replication_hops",
		.type = "integer",
		.description = "Number of hops to replicate (1 = direct follows, 2 = follows of follows, etc.).",
		.default_value = { .kind = k_kind_int, .int_value = 2 } },
	{ .name = "delete_stale_feeds",
		.type = "boolean",
		.description = "Periodically delete feeds that aren't visible from local accounts or related follows.",
		.default_value = { .kind = k_kind_bool, .bool_value = false } },
	{ .name = "talk_to_strangers",
		.type = "boolean",
		.description = "Whether connections are accepted from accounts that aren't in the replication range or otherwise already known.",
		.default_value = { .kind = k_kind_bool, .bool_value = true } },
	{ .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } },
	{ .name = "broadcast", .type = "boolean", .description = "Send network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
	{ .name = "discovery", .type = "boolean", .description = "Receive network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
	{ .name = "stay_connected",
		.type = "boolean",
		.description = "Whether to attempt to keep several peer connections open.",
		.default_value = { .kind = k_kind_bool, .bool_value = false } },
};

static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind)
{
	for (int i = 0; i < tf_countof(k_settings); i++)
	{
		if (strcmp(k_settings[i].name, name) == 0 && (kind == k_kind_unknown || k_settings[i].default_value.kind == kind))
		{
			return &k_settings[i];
		}
	}
	if (kind != k_kind_unknown)
	{
		tf_printf("Did not find global setting of type %s: %s.\n", k_kind_name[kind], name);
	}
	return NULL;
}

tf_setting_kind_t tf_util_get_global_setting_kind(const char* name)
{
	const setting_t* setting = _util_get_setting(name, k_kind_unknown);
	return setting ? setting->default_value.kind : k_kind_unknown;
}

bool tf_util_get_default_global_setting_bool(const char* name)
{
	const setting_t* setting = _util_get_setting(name, k_kind_bool);
	return setting ? setting->default_value.bool_value : false;
}

int tf_util_get_default_global_setting_int(const char* name)
{
	const setting_t* setting = _util_get_setting(name, k_kind_int);
	return setting ? setting->default_value.int_value : 0;
}

const char* tf_util_get_default_global_setting_string(const char* name)
{
	const setting_t* setting = _util_get_setting(name, k_kind_string);
	return setting && setting->default_value.string_value ? setting->default_value.string_value : "";
}

static JSValue _util_defaultGlobalSettings(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
	JSValue settings = JS_NewObject(context);
	for (int i = 0; i < tf_countof(k_settings); i++)
	{
		JSValue entry = JS_NewObject(context);
		JS_SetPropertyStr(context, entry, "type", JS_NewString(context, k_settings[i].type));
		JS_SetPropertyStr(context, entry, "description", JS_NewString(context, k_settings[i].description));
		switch (k_settings[i].default_value.kind)
		{
		case k_kind_bool:
			JS_SetPropertyStr(context, entry, "default_value", JS_NewBool(context, k_settings[i].default_value.bool_value));
			break;
		case k_kind_int:
			JS_SetPropertyStr(context, entry, "default_value", JS_NewInt32(context, k_settings[i].default_value.int_value));
			break;
		case k_kind_string:
			JS_SetPropertyStr(
				context, entry, "default_value", k_settings[i].default_value.string_value ? JS_NewString(context, k_settings[i].default_value.string_value) : JS_UNDEFINED);
			break;
		case k_kind_unknown:
			break;
		}
		JS_SetPropertyStr(context, settings, k_settings[i].name, entry);
	}
	return settings;
}

void tf_util_document_settings(const char* line_prefix)
{
	char buffer[32];
	for (int i = 0; i < tf_countof(k_settings); i++)
	{
		const char* default_value = NULL;
		const char* quote = "";
		switch (k_settings[i].default_value.kind)
		{
		case k_kind_bool:
			default_value = k_settings[i].default_value.bool_value ? "true" : "false";
			break;
		case k_kind_string:
			quote = "\"";
			default_value = k_settings[i].default_value.string_value ? k_settings[i].default_value.string_value : "";
			break;
		case k_kind_int:
			snprintf(buffer, sizeof(buffer), "%d", k_settings[i].default_value.int_value);
			default_value = buffer;
			break;
		case k_kind_unknown:
			break;
		}
		tf_printf("%s%s (default: %s%s%s): %s\n", line_prefix, k_settings[i].name, quote, default_value, quote, k_settings[i].description);
	}
}

JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size)
{
	JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size);
	JSValue args[] = {
		array_buffer,
		JS_NewInt64(context, 0),
		JS_NewInt64(context, size),
	};
	JSValue result = JS_NewTypedArray(context, tf_countof(args), args, JS_TYPED_ARRAY_UINT8C);
	JS_FreeValue(context, array_buffer);
	return result;
}

void tf_util_register(JSContext* context)
{
	JSValue global = JS_GetGlobalObject(context);
	JS_SetPropertyStr(context, global, "utf8Decode", JS_NewCFunction(context, _util_utf8_decode, "utf8Decode", 1));
	JS_SetPropertyStr(context, global, "utf8Encode", JS_NewCFunction(context, _util_utf8_encode, "utf8Encode", 1));
	JS_SetPropertyStr(context, global, "base64Decode", JS_NewCFunction(context, _util_base64_decode, "base64Decode", 1));
	JS_SetPropertyStr(context, global, "base64Encode", JS_NewCFunction(context, _util_base64_encode, "base64Encode", 1));
	JS_SetPropertyStr(context, global, "bip39Words", JS_NewCFunction(context, _util_bip39_words, "bip39Words", 1));
	JS_SetPropertyStr(context, global, "bip39Bytes", JS_NewCFunction(context, _util_bip39_bytes, "bip39Bytes", 1));
	JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1));
	JS_SetPropertyStr(context, global, "parseHttpResponse", JS_NewCFunction(context, _util_parseHttpResponse, "parseHttpResponse", 2));
	JS_SetPropertyStr(context, global, "defaultGlobalSettings", JS_NewCFunction(context, _util_defaultGlobalSettings, "defaultGlobalSettings", 2));
	JS_FreeValue(context, global);
}

int tf_util_get_length(JSContext* context, JSValue value)
{
	if (JS_IsUndefined(value))
	{
		return 0;
	}

	JSValue length = JS_GetPropertyStr(context, value, "length");
	int result = 0;
	JS_ToInt32(context, &result, length);
	JS_FreeValue(context, length);
	return result;
}

int tf_util_insert_index(const void* key, const void* base, size_t count, size_t size, int (*compare)(const void*, const void*))
{
	int lower = 0;
	int upper = count;
	while (lower < upper && lower < (int)count)
	{
		int guess = (lower + upper) / 2;
		int result = compare(key, ((char*)base) + size * guess);
		if (result < 0)
		{
			upper = guess;
		}
		else if (result > 0)
		{
			lower = guess + 1;
		}
		else
		{
			return guess;
		}
	};
	return lower;
}

size_t tf_base64_encode(const uint8_t* source, size_t source_length, char* out, size_t out_length)
{
	sodium_bin2base64(out, out_length, source, source_length, sodium_base64_VARIANT_ORIGINAL);
	return sodium_base64_ENCODED_LEN(source_length, sodium_base64_VARIANT_ORIGINAL) - 1;
}

size_t tf_base64_decode(const char* source, size_t source_length, uint8_t* out, size_t out_length)
{
	size_t actual_length = 0;
	return sodium_base642bin(out, out_length, source, source_length, NULL, &actual_length, NULL, sodium_base64_VARIANT_ORIGINAL) == 0 ? actual_length : 0;
}

static int _tf_util_backtrace_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function)
{
	char** stack = data;
	char line[256];
	int length = snprintf(line, sizeof(line), "%p %s:%d %s\n", (void*)pc, filename, line_number, function);
	int current = *stack ? strlen(*stack) : 0;
	*stack = tf_resize_vec(*stack, current + length + 1);
	memcpy(*stack + current, line, length + 1);
	return 0;
}

static void _tf_util_backtrace_error(void* data, const char* message, int error)
{
	char** stack = data;
	if (message)
	{
		int length = strlen(message);
		int current = *stack ? strlen(*stack) : 0;
		*stack = tf_resize_vec(*stack, current + length + 1);
		memcpy(*stack + current, message, length + 1);
	}
}

const char* tf_util_backtrace_to_string(void* const* buffer, int count)
{
	extern struct backtrace_state* g_backtrace_state;
	char* string = NULL;
	for (int i = 0; i < count; i++)
	{
		backtrace_pcinfo(g_backtrace_state, (uintptr_t)buffer[i], _tf_util_backtrace_callback, _tf_util_backtrace_error, &string);
	}
	return string;
}

static int _tf_util_backtrace_single_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function)
{
	char** stack = data;
	char line[256];
	int length = (int)tf_string_set(line, sizeof(line), function);
	int current = *stack ? strlen(*stack) : 0;
	*stack = tf_resize_vec(*stack, current + length + 1);
	memcpy(*stack + current, line, length + 1);
	return 0;
}

const char* tf_util_function_to_string(void* function)
{
	extern struct backtrace_state* g_backtrace_state;
	char* string = NULL;
	backtrace_pcinfo(g_backtrace_state, (uintptr_t)function, _tf_util_backtrace_single_callback, _tf_util_backtrace_error, &string);
	return string;
}

const char* tf_util_backtrace_string()
{
	void* buffer[32];
	int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer));
	return tf_util_backtrace_to_string(buffer, count);
}

void tf_util_print_backtrace()
{
	const char* bt = tf_util_backtrace_string();
	if (bt)
	{
		tf_printf("%s\n", bt);
	}
	tf_free((void*)bt);
}

#if defined(__ANDROID__)
typedef struct _android_backtrace_t
{
	void** current;
	void** end;
} android_backtrace_t;

static _Unwind_Reason_Code _android_unwind_callback(struct _Unwind_Context* context, void* arg)
{
	android_backtrace_t* state = arg;
	uintptr_t pc = _Unwind_GetIP(context);
	if (pc)
	{
		if (state->current == state->end)
		{
			return _URC_END_OF_STACK;
		}
		else
		{
			*state->current++ = (void*)pc;
		}
	}
	return _URC_NO_REASON;
}
#endif

int tf_util_backtrace(void** buffer, int count)
{
#ifdef _WIN32
	return CaptureStackBackTrace(0, count, buffer, NULL);
#elif defined(__ANDROID__)
	android_backtrace_t state = { .current = buffer, .end = buffer + count };
	_Unwind_Backtrace(_android_unwind_callback, &state);
	return state.current - buffer;
#elif defined(__HAIKU__)
	return 0;
#else
	return backtrace(buffer, count);
#endif
}

bool tf_util_is_mobile()
{
	return TF_IS_MOBILE != 0;
}

size_t tf_string_set(char* buffer, size_t size, const char* string)
{
	size_t length = string ? strlen(string) : 0;
	length = tf_min(length, size - 1);
	if (size)
	{
		if (length)
		{
			memcpy(buffer, string, length);
		}
		buffer[length] = 0;
	}
	return length;
}
