/*
 * Copyright {{Year}} {{Author}} <{{Email}}>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include "fu-{{vendor_dash_example}}-common.h"
#include "fu-{{vendor_dash_example}}-device.h"
#include "fu-{{vendor_dash_example}}-firmware.h"
#include "fu-{{vendor_dash_example}}-struct.h"

/* this can be set using Flags=example in the quirk file  */
#define FU_{{VENDOR_EXAMPLE}}_DEVICE_FLAG_EXAMPLE "example"

struct _Fu{{VendorExample}}Device {
	Fu{{Parent}}Device parent_instance;
	guint16 start_addr;
};

G_DEFINE_TYPE(Fu{{VendorExample}}Device, fu_{{vendor_example}}_device, FU_TYPE_{{PARENT}}_DEVICE)

static void
fu_{{vendor_example}}_device_to_string(FuDevice *device, guint idt, GString *str)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);
	fwupd_codec_string_append_hex(str, idt, "StartAddr", self->start_addr);
}

/* TODO: this is only required if the device instance state is required elsewhere */
guint16
fu_{{vendor_example}}_device_get_start_addr(Fu{{VendorExample}}Device *self)
{
	g_return_val_if_fail(FU_IS_{{VENDOR_EXAMPLE}}_DEVICE(self), G_MAXUINT16);
	return self->start_addr;
}

static gboolean
fu_{{vendor_example}}_device_detach(FuDevice *device, FuProgress *progress, GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);

	/* sanity check */
	if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
		g_debug("already in bootloader mode, skipping");
		return TRUE;
	}

	/* TODO: switch the device into bootloader mode */
	g_assert(self != NULL);

	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	return TRUE;
}

static gboolean
fu_{{vendor_example}}_device_attach(FuDevice *device, FuProgress *progress, GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);

	/* sanity check */
	if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
		g_debug("already in runtime mode, skipping");
		return TRUE;
	}

	/* TODO: switch the device into runtime mode */
	g_assert(self != NULL);

	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	return TRUE;
}

static gboolean
fu_{{vendor_example}}_device_reload(FuDevice *device, GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);
	/* TODO: reprobe the hardware, or delete this vfunc to use ->setup() as a fallback */
	g_assert(self != NULL);
	return TRUE;
}

static gboolean
fu_{{vendor_example}}_device_probe(FuDevice *device, GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);

	/* Fu{{Parent}}Device->probe */
	if (!FU_DEVICE_CLASS(fu_{{vendor_example}}_device_parent_class)->probe(device, error))
		return FALSE;

	/* TODO: probe the device for properties available before it is opened */
	if (fu_device_has_private_flag(device, FU_{{VENDOR_EXAMPLE}}_DEVICE_FLAG_EXAMPLE))
		self->start_addr = 0x100;

{%- if Parent == 'Udev' -%}
	/* get from sysfs */
	return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error);
{%- else %}
	/* success */
	return TRUE;
{%- endif %}
}

static gboolean
fu_{{vendor_example}}_device_setup(FuDevice *device, GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);

	/* {{Parent}}Device->setup */
	if (!FU_DEVICE_CLASS(fu_{{vendor_example}}_device_parent_class)->setup(device, error))
		return FALSE;

	/* TODO: get the version and other properties from the hardware while open */
	g_assert(self != NULL);
	fu_device_set_version(device, "1.2.3");

	/* success */
	return TRUE;
}

static gboolean
fu_{{vendor_example}}_device_prepare(FuDevice *device,
				 FuProgress *progress,
				 FwupdInstallFlags flags,
				 GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);
	/* TODO: anything the device has to do before the update starts */
	g_assert(self != NULL);
	return TRUE;
}

static gboolean
fu_{{vendor_example}}_device_cleanup(FuDevice *device,
				 FuProgress *progress,
				 FwupdInstallFlags flags,
				 GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);
	/* TODO: anything the device has to do when the update has completed */
	g_assert(self != NULL);
	return TRUE;
}

static FuFirmware *
fu_{{vendor_example}}_device_prepare_firmware(FuDevice *device,
					  GInputStream *stream,
					  FuProgress *progress,
					  FwupdInstallFlags flags,
					  GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);
	g_autoptr(FuFirmware) firmware = fu_{{vendor_example}}_firmware_new();

	/* TODO: you do not need to use this vfunc if not checking attributes */
	if (self->start_addr !=
	    fu_{{vendor_example}}_firmware_get_start_addr(FU_{{VENDOR_EXAMPLE}}_FIRMWARE(firmware))) {
		g_set_error(
		    error,
		    FWUPD_ERROR,
		    FWUPD_ERROR_INVALID_FILE,
		    "start address mismatch, got 0x%04x, expected 0x%04x",
		    fu_{{vendor_example}}_firmware_get_start_addr(FU_{{VENDOR_EXAMPLE}}_FIRMWARE(firmware)),
		    self->start_addr);
		return NULL;
	}

	if (!fu_firmware_parse_stream(firmware, stream, 0x0, flags, error))
		return NULL;
	return g_steal_pointer(&firmware);
}

