/*
 * Ibis - IRCv3 Library
 * Copyright (C) 2022-2024 Ibis Developers <devel@pidgin.im>
 *
 * 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 <glib.h>
#include <glib/gi18n.h>

#ifndef _WIN32
#include <signal.h>
#include <glib-unix.h>
#endif /* _WIN32 */

#include <hasl.h>

#include <ibis.h>

/******************************************************************************
 * Command line options
 *****************************************************************************/
static char *alt_nick = NULL;
static char *join_channels = NULL;
static char *nick = NULL;
static char *server = NULL;
static int port = 6667;
static char *password = NULL;
static gboolean require_sasl = FALSE;
static gboolean sasl_allow_clear_text = FALSE;
static char *sasl_mechanisms = NULL;
static char *sasl_password = NULL;
static char *sasl_username = NULL;
static gboolean tls = FALSE;

static GOptionEntry cli_options[] = {
	{
		.long_name = "alt-nick",
		.short_name = 'a',
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &alt_nick,
		.description = N_("An alternate nickname to use."),
		.arg_description = N_("NICK"),
	}, {
		.long_name = "join-channels",
		.short_name = 'j',
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &join_channels,
		.description = N_("A comma separated list of channels to join."),
		.arg_description = N_("CHANNEL"),
	}, {
		.long_name = "nick",
		.short_name = 'n',
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &nick,
		.description = N_("The nickname to use."),
		.arg_description = N_("NICK"),
	}, {
		.long_name = "password",
		.short_name = 'P',
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &password,
		.description = N_("The server password to use."),
	}, {
		.long_name = "port",
		.short_name = 'p',
		.arg = G_OPTION_ARG_INT,
		.arg_data = &port,
		.description = N_("The port to connect to on the server."),
		.arg_description = N_("PORT"),
	}, {
		.long_name = "require-sasl",
		.arg = G_OPTION_ARG_NONE,
		.arg_data = &require_sasl,
		.description = N_("Require SASL authentication"),
	}, {
		.long_name = "sasl-allow-clear-text",
		.arg = G_OPTION_ARG_NONE,
		.arg_data = &sasl_allow_clear_text,
		.description = N_("Allow clear text mechanisms."),
	}, {
		.long_name = "sasl-mechanisms",
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &sasl_mechanisms,
		.description = N_("Comma separated list of SASL mechanisms to use."),
	}, {
		.long_name = "sasl-password",
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &sasl_password,
		.description = N_("A password to use for SASL."),
	}, {
		.long_name = "sasl-username",
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &sasl_username,
		.description = N_("A username to use for SASL."),
	}, {
		.long_name = "server",
		.short_name = 's',
		.arg = G_OPTION_ARG_STRING,
		.arg_data = &server,
		.description = N_("The ip or hostname for the server to connect to."),
		.arg_description = N_("HOSTNAME"),
	}, {
		.long_name = "tls",
		.short_name = 't',
		.arg = G_OPTION_ARG_NONE,
		.arg_data = &tls,
		.description = N_("Attempt a TLS connection."),
	},
	G_OPTION_ENTRY_NULL
};

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
ibiscli_log(IbisMessage *message, const char *prefix) {
	GDateTime *now = NULL;
	char *serialized = NULL;
	char *timestamp;

	now = g_date_time_new_now_local();
	timestamp = g_date_time_format(now, "%H:%M:%S");
	g_date_time_unref(now);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_print("%s %s%s", timestamp, prefix, serialized);

	g_free(timestamp);
	g_free(serialized);
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
ibiscli_activate_cb(GApplication *application, gpointer data) {
	IbisClient *client = data;

	/* Hold the application so we stick around. */
	g_application_hold(application);

	if(!ibis_str_is_empty(nick)) {
		ibis_client_set_nick(client, nick);
	}

	ibis_client_set_alt_nick(client, alt_nick);

	if(require_sasl) {
		HaslContext *hasl_context = hasl_context_new();

		if(!ibis_str_is_empty(sasl_mechanisms)) {
			hasl_context_set_allowed_mechanisms(hasl_context, sasl_mechanisms);
		}

		if(!ibis_str_is_empty(sasl_username) &&
		   !ibis_str_is_empty(sasl_password))
		{
			hasl_context_set_username(hasl_context, sasl_username);
			hasl_context_set_password(hasl_context, sasl_password);
		}

		hasl_context_set_allow_clear_text(hasl_context, sasl_allow_clear_text);
		hasl_context_set_tls(hasl_context, tls);

		ibis_client_set_hasl_context(client, hasl_context);
		g_clear_object(&hasl_context);
	}

	ibis_client_connect(client, server, (guint16)port, password, tls, NULL,
	                    NULL);
}

static void
ibiscli_client_connected_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec,
                            gpointer data)
{
	IbisClient *client = IBIS_CLIENT(obj);
	GApplication *application = data;

	if(!ibis_client_get_connected(client)) {
		g_application_release(application);
	}
}

