/*
 * Ibis - IRCv3 Library
 * Copyright (C) 2022-2024 Ibis Developers
 *
 * Ibis is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include "ibisctcpmessage.h"

#include "ibisconstants.h"
#include "ibisstring.h"

struct _IbisCTCPMessage {
	GObject parent;

	char *command;
	GStrv params;
};

enum {
	PROP_0,
	PROP_COMMAND,
	PROP_PARAMS,
	N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
ibis_ctcp_message_set_command(IbisCTCPMessage *message, const char *command) {
	char *normalized = NULL;

	g_return_if_fail(IBIS_IS_CTCP_MESSAGE(message));

	if(command != NULL) {
		normalized = g_utf8_strup(command, -1);
	}

	if(g_set_str(&message->command, normalized)) {
		g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_COMMAND]);
	}

	g_clear_pointer(&normalized, g_free);
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(IbisCTCPMessage, ibis_ctcp_message, G_TYPE_OBJECT)

static void
ibis_ctcp_message_finalize(GObject *obj) {
	IbisCTCPMessage *message = IBIS_CTCP_MESSAGE(obj);

	g_clear_pointer(&message->command, g_free);
	g_clear_pointer(&message->params, g_strfreev);

	G_OBJECT_CLASS(ibis_ctcp_message_parent_class)->finalize(obj);
}

static void
ibis_ctcp_message_get_property(GObject *obj, guint param_id, GValue *value,
                               GParamSpec *pspec)
{
	IbisCTCPMessage *message = IBIS_CTCP_MESSAGE(obj);

	switch(param_id) {
	case PROP_COMMAND:
		g_value_set_string(value, ibis_ctcp_message_get_command(message));
		break;
	case PROP_PARAMS:
		g_value_set_boxed(value, ibis_ctcp_message_get_params(message));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
ibis_ctcp_message_set_property(GObject *obj, guint param_id,
                               const GValue *value,
                               GParamSpec *pspec)
{
	IbisCTCPMessage *message = IBIS_CTCP_MESSAGE(obj);

	switch(param_id) {
	case PROP_COMMAND:
		ibis_ctcp_message_set_command(message, g_value_get_string(value));
		break;
	case PROP_PARAMS:
		ibis_ctcp_message_set_paramsv(message, g_value_get_boxed(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
ibis_ctcp_message_init(G_GNUC_UNUSED IbisCTCPMessage *message) {
}

static void
ibis_ctcp_message_class_init(IbisCTCPMessageClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->finalize = ibis_ctcp_message_finalize;
	obj_class->get_property = ibis_ctcp_message_get_property;
	obj_class->set_property = ibis_ctcp_message_set_property;

	/**
	 * IbisCTCPMessage:command:
	 *
	 * The command for this message.
	 *
	 * Since: 0.5
	 */
	properties[PROP_COMMAND] = g_param_spec_string(
		"command", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);

	/**
	 * IbisCTCPMessage:params:
	 *
	 * The params for this message.
	 *
	 * Since: 0.5
	 */
	properties[PROP_PARAMS] = g_param_spec_boxed(
		"params", NULL, NULL,
		G_TYPE_STRV,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
const char *
ibis_ctcp_message_get_command(IbisCTCPMessage *message) {
	g_return_val_if_fail(IBIS_IS_CTCP_MESSAGE(message), NULL);

	return message->command;
}

GStrv
ibis_ctcp_message_get_params(IbisCTCPMessage *message) {
	g_return_val_if_fail(IBIS_IS_CTCP_MESSAGE(message), NULL);

	return message->params;
}

void
ibis_ctcp_message_set_params(IbisCTCPMessage *message, ...) {
	GStrv params = NULL;
	GStrvBuilder *builder = NULL;
	va_list args;
	const char *param = NULL;

	g_return_if_fail(IBIS_IS_CTCP_MESSAGE(message));

	builder = g_strv_builder_new();

	va_start(args, message);
	while((param = va_arg(args, const char *)) != NULL) {
		g_strv_builder_add(builder, param);
	}
	va_end(args);

	params = g_strv_builder_end(builder);
	g_clear_pointer(&builder, g_strv_builder_unref);

	ibis_ctcp_message_set_paramsv(message, params);
	g_strfreev(params);
}

void
ibis_ctcp_message_set_paramsv(IbisCTCPMessage *message, GStrv params) {
	g_return_if_fail(IBIS_IS_CTCP_MESSAGE(message));

	if(message->params != params) {
		g_clear_pointer(&message->params, g_strfreev);

		if(params != NULL) {
			message->params = g_strdupv(params);
		}

		g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_PARAMS]);
	}
}

gboolean
ibis_ctcp_message_is_command(IbisCTCPMessage *message, const char *command) {
	g_return_val_if_fail(IBIS_IS_CTCP_MESSAGE(message), FALSE);

	return ibis_str_equal(message->command, command);
}

IbisCTCPMessage *
ibis_ctcp_message_new(const char *command) {
	g_return_val_if_fail(!ibis_str_is_empty(command), NULL);

	return g_object_new(
		IBIS_TYPE_CTCP_MESSAGE,
		"command", command,
		NULL);
}

IbisCTCPMessage *
ibis_ctcp_message_parse(const char *str) {
	IbisCTCPMessage *message = NULL;
	GStrv params = NULL;
	char *clean = NULL;
	gsize length = 0;
	guint n_params = 0;

	if(ibis_str_is_empty(str)) {
		return NULL;
	}

	if(str[0] != IBIS_CTCP_DELIMITER) {
		return NULL;
	}

	length = strlen(str);
	if(str[length - 1] == IBIS_CTCP_DELIMITER) {
		/* If the last character is a delimiter, copy the body
		 * skipping both leading and trailing delimiters.
		 */
		clean = g_strndup(str + 1, length - 2);
	} else {
		/* Skip the leading delimiter. */
		clean = g_strdup(str + 1);
	}

	params = g_strsplit(clean, " ", 2);
	g_free(clean);

	n_params = g_strv_length(params);
	if(n_params == 0) {
		return NULL;
	}

	message = ibis_ctcp_message_new(params[0]);

	if(n_params > 1 && !ibis_str_is_empty(params[1])) {
		GStrv remaining = g_strsplit(params[1], " ", 0);
		ibis_ctcp_message_set_paramsv(message, remaining);
		g_strfreev(remaining);
	}

	g_strfreev(params);

	return message;
}

char *
ibis_ctcp_message_serialize(IbisCTCPMessage *message) {
	GString *str = NULL;

	g_return_val_if_fail(IBIS_IS_CTCP_MESSAGE(message), NULL);

	str = g_string_new("");
	g_string_append_printf(str, "%c%s", IBIS_CTCP_DELIMITER, message->command);

	if(message->params != NULL) {
		char *body = NULL;

		body = g_strjoinv(" ", message->params);
		g_string_append_printf(str, " %s", body);
		g_free(body);
	}

	g_string_append_c(str, IBIS_CTCP_DELIMITER);

	return g_string_free_and_steal(str);
}