static gboolean
fu_{{vendor_example}}_device_write_blocks(Fu{{VendorExample}}Device *self,
				      FuChunkArray *chunks,
				      FuProgress *progress,
				      GError **error)
{
	/* progress */
	fu_progress_set_id(progress, G_STRLOC);
	fu_progress_set_steps(progress, fu_chunk_array_length(chunks));
	for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
		g_autoptr(FuChunk) chk = NULL;

		/* prepare chunk */
		chk = fu_chunk_array_index(chunks, i, error);
		if (chk == NULL)
			return FALSE;

		/* TODO: send to hardware */

{%- if Parent == 'Hid' %}
		guint8 buf[64] = { 0x12, 0x24, 0x0 }; /* TODO: this is the preamble */

		/* TODO: copy in payload */
		if (!fu_memcpy_safe(buf, sizeof(buf), 0x2, /* TODO: copy to dst at offset */
				    fu_chunk_get_data(chk),
				    fu_chunk_get_data_sz(chk),
				    0x0, /* src */
				    fu_chunk_get_data_sz(chk),
				    error))
			return FALSE;
		if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
					      0x01,
					      buf,
					      sizeof(buf),
					      5000, /* ms */
					      FU_HID_DEVICE_FLAG_NONE,
					      error)) {
			g_prefix_error_literal(error, "failed to send packet: ");
			return FALSE;
		}
		if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
					      0x01,
					      buf,
					      sizeof(buf),
					      5000, /* ms */
					      FU_HID_DEVICE_FLAG_NONE,
					      error)) {
			g_prefix_error_literal(error, "failed to receive packet: ");
			return FALSE;
		}
{%- endif %}

		/* update progress */
		fu_progress_step_done(progress);
	}

	/* success */
	return TRUE;
}

static gboolean
fu_{{vendor_example}}_device_write_firmware(FuDevice *device,
					FuFirmware *firmware,
					FuProgress *progress,
					FwupdInstallFlags flags,
					GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);
	g_autoptr(GInputStream) stream = NULL;
	g_autoptr(FuChunkArray) chunks = NULL;

	/* progress */
	fu_progress_set_id(progress, G_STRLOC);
	fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 44, NULL);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 35, NULL);

	/* get default image */
	stream = fu_firmware_get_stream(firmware, error);
	if (stream == NULL)
		return FALSE;

	/* write each block */
	chunks = fu_chunk_array_new_from_stream(stream, self->start_addr, FU_CHUNK_PAGESZ_NONE, 64 /* block_size */, error);
	if (chunks == NULL)
		return FALSE;
	if (!fu_{{vendor_example}}_device_write_blocks(self,
						   chunks,
						   fu_progress_get_child(progress),
						   error))
		return FALSE;
	fu_progress_step_done(progress);

	/* TODO: verify each block */
	fu_progress_step_done(progress);

	/* success! */
	return TRUE;
}

static gboolean
fu_{{vendor_example}}_device_set_quirk_kv(FuDevice *device,
				      const gchar *key,
				      const gchar *value,
				      GError **error)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(device);

	/* TODO: parse value from quirk file */
	if (g_strcmp0(key, "{{VendorExample}}StartAddr") == 0) {
		guint64 tmp = 0;
		if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
			return FALSE;
		self->start_addr = tmp;
		return TRUE;
	}

	/* failed */
	g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported");
	return FALSE;
}

static void
fu_{{vendor_example}}_device_set_progress(FuDevice *self, FuProgress *progress)
{
	fu_progress_set_id(progress, G_STRLOC);
	fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
	fu_progress_add_step(progress, FWUPD_STATUS_DECOMPRESSING, 0, "prepare-fw");
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 57, "write");
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 43, "reload");
}

static void
fu_{{vendor_example}}_device_init(Fu{{VendorExample}}Device *self)
{
	self->start_addr = 0x5000;
	fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET);
	fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
	fu_device_add_protocol(FU_DEVICE(self), "com.{{vendor}}.{{example}}");
	fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
	fu_device_add_icon(FU_DEVICE(self), "icon-name");
	fu_device_register_private_flag(FU_DEVICE(self),
					FU_{{VENDOR_EXAMPLE}}_DEVICE_FLAG_EXAMPLE);
{%- if Parent == 'Usb' %}
	fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x01);
{%- endif %}
{%- if Parent == 'Hid' %}
	fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_RETRY_FAILURE);
{%- endif %}
}

static void
fu_{{vendor_example}}_device_finalize(GObject *object)
{
	Fu{{VendorExample}}Device *self = FU_{{VENDOR_EXAMPLE}}_DEVICE(object);

	/* TODO: free any allocated instance state here */
	g_assert(self != NULL);

	G_OBJECT_CLASS(fu_{{vendor_example}}_device_parent_class)->finalize(object);
}

static void
fu_{{vendor_example}}_device_class_init(Fu{{VendorExample}}DeviceClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);
	FuDeviceClass *device_class = FU_DEVICE_CLASS(klass);
	object_class->finalize = fu_{{vendor_example}}_device_finalize;
	device_class->to_string = fu_{{vendor_example}}_device_to_string;
	device_class->probe = fu_{{vendor_example}}_device_probe;
	device_class->setup = fu_{{vendor_example}}_device_setup;
	device_class->reload = fu_{{vendor_example}}_device_reload;
	device_class->prepare = fu_{{vendor_example}}_device_prepare;
	device_class->cleanup = fu_{{vendor_example}}_device_cleanup;
	device_class->attach = fu_{{vendor_example}}_device_attach;
	device_class->detach = fu_{{vendor_example}}_device_detach;
	device_class->prepare_firmware = fu_{{vendor_example}}_device_prepare_firmware;
	device_class->write_firmware = fu_{{vendor_example}}_device_write_firmware;
	device_class->set_quirk_kv = fu_{{vendor_example}}_device_set_quirk_kv;
	device_class->set_progress = fu_{{vendor_example}}_device_set_progress;
}
