/*
 * Copyright 2022 Richard Hughes <richard@hughsie.com>
 * Copyright 2022 Google LLC
 * Written by Simon Glass <sjg@chromium.org>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include "fu-vbe-plugin.h"
#include "fu-vbe-simple-device.h"

struct _FuVbePlugin {
	FuPlugin parent_instance;
};

G_DEFINE_TYPE(FuVbePlugin, fu_vbe_plugin, FU_TYPE_PLUGIN)

static gboolean
fu_vbe_plugin_coldplug_img(FuPlugin *plugin,
			   FuFdtImage *fdt_root,
			   FuFdtImage *fdt_node,
			   GError **error)
{
	GType device_gtype = G_TYPE_INVALID;
	g_autofree gchar *compatible = NULL;
	g_auto(GStrv) split = NULL;
	g_autoptr(FuDevice) dev = NULL;

	/* we expect 'fwupd,vbe-<driver>' */
	if (!fu_fdt_image_get_attr_str(fdt_node,
				       FU_FIT_FIRMWARE_ATTR_COMPATIBLE,
				       &compatible,
				       error)) {
		g_prefix_error_literal(error, "missing update mechanism: ");
		return FALSE;
	}
	split = g_strsplit(compatible, ",", 2);
	if (g_strv_length(split) != 2) {
		g_set_error(error,
			    FWUPD_ERROR,
			    FWUPD_ERROR_INVALID_FILE,
			    "update mechanism is invalid: %s",
			    compatible);
		return FALSE;
	}
	if (g_strcmp0(split[0], "fwupd") != 0) {
		g_set_error(error,
			    FWUPD_ERROR,
			    FWUPD_ERROR_INVALID_FILE,
			    "update mechanism should have manufacturer of fwupd: %s",
			    split[0]);
		return FALSE;
	}

	/* skip past 'vbe-' */
	if (!g_str_has_prefix(split[1], "vbe-")) {
		g_set_error(error,
			    FWUPD_ERROR,
			    FWUPD_ERROR_INVALID_FILE,
			    "update mechanism is missing vbe prefix: %s",
			    split[1]);
		return FALSE;
	}
	if (g_strcmp0(split[1], "vbe-simple") == 0) {
		device_gtype = FU_TYPE_VBE_SIMPLE_DEVICE;
	} else {
		g_set_error(error,
			    FWUPD_ERROR,
			    FWUPD_ERROR_INVALID_FILE,
			    "no driver for VBE method '%s'",
			    split[1]);
		return FALSE;
	}

	/* success */
	dev = g_object_new(device_gtype,
			   "context",
			   fu_plugin_get_context(plugin),
			   "fdt-root",
			   fdt_root,
			   "fdt-node",
			   fdt_node,
			   NULL);
	fu_plugin_add_device(plugin, dev);
	return TRUE;
}

static gboolean
fu_vbe_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error)
{
	g_autoptr(FuFirmware) fdt = NULL;
	g_autoptr(FuFdtImage) fdt_root = NULL;
	g_autoptr(GPtrArray) fdt_imgs = NULL;

	/* get compatible from root node */
	fdt = fu_context_get_fdt(fu_plugin_get_context(plugin), error);
	if (fdt == NULL)
		return FALSE;
	fdt_root = fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(fdt), "/chosen/fwupd", error);
	if (fdt_root == NULL)
		return FALSE;
	fdt_imgs = fu_firmware_get_images(FU_FIRMWARE(fdt_root));
	for (guint i = 0; i < fdt_imgs->len; i++) {
		FuFdtImage *fdt_node = g_ptr_array_index(fdt_imgs, i);
		g_autoptr(GError) error_local = NULL;
		if (!fu_vbe_plugin_coldplug_img(plugin, fdt_root, fdt_node, &error_local)) {
			g_warning("%s", error_local->message);
			continue;
		}
	}

	/* nothing found? */
	if (fu_plugin_get_devices(plugin)->len == 0) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_NOT_SUPPORTED,
				    "no valid VBE update mechanism found");
		return FALSE;
	}

	/* success */
	return TRUE;
}

static void
fu_vbe_plugin_init(FuVbePlugin *self)
{
}

static void
fu_vbe_plugin_constructed(GObject *obj)
{
	FuPlugin *plugin = FU_PLUGIN(obj);
	fu_plugin_add_device_gtype(plugin, FU_TYPE_VBE_SIMPLE_DEVICE); /* coverage */

	/* chain up to parent */
	G_OBJECT_CLASS(fu_vbe_plugin_parent_class)->constructed(obj);
}

static void
fu_vbe_plugin_class_init(FuVbePluginClass *klass)
{
	FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass);
	plugin_class->constructed = fu_vbe_plugin_constructed;
	plugin_class->coldplug = fu_vbe_plugin_coldplug;
}
