/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Purple 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 <birb.h>

#include "purplecommand.h"

struct _PurpleCommand {
	GObject parent;

	char *icon_name;
	char *id;
	GDateTime *last_used;
	char *name;
	int priority;
	char *source;
	char *summary;
	PurpleTags *tags;
	guint use_count;
};

enum {
	PROP_0,
	PROP_ICON_NAME,
	PROP_ID,
	PROP_LAST_USED,
	PROP_NAME,
	PROP_PRIORITY,
	PROP_SOURCE,
	PROP_SUMMARY,
	PROP_TAGS,
	PROP_USE_COUNT,
	N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };

enum {
	SIG_EXECUTED,
	N_SIGNALS,
};
static guint signals[N_SIGNALS] = {0, };

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
purple_command_set_id(PurpleCommand *command, const char *id) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));

	if(g_set_str(&command->id, id)) {
		g_object_notify_by_pspec(G_OBJECT(command), properties[PROP_ID]);
	}
}

static void
purple_command_set_name(PurpleCommand *command, const char *name) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));
	g_return_if_fail(!birb_str_is_empty(name));

	if(g_set_str(&command->name, name)) {
		g_object_notify_by_pspec(G_OBJECT(command), properties[PROP_NAME]);
	}
}

static void
purple_command_set_source(PurpleCommand *command, const char *source) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));
	g_return_if_fail(!birb_str_is_empty(source));

	if(g_set_str(&command->source, source)) {
		g_object_notify_by_pspec(G_OBJECT(command), properties[PROP_SOURCE]);
	}
}

static void
purple_command_set_priority(PurpleCommand *command, int priority) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));

	if(command->priority != priority) {
		command->priority = priority;

		g_object_notify_by_pspec(G_OBJECT(command),
		                         properties[PROP_PRIORITY]);
	}
}

/******************************************************************************
 * Default Handlers
 *****************************************************************************/
