/*
 * Copyright 2018 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include <string.h>

#include "fu-wacom-usb-device.h"
#include "fu-wacom-usb-module-touch.h"
#include "fu-wacom-usb-struct.h"

struct _FuWacomUsbModuleTouch {
	FuWacomUsbModule parent_instance;
};

G_DEFINE_TYPE(FuWacomUsbModuleTouch, fu_wacom_usb_module_touch, FU_TYPE_WACOM_USB_MODULE)

static gboolean
fu_wacom_usb_module_touch_write_firmware(FuDevice *device,
					 FuFirmware *firmware,
					 FuProgress *progress,
					 FwupdInstallFlags flags,
					 GError **error)
{
	FuWacomUsbModule *self = FU_WACOM_USB_MODULE(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_ERASE, 10, NULL);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, NULL);

	g_debug("using element at addr 0x%0x", (guint)fu_firmware_get_addr(firmware));

	/* build each data packet */
	stream = fu_firmware_get_stream(firmware, error);
	if (stream == NULL) {
		g_prefix_error_literal(error, "wacom touch module failed to get stream: ");
		return FALSE;
	}
	chunks = fu_chunk_array_new_from_stream(stream,
						fu_firmware_get_addr(firmware),
						FU_CHUNK_PAGESZ_NONE,
						128,
						error);
	if (chunks == NULL)
		return FALSE;

	/* start, which will erase the module */
	if (!fu_wacom_usb_module_set_feature(self,
					     FU_WACOM_USB_MODULE_COMMAND_START,
					     NULL,
					     fu_progress_get_child(progress),
					     FU_WACOM_USB_MODULE_POLL_INTERVAL,
					     FU_WACOM_USB_MODULE_START_TIMEOUT,
					     error)) {
		g_prefix_error_literal(error, "wacom touch module failed to erase: ");
		return FALSE;
	}
	fu_progress_step_done(progress);

	/* data */
	for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
		g_autoptr(FuChunk) chk = NULL;
		guint8 buf[128 + 7] = {0xff};
		g_autoptr(GBytes) blob_chunk = NULL;

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

		/* build G11T data packet */
		memset(buf, 0xff, sizeof(buf));
		buf[0] = 0x01; /* writing */
		buf[1] = fu_chunk_get_idx(chk) + 1;
		fu_memwrite_uint32(&buf[2], fu_chunk_get_address(chk), G_LITTLE_ENDIAN);
		buf[6] = 0x10; /* no idea! */
		if (!fu_memcpy_safe(buf,
				    sizeof(buf),
				    0x07, /* dst */
				    fu_chunk_get_data(chk),
				    fu_chunk_get_data_sz(chk),
				    0x0, /* src */
				    fu_chunk_get_data_sz(chk),
				    error)) {
			g_prefix_error_literal(error, "wacom touch module failed to memcpy: ");
			return FALSE;
		}
		blob_chunk = g_bytes_new(buf, sizeof(buf));
		if (!fu_wacom_usb_module_set_feature(self,
						     FU_WACOM_USB_MODULE_COMMAND_DATA,
						     blob_chunk,
						     fu_progress_get_child(progress),
						     FU_WACOM_USB_MODULE_POLL_INTERVAL,
						     FU_WACOM_USB_MODULE_DATA_TIMEOUT,
						     error)) {
			g_prefix_error(error, "failed to write block %u: ", fu_chunk_get_idx(chk));
			return FALSE;
		}

		/* update progress */
		fu_progress_set_percentage_full(fu_progress_get_child(progress),
						i + 1,
						fu_chunk_array_length(chunks));
	}
	fu_progress_step_done(progress);

	/* end */
	if (!fu_wacom_usb_module_set_feature(self,
					     FU_WACOM_USB_MODULE_COMMAND_END,
					     NULL,
					     fu_progress_get_child(progress),
					     FU_WACOM_USB_MODULE_POLL_INTERVAL,
					     FU_WACOM_USB_MODULE_END_TIMEOUT,
					     error)) {
		g_prefix_error_literal(error, "wacom touch module failed to end: ");
		return FALSE;
	}
	fu_progress_step_done(progress);

	/* success */
	return TRUE;
}

static void
fu_wacom_usb_module_touch_init(FuWacomUsbModuleTouch *self)
{
	fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
	fu_device_set_install_duration(FU_DEVICE(self), 30);
	fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_IHEX_FIRMWARE);
}

static void
fu_wacom_usb_module_touch_class_init(FuWacomUsbModuleTouchClass *klass)
{
	FuDeviceClass *device_class = FU_DEVICE_CLASS(klass);
	device_class->write_firmware = fu_wacom_usb_module_touch_write_firmware;
}

FuWacomUsbModule *
fu_wacom_usb_module_touch_new(FuDevice *proxy)
{
	FuWacomUsbModule *module = NULL;
	module = g_object_new(FU_TYPE_WACOM_USB_MODULE_TOUCH,
			      "proxy",
			      proxy,
			      "fw-type",
			      FU_WACOM_USB_MODULE_FW_TYPE_TOUCH,
			      NULL);
	return module;
}