static void
ibiscli_client_registered_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec,
                             G_GNUC_UNUSED gpointer data)
{
	IbisClient *client = IBIS_CLIENT(obj);

	if(ibis_client_get_registered(client)) {
		if(!ibis_str_is_empty(join_channels)) {
			IbisMessage *message = ibis_message_new(IBIS_MSG_JOIN);

			ibis_message_set_params(message, join_channels, NULL);

			ibis_client_write(client, message);
		}
	}
}

#ifndef _WIN32
static gboolean
ibiscli_sigint_cb(gpointer data) {
	IbisClient *client = data;

	ibis_client_disconnect(client, NULL, "I'm out of here!");

	return G_SOURCE_REMOVE;
}
#endif /* _WIN32 */

static void
ibiscli_network_changed_cb(GObject *self, G_GNUC_UNUSED GParamSpec *pspec,
                           G_GNUC_UNUSED gpointer data)
{
	IbisClient *client = IBIS_CLIENT(self);

	g_message("network name changed to '%s'", ibis_client_get_network(client));
}

static gboolean
ibiscli_message_cb(G_GNUC_UNUSED IbisClient *client, const char *command,
                   IbisMessage *message, G_GNUC_UNUSED gpointer data)
{
	ibiscli_log(message, "< ");

	if(ibis_str_equal(command, IBIS_ERR_SASLABORTED) ||
	   ibis_str_equal(command, IBIS_ERR_SASLALREADY) ||
	   ibis_str_equal(command, IBIS_ERR_SASLFAIL) ||
	   ibis_str_equal(command, IBIS_ERR_SASLTOOLONG) ||
	   ibis_str_equal(command, IBIS_MSG_AUTHENTICATE) ||
	   ibis_str_equal(command, IBIS_MSG_CAP) ||
	   ibis_str_equal(command, IBIS_MSG_NICK) ||
	   ibis_str_equal(command, IBIS_MSG_PING) ||
	   ibis_str_equal(command, IBIS_MSG_PONG) ||
	   ibis_str_equal(command, IBIS_RPL_SASLMECHS) ||
	   ibis_str_equal(command, IBIS_RPL_SASLSUCCESS) ||
	   ibis_str_equal(command, IBIS_RPL_WELCOME))
	{
		return FALSE;
	}

	return TRUE;
}

static gboolean
ibiscli_message_after_cb(G_GNUC_UNUSED IbisClient *client,
                         G_GNUC_UNUSED const char *command,
                         G_GNUC_UNUSED IbisMessage *message,
                         G_GNUC_UNUSED gpointer data)
{
	/* We should be logging everything, so all we need to do here is tell Ibis
	 * that we've handled everything we were supposed to.
	 */

	return TRUE;
}

static void
ibiscli_wrote_message_cb(G_GNUC_UNUSED IbisClient *client,
                         IbisMessage *message,
                         G_GNUC_UNUSED gpointer data)
{
	ibiscli_log(message, "> ");
}

/******************************************************************************
 * Main
 *****************************************************************************/
int
main(int argc, char *argv[]) {
	IbisClient *client = NULL;
	GApplication *application = NULL;
	GError *error = NULL;
	gint ret = 0;

	client = ibis_client_new();
	g_signal_connect(client, "notify::network",
	                 G_CALLBACK(ibiscli_network_changed_cb), NULL);
	g_signal_connect(client, "message",
	                 G_CALLBACK(ibiscli_message_cb), NULL);
	g_signal_connect_after(client, "message",
	                       G_CALLBACK(ibiscli_message_after_cb), NULL);
	g_signal_connect(client, "wrote-message",
	                 G_CALLBACK(ibiscli_wrote_message_cb), NULL);

	application = g_application_new("org.imfreedom.ibis.Ibis-Cli",
	                                G_APPLICATION_NON_UNIQUE);
	g_application_add_main_option_entries(application, cli_options);
	g_signal_connect(application, "activate",
	                 G_CALLBACK(ibiscli_activate_cb), client);

	g_signal_connect(client, "notify::connected",
	                 G_CALLBACK(ibiscli_client_connected_cb), application);
	g_signal_connect(client, "notify::registered",
	                 G_CALLBACK(ibiscli_client_registered_cb), NULL);

#ifndef _WIN32
	g_unix_signal_add(SIGINT, ibiscli_sigint_cb, client);
#endif /* _WIN32 */

	ret = g_application_run(application, argc, argv);

	g_clear_object(&application);

	error = ibis_client_get_error(client);
	if(error != NULL) {
		g_warning("client error: %s", error->message);
	}
	g_clear_object(&client);

	g_clear_pointer(&alt_nick, g_free);
	g_clear_pointer(&join_channels, g_free);
	g_clear_pointer(&nick, g_free);
	g_clear_pointer(&password, g_free);
	g_clear_pointer(&sasl_mechanisms, g_free);
	g_clear_pointer(&sasl_password, g_free);
	g_clear_pointer(&sasl_username, g_free);
	g_clear_pointer(&server, g_free);

	return ret;
}
