#include <jansson.h>

#include <obs-module.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <util/windows/window-helpers.h>

#include "compat-helpers.h"
#include "compat-format-ver.h"

enum match_flags {
	MATCH_EXE = 1 << 0,
	MATCH_TITLE = 1 << 1,
	MATCH_CLASS = 1 << 2,
};

static inline const char *get_string_val(json_t *entry, const char *key)
{
	json_t *str_val = json_object_get(entry, key);
	if (!str_val || !json_is_string(str_val))
		return NULL;

	return json_string_value(str_val);
}

static inline int get_int_val(json_t *entry, const char *key)
{
	json_t *integer_val = json_object_get(entry, key);
	if (!integer_val || !json_is_integer(integer_val))
		return 0;

	return (int)json_integer_value(integer_val);
}

static inline bool get_bool_val(json_t *entry, const char *key)
{
	json_t *bool_val = json_object_get(entry, key);
	if (!bool_val || !json_is_boolean(bool_val))
		return false;

	return json_is_true(bool_val);
}

static json_t *open_json_file(const char *file)
{
	char *file_data = os_quick_read_utf8_file(file);
	json_error_t error;
	json_t *root;
	json_t *list;
	int format_ver;

	if (!file_data)
		return NULL;

	root = json_loads(file_data, JSON_REJECT_DUPLICATES, &error);
	bfree(file_data);

	if (!root) {
		blog(LOG_WARNING,
		     "compat-helpers.c: [open_json_file] "
		     "Error reading JSON file (%d): %s",
		     error.line, error.text);
		return NULL;
	}

	format_ver = get_int_val(root, "format_version");

	if (format_ver != COMPAT_FORMAT_VERSION) {
		blog(LOG_DEBUG,
		     "compat-helpers.c: [open_json_file] "
		     "Wrong format version (%d), expected %d",
		     format_ver, COMPAT_FORMAT_VERSION);
		json_decref(root);
		return NULL;
	}

	list = json_object_get(root, "entries");
	if (list)
		json_incref(list);
	json_decref(root);

	if (!list) {
		blog(LOG_WARNING, "compat-helpers.c: [open_json_file] "
				  "No compatibility list");
		return NULL;
	}

	return list;
}

static json_t *open_compat_file(void)
{
	char *file;
	json_t *root = NULL;

	file = obs_module_config_path("compatibility.json");
	if (file) {
		root = open_json_file(file);
		bfree(file);
	}

	if (!root) {
		file = obs_module_file("compatibility.json");
		if (file) {
			root = open_json_file(file);
			bfree(file);
		}
	}

	return root;
}

static json_t *compat_entries;
struct compat_result *check_compatibility(const char *win_title,
					  const char *win_class,
					  const char *exe,
					  enum source_type type)
{
	if (!compat_entries) {
		json_t *root = open_compat_file();
		if (!root)
			return NULL;

		compat_entries = root;
	}

	struct dstr message;
	struct compat_result *res = NULL;
	json_t *entry;
	size_t index;

	json_array_foreach (compat_entries, index, entry) {
		if (type == GAME_CAPTURE &&
		    !get_bool_val(entry, "game_capture"))
			continue;
		if (type == WINDOW_CAPTURE_WGC &&
		    !get_bool_val(entry, "window_capture_wgc"))
			continue;
		if (type == WINDOW_CAPTURE_BITBLT &&
		    !get_bool_val(entry, "window_capture"))
			continue;

		int match_flags = get_int_val(entry, "match_flags");
		const char *j_exe = get_string_val(entry, "executable");
		const char *j_title = get_string_val(entry, "window_title");
		const char *j_class = get_string_val(entry, "window_class");

		if ((match_flags & MATCH_CLASS) &&
		    (!win_class || strcmp(win_class, j_class) != 0))
			continue;
		if ((match_flags & MATCH_EXE) &&
		    (!exe || astrcmpi(exe, j_exe) != 0))
			continue;
		/* Title supports partial matches as some games append additional
		 * information after the title, e.g., "Minecraft 1.18". */
		if ((match_flags & MATCH_TITLE) &&
		    (!win_title ||
		     astrcmpi_n(win_title, j_title, strlen(j_title)) != 0))
			continue;

		/* Attempt to translate and compile message */
		const char *key = get_string_val(entry, "translation_key");
		const char *msg = get_string_val(entry, "message");
		obs_module_get_string(key, &msg);

		dstr_init_copy(&message, msg);

		const char *name = get_string_val(entry, "name");
		/* Replace placeholders in generic messages */
		if (name && dstr_find(&message, "%") != NULL) {
			dstr_replace(&message, "%name%", name);
		}

		const char *url = get_string_val(entry, "url");
		/* Append clickable URL in Qt rich text */
		if (url && strncmp(url, "https://", 8) == 0) {
			dstr_catf(&message, "<br>\n<a href=\"%s\">%s</a>", url,
				  url + 8);
		}

		res = bzalloc(sizeof(struct compat_result));
		res->severity = get_int_val(entry, "severity");
		res->message = message.array;

		break;
	}

	return res;
}

void compat_result_free(struct compat_result *res)
{
	bfree(res->message);
	bfree(res);
}

void compat_json_free()
{
	json_decref(compat_entries);
}
