/*
 * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>
#include <glib/gi18n-lib.h>

#include <gio/gio.h>

#include <gplugin.h>
#include <gplugin-native.h>

#include <girepository.h>

#include "gplugin-introspection.h"

/******************************************************************************
 * Helpers
 *****************************************************************************/
static char *
gplugin_introspection_get_ir_dump_directory(
	int *argc,
	char ***argv,
	GError **error)
{
	GOptionContext *ctx = NULL;
	char *irdump = NULL;
	GOptionEntry entries[] = {
		{
			.long_name = "introspect-dump",
			.short_name = 'i',
			.flags = G_OPTION_FLAG_NONE,
			.arg = G_OPTION_ARG_STRING,
			.arg_data = &irdump,
		},
		G_OPTION_ENTRY_NULL,
	};

	ctx = g_option_context_new("");
	g_option_context_set_summary(
		ctx,
		_("Loads plugins so they can be scanned by g-ir-scanner"));
	g_option_context_set_translation_domain(ctx, GETTEXT_PACKAGE);
	g_option_context_add_main_entries(ctx, entries, GETTEXT_PACKAGE);

	g_option_context_parse(ctx, argc, argv, error);
	g_clear_pointer(&ctx, g_option_context_free);

	return irdump;
}

static GPluginPlugin *
gplugin_introspection_query_and_load_plugin(
	GPluginLoader *loader,
	const char *filename,
	GError **error)
{
	GPluginPlugin *plugin = NULL;
	GError *local_error = NULL;
	gboolean loaded = FALSE;

	plugin = gplugin_loader_query_plugin(loader, filename, &local_error);
	if(local_error != NULL) {
		g_clear_object(&plugin);

		g_propagate_error(error, local_error);

		return NULL;
	}

	if(!GPLUGIN_IS_PLUGIN(plugin)) {
		g_set_error_literal(
			error,
			GPLUGIN_DOMAIN,
			0,
			"plugin unexpectedly is invalid");

		return NULL;
	}

	loaded = gplugin_loader_load_plugin(loader, plugin, &local_error);
	if(local_error != NULL) {
		g_clear_object(&plugin);

		g_propagate_error(error, local_error);

		return NULL;
	}

	if(!loaded) {
		g_set_error(
			error,
			GPLUGIN_DOMAIN,
			0,
			_("Failed to load plugin %s: unknown error"),
			filename);

		g_clear_object(&plugin);

		return NULL;
	}

	return plugin;
}

static void
gplugin_introspection_introspect_unload_plugins(
	GPluginLoader *loader,
	GListModel *plugins)
{
	for(guint i = 0; i < g_list_model_get_n_items(plugins); i++) {
		GPluginPlugin *plugin = NULL;

		plugin = g_list_model_get_item(plugins, i);

		gplugin_loader_unload_plugin(loader, plugin, TRUE, NULL);

		g_clear_object(&plugin);
	}
}

/******************************************************************************
 * Public API
 *****************************************************************************/
int
gplugin_introspection_introspect_plugin(
	int *argc,
	char ***argv,
	const char *filename)
{
	return gplugin_introspection_introspect_plugins(argc, argv, filename, NULL);
}

int
gplugin_introspection_introspect_plugins(int *argc, char ***argv, ...)
{
	GPluginLoader *loader = NULL;
	GError *error = NULL;
	GListStore *plugins = NULL;
	char *irdump = NULL;
	const char *filename = NULL;
	va_list args;

	irdump = gplugin_introspection_get_ir_dump_directory(argc, argv, &error);
	if(error != NULL) {
		g_print(
			_("Failed to parse command line arguments: %s\n"),
			error->message);
		g_free(irdump);

		return 1;
	}

	if(irdump == NULL || irdump[0] == '\0') {
		g_print(_("The introspect-dump option was not provided. This tool is"
				  "only meant to be called by g-ir-scanner directly."));

		return 1;
	}

	/* Create an instance of the native loader to load the plugins. */
	loader = g_object_new(GPLUGIN_TYPE_NATIVE_LOADER, NULL);

	/* Load our plugins. We store them in a GListStore so we can easily unload
	 * and unref them later. We can't use GPLUGIN_TYPE_PLUGIN as it's an
	 * interface, so just force it down to GObject to make it work.
	 */
	plugins = g_list_store_new(G_TYPE_OBJECT);

	va_start(args, argv);
	while((filename = va_arg(args, const char *)) != NULL) {
		GPluginPlugin *plugin = NULL;

		plugin = gplugin_introspection_query_and_load_plugin(
			loader,
			filename,
			&error);
		if(error != NULL) {
			g_print(
				_("Failed to query or load plugin %s: %s\n"),
				filename,
				error->message);

			gplugin_introspection_introspect_unload_plugins(
				loader,
				G_LIST_MODEL(plugins));
			g_clear_object(&plugins);

			g_free(irdump);
			g_clear_object(&plugin);
			g_clear_object(&loader);

			return 1;
		}

		g_list_store_append(plugins, plugin);

		/* Remove our reference to the plugin as we don't need it any more. */
		g_clear_object(&plugin);
	}
	va_end(args);

	/* Now that all of the plugins have been loaded, run the scanner. */
	if(!g_irepository_dump(irdump, &error)) {
		g_print("g_irepository_dump() failed: %s\n", error->message);

		g_free(irdump);
		gplugin_introspection_introspect_unload_plugins(
			loader,
			G_LIST_MODEL(plugins));
		g_clear_object(&plugins);

		g_clear_object(&loader);

		return 1;
	}

	gplugin_introspection_introspect_unload_plugins(
		loader,
		G_LIST_MODEL(plugins));
	g_clear_object(&plugins);
	g_clear_object(&loader);
	g_free(irdump);

	return 0;
}
