// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2025, Richard Acayan. All rights reserved.
 */

#include <gio/gio.h>
#include <glib.h>
#include <libqmi-glib.h>
#include <libqrtr.h>
#include <stdio.h>
#include <string.h>

#include "qvd-message.h"

enum {
	PROP_REQ = 1,
	PROP_SOCK,
	PROP_NODE,
	PROP_PORT,
	N_PROPS,
};

struct _QVDMessage {
	GObject parent_instance;

	gboolean txn_valid;
	unsigned int txn;

	GSocket *sock;
	struct qrtr_packet *req;
	guint32 node;
	guint32 port;
};

G_DEFINE_TYPE(QVDMessage, qvd_message, G_TYPE_OBJECT)

static GParamSpec *props[N_PROPS] = { NULL, };

static void qvd_message_dispose(GObject *obj);

void qvd_message_get_property(GObject *obj,
			      guint prop,
			      GValue *val,
			      GParamSpec *spec)
{
	QVDMessage *ctx = QVD_MESSAGE(obj);

	switch (prop) {
		case PROP_REQ:
			g_value_set_pointer(val, ctx->req);
			break;
		case PROP_SOCK:
			g_value_set_object(val, G_OBJECT(ctx->sock));
			break;
		case PROP_NODE:
			g_value_set_uint(val, ctx->node);
			break;
		case PROP_PORT:
			g_value_set_uint(val, ctx->port);
			break;
		default:
			break;
	}
}

void qvd_message_set_property(GObject *obj,
			      guint prop,
			      const GValue *val,
			      GParamSpec *spec)
{
	QVDMessage *ctx = QVD_MESSAGE(obj);

	switch (prop) {
		case PROP_REQ:
			ctx->req = g_value_get_pointer(val);
			break;
		case PROP_SOCK:
			ctx->sock = G_SOCKET(g_value_get_object(val));
			break;
		case PROP_NODE:
			ctx->node = g_value_get_uint(val);
			break;
		case PROP_PORT:
			ctx->port = g_value_get_uint(val);
			break;
		default:
			break;
	}
}

static void qvd_message_class_init(QVDMessageClass *c)
{
	GObjectClass *obj_class = G_OBJECT_CLASS(c);

	obj_class->get_property = qvd_message_get_property;
	obj_class->set_property = qvd_message_set_property;
	obj_class->dispose = qvd_message_dispose;

	props[PROP_REQ] = g_param_spec_pointer("request", "Request",
					       "QMI request from the modem",
					       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);

	props[PROP_SOCK] = g_param_spec_object("socket", "Socket",
					       "Socket for request and response",
					       G_TYPE_SOCKET,
					       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);

	props[PROP_NODE] = g_param_spec_uint("peer-node", "Peer Node",
					     "QRTR Node of client",
					     0, UINT32_MAX, 0,
					     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);

	props[PROP_PORT] = g_param_spec_uint("peer-port", "Peer Port",
					     "QRTR Port of client",
					     0, UINT32_MAX, 0,
					     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);

	g_object_class_install_properties(obj_class, N_PROPS, props);
}

static void qvd_message_init(QVDMessage *ctx)
{
}

static void qvd_message_dispose(GObject *obj)
{
	QVDMessage *ctx = QVD_MESSAGE(obj);

	g_clear_object(&ctx->sock);
	g_free(ctx->req->data);
	g_free(ctx->req);
}

QVDMessage *qvd_message_new(GSocket *sock,
			    const struct qrtr_packet *req,
			    guint32 node, guint32 port)
{
	return QVD_MESSAGE(g_object_new(QVD_TYPE_MESSAGE,
					"socket", sock,
					"request", req,
					"peer-node", node,
					"peer-port", port,
					NULL));
}

guint qvd_message_get_msg_id(QVDMessage *ctx)
{
	unsigned int msg_id;

	qmi_decode_header(ctx->req, &msg_id);

	return msg_id;
}

void qvd_message_decode(QVDMessage *ctx,
			void *c_struct,
			struct qmi_elem_info *ei,
			GError **out_err)
{
	GError *err;
	guint msg_id;
	int ret;

	msg_id = qvd_message_get_msg_id(ctx);

	ret = qmi_decode_message(c_struct, &ctx->txn, ctx->req, QMI_REQUEST, msg_id, ei);
	if (ret < 0) {
		err = g_error_new(QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_MESSAGE,
				  "failed to decode request: %s",
				  strerror(-ret));
		g_propagate_error(out_err, err);
		return;
	}

	ctx->txn_valid = true;
}

void qvd_message_respond(QVDMessage *ctx,
			 const void *c_struct,
			 struct qmi_elem_info *ei,
			 GError **out_err)
{
	GError *err;
	struct qrtr_packet outpkt;
	guint msg_id;
	ssize_t ret;
	int fd;

	if (!ctx->txn_valid) {
		err = g_error_new(QMI_CORE_ERROR, QMI_CORE_ERROR_WRONG_STATE,
				  "request must be decoded before issuing a response");
		g_propagate_error(out_err, err);
		return;
	}

	fd = g_socket_get_fd(ctx->sock);
	msg_id = qvd_message_get_msg_id(ctx);

	outpkt.data_len = 65536;
	outpkt.data = g_malloc(outpkt.data_len);

	ret = qmi_encode_message(&outpkt, QMI_RESPONSE, msg_id, ctx->txn, c_struct, ei);
	if (ret < 0) {
		err = g_error_new(QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_MESSAGE,
				  "failed to encode response: %s",
				  strerror(-ret));
		g_propagate_error(out_err, err);
		goto err;
	}

	ret = qrtr_sendto(fd, ctx->node, ctx->port, outpkt.data, outpkt.data_len);
	if (ret < 0) {
		err = g_error_new(QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_MESSAGE,
				  "failed to send response: %s",
				  strerror(-ret));
		g_propagate_error(out_err, err);
	}

err:
	g_free(outpkt.data);
}