static void
purple_command_default_executed_handler(PurpleCommand *command,
                                        G_GNUC_UNUSED PurpleConversation *conversation,
                                        G_GNUC_UNUSED GStrv params)
{
	GDateTime *now = NULL;
	GObject *obj = G_OBJECT(command);

	/* We're going to set multiple properties, so we need to freeze property
	 * notifications.
	 */
	g_object_freeze_notify(obj);

	/* We use local time as this should be user specific. */
	now = g_date_time_new_now_local();
	purple_command_set_last_used(command, now);
	birb_date_time_clear(&now);

	if(command->use_count < G_MAXUINT32) {
		purple_command_set_use_count(command, command->use_count + 1);
	}

	g_object_thaw_notify(obj);
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(PurpleCommand, purple_command, G_TYPE_OBJECT)

static void
purple_command_constructed(GObject *obj) {
	PurpleCommand *command = NULL;

	G_OBJECT_CLASS(purple_command_parent_class)->constructed(obj);

	command = PURPLE_COMMAND(obj);

	if(command->id == NULL) {
		char *id = g_uuid_string_random();

		purple_command_set_id(command, id);
		g_clear_pointer(&id, g_free);
	}
}

static void
purple_command_finalize(GObject *obj) {
	PurpleCommand *command = PURPLE_COMMAND(obj);

	g_clear_pointer(&command->icon_name, g_free);
	g_clear_pointer(&command->id, g_free);
	birb_date_time_clear(&command->last_used);
	g_clear_pointer(&command->name, g_free);
	g_clear_pointer(&command->source, g_free);
	g_clear_pointer(&command->summary, g_free);
	g_clear_object(&command->tags);

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

static void
purple_command_get_property(GObject *obj, guint param_id, GValue *value,
                            GParamSpec *pspec)
{
	PurpleCommand *command = PURPLE_COMMAND(obj);

	switch(param_id) {
	case PROP_ICON_NAME:
		g_value_set_string(value, purple_command_get_icon_name(command));
		break;
	case PROP_ID:
		g_value_set_string(value, purple_command_get_id(command));
		break;
	case PROP_LAST_USED:
		g_value_set_boxed(value, purple_command_get_last_used(command));
		break;
	case PROP_NAME:
		g_value_set_string(value, purple_command_get_name(command));
		break;
	case PROP_PRIORITY:
		g_value_set_int(value, purple_command_get_priority(command));
		break;
	case PROP_SOURCE:
		g_value_set_string(value, purple_command_get_source(command));
		break;
	case PROP_SUMMARY:
		g_value_set_string(value, purple_command_get_summary(command));
		break;
	case PROP_TAGS:
		g_value_set_object(value, purple_command_get_tags(command));
		break;
	case PROP_USE_COUNT:
		g_value_set_uint(value, purple_command_get_use_count(command));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
purple_command_set_property(GObject *obj, guint param_id, const GValue *value,
                            GParamSpec *pspec)
{
	PurpleCommand *command = PURPLE_COMMAND(obj);

	switch(param_id) {
	case PROP_ICON_NAME:
		purple_command_set_icon_name(command, g_value_get_string(value));
		break;
	case PROP_ID:
		purple_command_set_id(command, g_value_get_string(value));
		break;
	case PROP_LAST_USED:
		purple_command_set_last_used(command, g_value_get_boxed(value));
		break;
	case PROP_NAME:
		purple_command_set_name(command, g_value_get_string(value));
		break;
	case PROP_PRIORITY:
		purple_command_set_priority(command, g_value_get_int(value));
		break;
	case PROP_SOURCE:
		purple_command_set_source(command, g_value_get_string(value));
		break;
	case PROP_SUMMARY:
		purple_command_set_summary(command, g_value_get_string(value));
		break;
	case PROP_USE_COUNT:
		purple_command_set_use_count(command, g_value_get_uint(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
purple_command_init(PurpleCommand *command) {
	command->tags = purple_tags_new();
}

static void
purple_command_class_init(PurpleCommandClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->constructed = purple_command_constructed;
	obj_class->finalize = purple_command_finalize;
	obj_class->get_property = purple_command_get_property;
	obj_class->set_property = purple_command_set_property;

	/**
	 * PurpleCommand:icon-name:
	 *
	 * The icon name for the command.
	 *
	 * Since: 3.0
	 */
	properties[PROP_ICON_NAME] = g_param_spec_string(
		"icon-name", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:id:
	 *
	 * A unique identifier for the command.
	 *
	 * Since: 3.0
	 */
	properties[PROP_ID] = g_param_spec_string(
		"id", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS |
		G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:last-used:
	 *
	 * The date time of when the command was last used.
	 *
	 * Since: 3.0
	 */
	properties[PROP_LAST_USED] = g_param_spec_boxed(
		"last-used", NULL, NULL,
		G_TYPE_DATE_TIME,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:name:
	 *
	 * The name of the command.
	 *
	 * This is what the user would type after the prefix that the user
	 * interface is using. If the user interface is using `/`, then this would
	 * be `me` for the `/me` command.
	 *
	 * Since: 3.0
	 */
	properties[PROP_NAME] = g_param_spec_string(
		"name", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS |
		G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:priority:
	 *
	 * The priority for the command.
	 *
	 * If multiple commands have the same name, this will be used to determine
	 * which one to return or sort first. Higher values will have higher
	 * priority.
	 *
	 * Since: 3.0
	 */
	properties[PROP_PRIORITY] = g_param_spec_int(
		"priority", NULL, NULL,
		G_MININT32, G_MAXINT32, 0,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS |
		G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:source:
	 *
	 * The source of the command.
	 *
	 * This is a user visible string that user interfaces can display to help
	 * users determine what this command is from. For example, this could be
	 * `IRC`, `XMPP`, `my cool plugin`, etc.
	 *
	 * Since: 3.0
	 */
	properties[PROP_SOURCE] = g_param_spec_string(
		"source", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS |
		G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:summary:
	 *
	 * A summary of the command.
	 *
	 * This is a user visible string that user interfaces can display to help
	 * users determine what this command will do.
	 *
	 * Since: 3.0
	 */
	properties[PROP_SUMMARY] = g_param_spec_string(
		"summary", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:tags:
	 *
	 * The [class@Tags] for the command.
	 *
	 * These tags will be used with [property@Conversation:tags] in a call to
	 * [method@Tags.contains] to determine if the command is valid for a
	 * conversation. Likewise, if this doesn't contain any tags, it will match
	 * all conversations.
	 *
	 * Since: 3.0
	 */
	properties[PROP_TAGS] = g_param_spec_object(
		"tags", NULL, NULL,
		PURPLE_TYPE_TAGS,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleCommand:use-count:
	 *
	 * The count of how many times the command has been used.
	 *
	 * Since: 3.0
	 */
	properties[PROP_USE_COUNT] = g_param_spec_uint(
		"use-count", NULL, NULL,
		0, G_MAXUINT32, 0,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);

	/**
	 * PurpleCommand::executed:
	 * @command: The instance.
	 * @conversation: (nullable): The conversation this command was run from.
	 * @params: (nullable): The parameters passed to the command.
	 *
	 * Emitted when [method@Command.execute] is called.
	 *
	 * Since: 3.0
	 */
	signals[SIG_EXECUTED] = g_signal_new_class_handler(
		"executed",
		G_OBJECT_CLASS_TYPE(klass),
		G_SIGNAL_RUN_LAST,
		G_CALLBACK(purple_command_default_executed_handler),
		NULL,
		NULL,
		NULL,
		G_TYPE_NONE,
		2,
		PURPLE_TYPE_CONVERSATION,
		G_TYPE_STRV);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
void
purple_command_execute(PurpleCommand *command,
                       PurpleConversation *conversation, const char *params)
{
	GStrv paramsv = NULL;

	g_return_if_fail(PURPLE_IS_COMMAND(command));

	if(params != NULL) {
		paramsv = g_strsplit(params, " ", -1);
	}

	purple_command_executev(command, conversation, paramsv);

	g_clear_pointer(&paramsv, g_strfreev);
}

void
purple_command_executev(PurpleCommand *command,
                        PurpleConversation *conversation, GStrv params)
{
	g_return_if_fail(PURPLE_IS_COMMAND(command));

	g_signal_emit(G_OBJECT(command), signals[SIG_EXECUTED], 0, conversation,
	              params);
}

const char *
purple_command_get_icon_name(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), NULL);

	return command->icon_name;
}

const char *
purple_command_get_id(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), NULL);

	return command->id;
}

GDateTime *
purple_command_get_last_used(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), NULL);

	return command->last_used;
}

const char *
purple_command_get_name(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), NULL);

	return command->name;
}

int
purple_command_get_priority(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), 0);

	return command->priority;
}

const char *
purple_command_get_source(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), NULL);

	return command->source;
}

const char *
purple_command_get_summary(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), NULL);

	return command->summary;
}

PurpleTags *
purple_command_get_tags(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), NULL);

	return command->tags;
}

guint
purple_command_get_use_count(PurpleCommand *command) {
	g_return_val_if_fail(PURPLE_IS_COMMAND(command), 0);

	return command->use_count;
}

PurpleCommand *
purple_command_new(const char *name, const char *source, int priority) {
	g_return_val_if_fail(!birb_str_is_empty(name), NULL);
	g_return_val_if_fail(!birb_str_is_empty(source), NULL);

	return g_object_new(
		PURPLE_TYPE_COMMAND,
		"name", name,
		"priority", priority,
		"source", source,
		NULL);
}

PurpleCommand *
purple_command_new_with_id(const char *id, const char *name,
                           const char *source, int priority)
{
	g_return_val_if_fail(!birb_str_is_empty(name), NULL);
	g_return_val_if_fail(!birb_str_is_empty(source), NULL);

	return g_object_new(
		PURPLE_TYPE_COMMAND,
		"id", id,
		"name", name,
		"priority", priority,
		"source", source,
		NULL);
}

void
purple_command_set_icon_name(PurpleCommand *command, const char *icon_name) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));

	if(g_set_str(&command->icon_name, icon_name)) {
		g_object_notify_by_pspec(G_OBJECT(command),
		                         properties[PROP_ICON_NAME]);
	}
}

void
purple_command_set_last_used(PurpleCommand *command, GDateTime *last_used) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));

	if(birb_date_time_set(&command->last_used, last_used)) {
		g_object_notify_by_pspec(G_OBJECT(command),
		                         properties[PROP_LAST_USED]);
	}
}

void
purple_command_set_summary(PurpleCommand *command, const char *summary) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));

	if(g_set_str(&command->summary, summary)) {
		g_object_notify_by_pspec(G_OBJECT(command), properties[PROP_SUMMARY]);
	}
}

void
purple_command_set_use_count(PurpleCommand *command, guint use_count) {
	g_return_if_fail(PURPLE_IS_COMMAND(command));

	if(command->use_count != use_count) {
		command->use_count = use_count;

		g_object_notify_by_pspec(G_OBJECT(command),
		                         properties[PROP_USE_COUNT]);
	}
}
