/*
 *
 * netboard-jabber  --  network backend for netboard
 *
 * Based on example from loudmouth documentation.
 * 
 * Copyright (C) 2006-2007 Josef Kufner <jk@myserver.cz>
 * Copyright (C) 2003-2006 Imendio AB
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "main.h"	/* includes loudmouth and glib */
#include "process.h"
#include "message.h"

/* global vars */
gint verbose = D_NORMAL;
gchar *j_server = NULL;
gint j_port = 5222;
gchar *j_username = NULL;
gchar *j_nick = NULL;
gchar *j_password = NULL;
gchar *j_resource = "netboard";
gchar *j_room = NULL;
gchar *j_room_passwd = NULL;
gchar *j_fingerprint = NULL;
gint verbose_flag = FALSE, quiet_flag = FALSE, debug_flag = FALSE;
gchar *j_room_complete, *j_from_complete;

gchar start_time[32];

gchar *verbose_level_str[D__MAX + 1] = { 
	"error", "normal", "verbose", "debug", NULL
};


/* local vars */
static GMainLoop *main_loop;
static gboolean test_success = FALSE;

static guchar expected_fingerprint[20];

static GIOChannel *stdin_channel;
static unsigned int stdin_channel_tag;

static GOptionEntry entries[] = {
	{"server", 's', 0, G_OPTION_ARG_STRING, &j_server,
	 "Server to connect to", NULL},
	{"port", 'P', 0, G_OPTION_ARG_INT, &j_port,
	 "Port to connect to [default=5222]", NULL},
	{"nick", 'n', 0, G_OPTION_ARG_STRING, &j_nick,
	 "User's nickname", NULL},
	{"username", 'u', 0, G_OPTION_ARG_STRING, &j_username,
	 "Username to connect with (e.g. 'user' in user@server.org)", NULL},
	{"password", 'p', 0, G_OPTION_ARG_STRING, &j_password,
	 "Password to try", NULL},
	{"resource", 'r', 0, G_OPTION_ARG_STRING, &j_resource,
	 "Resource connect with [default=netboard]", NULL},
	{"room", 'R', 0, G_OPTION_ARG_STRING, &j_room,
	 "Chat room to send the message to (e.g. user@server.org)", NULL},
	{"fingerprint", 'f', 0, G_OPTION_ARG_STRING, &j_fingerprint,
	 "SSL Fingerprint to use", NULL},
	{"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet_flag,
	 "Be quiet", NULL},
	{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
	 "Be verbose", NULL},
	{"debug", 'd', 0, G_OPTION_ARG_NONE, &debug_flag,
	 "Be more verbose -- debuging info", NULL},
	{NULL}
};


static gchar *get_part_name(const gchar *username)
{
	const gchar *ch;

	g_return_val_if_fail(username != NULL, NULL);

	ch = strchr(username, '@');
	if (!ch) {
		return NULL;
	}

	return g_strndup(username, ch - username);
}


static gchar *print_finger(const char *fpr, unsigned int size)
{
	gint i;
	gchar *ret = NULL;
	GString *s = g_string_new_len(NULL, size * 4);
	
	for (i = 0; i < size - 1; i++) {
		g_string_append_printf(s, "%02X:", (unsigned) fpr[i] & 0xff);
	}
	g_string_append_printf(s, "%02X", (unsigned) fpr[size - 1] & 0xff);
	
	ret = g_strdup(s->str);
	g_string_free(s, TRUE);
	return ret;
}


static LmSSLResponse ssl_cb(LmSSL *ssl, LmSSLStatus status, gpointer ud)
{
	print_msg(D_DEBUG, "SSL status: %d", status);

	switch (status) {
		case LM_SSL_STATUS_NO_CERT_FOUND:
			print_msg(D_ERR, "No certificate found!");
			break;
		case LM_SSL_STATUS_UNTRUSTED_CERT:
			print_msg(D_ERR, "Certificate is not trusted!");
			break;
		case LM_SSL_STATUS_CERT_EXPIRED:
			print_msg(D_ERR, "Certificate has expired!");
			break;
		case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
			print_msg(D_ERR, "Certificate has not been activated!");
			break;
		case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
			print_msg(D_ERR, "Certificate hostname does not match expected hostname!");
			break;
		case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
			{
				const char *fpr = lm_ssl_get_fingerprint(ssl);
				gchar *str;
				print_msg(D_ERR, "Certificate fingerprint does not match expected fingerprint!");
				str = print_finger(fpr, 16);
				print_msg(D_ERR, "Remote fingerprint:    %s", str);
				g_free(str);
				print_finger(expected_fingerprint, 16);
				print_msg(D_ERR, "Expected fingerprint:  %s", str);
				g_free(str);
				break;
			}
		case LM_SSL_STATUS_GENERIC_ERROR:
			print_msg(D_ERR, "Generic SSL error!");
			break;
	}

	return LM_SSL_RESPONSE_CONTINUE;
}


static gboolean stdin_data_cb(GIOChannel *source, GIOCondition condition, gpointer data)
{
	GIOStatus ret;
	GError *err = NULL;
	const int msg_size = 1024;
	char msg[msg_size];
	gsize len;

	print_msg(D_DEBUG, "stdin_data_cb...");
	
	if (condition & G_IO_IN) {
		/* read data */
		ret = g_io_channel_read_chars (source, msg, msg_size - 1, &len, &err);
		msg[len] = 0;
		if (ret == G_IO_STATUS_ERROR) {
			print_msg(D_DEBUG, "reading: %s", err->message);
			g_main_loop_quit(main_loop);
			return 0;
		}

		/* send message */
		print_msg(D_DEBUG, "Read %u bytes: %s", len, msg);
		process_stdin(msg, (LmConnection *)data);
	}
		
	/* check EOF and HUP */
	if (condition & G_IO_HUP || condition & G_IO_ERR) {
		/* check EOF */
		if (ret == G_IO_STATUS_EOF) {
			print_msg(D_VERBOSE, "EOF from stdin.");
			g_main_loop_quit(main_loop);
			return 0;
		}

	}

	/* bye */
	return 1;
}


static void connection_auth_cb(LmConnection *connection, gboolean success, gpointer user_data)
{
	MESSAGE *msg = NULL;
	
	if (success) {
		GError *error = NULL;
		LmMessage *m;
		LmMessageNode *muc_node, *hist_node;

		print_msg(D_VERBOSE, "Authenticated successfully.");

		/* initialize stdin channel */
		stdin_channel = g_io_channel_unix_new(0);
		g_io_channel_set_encoding(stdin_channel, NULL, NULL);
		print_msg(D_DEBUG, "stdin encoding: %s", g_io_channel_get_encoding(stdin_channel));
		g_io_channel_set_flags(stdin_channel, G_IO_FLAG_NONBLOCK | g_io_channel_get_flags(stdin_channel), NULL);
		stdin_channel_tag = g_io_add_watch(stdin_channel, G_IO_IN | G_IO_HUP | G_IO_ERR, stdin_data_cb, connection);

		/* send initial presence */
		/*
		m = lm_message_new(NULL, LM_MESSAGE_TYPE_PRESENCE);
		if (!lm_connection_send(connection, m, &error)) {
			print_msg(D_ERR, "Failed to send initial presence: '%s'", lm_message_node_to_string(m->node));
		} else {
			print_msg(D_VERBOSE, "Sent initial presence message.");
		}
		lm_message_unref(m);
		*/

		/* send presence to MUC */
		m = lm_message_new(NULL, LM_MESSAGE_TYPE_PRESENCE);
		lm_message_node_set_attribute(m->node, "from", j_from_complete);
		lm_message_node_set_attribute(m->node, "to", j_room_complete);
		lm_message_node_add_child(m->node, "show", "online");
		lm_message_node_add_child(m->node, "status", "");
		muc_node = lm_message_node_add_child(m->node, "x", NULL);
		lm_message_node_set_attribute(muc_node, "xmlns", "http://jabber.org/protocol/muc");
		hist_node = lm_message_node_add_child(muc_node, "history", NULL);
		lm_message_node_set_attribute(hist_node, "since", "20070218T18:43:30");
		if (j_room_passwd) {
			lm_message_node_add_child(muc_node, "password", j_room_passwd);
		}
		if (!lm_connection_send(connection, m, &error)) {
			print_msg(D_ERR, "Failed to send MUC presence: '%s'", lm_message_node_to_string(m->node));
		} else {
			print_msg(D_VERBOSE, "Sent MUC presence message: '%s'", lm_message_node_to_string(m->node));
		}
		lm_message_unref(m);
		lm_message_node_unref(muc_node);
		lm_message_node_unref(hist_node);

		/* send notification to GUI */
		msg = message_create('N', 'S');
		message_append(msg, MESSAGE_STRING, "Son", MESSAGE_END);
		message_to_file(msg, stdout, 0);
		message_destroy(msg);
		
		print_msg(D_NORMAL, "Initialization complete.");
	} else {
		print_msg(D_ERR, "Failed to authenticate!");
	}
}


static void connection_open_cb(LmConnection *connection, gboolean success, gpointer user_data)
{
	if (success) {
		gchar *user;

		/* connected -- send authentication */
		user = get_part_name(j_username);
		if (!lm_connection_authenticate(connection, user, j_password, j_resource, connection_auth_cb, NULL, FALSE, NULL)) {
			g_free(user);
			print_msg(D_ERR, "Failed to send authentication!");
			g_main_loop_quit(main_loop);
			return;
		}
		g_free(user);

		print_msg(D_VERBOSE, "Sent authentication message.");
	} else {
		/* connection failed */
		print_msg(D_ERR, "Failed to connect!");
		g_main_loop_quit(main_loop);
	}
}


LmHandlerResult connection_presence_cb (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer user_data)
{
	LmMessageNode *error = NULL;
	MESSAGE *msg;
	const gchar *from, *type;
	char *nick;
	int player_quit;
	
	/* presence received -- show it */
	print_msg(D_VERBOSE, "Received Presence: %s", lm_message_node_to_string(message->node));

	/* check for errors */
	error = lm_message_node_get_child(message->node, "error");
	if (error) {
		print_msg(D_ERR, "Error received: %s (code %s)",
				lm_message_node_get_value(error),
				lm_message_node_get_attribute(error, "code"));
		lm_message_node_unref(error);
		/* TODO */
		exit(EXIT_FAILURE);
	}
		
	/* do something */
	from = lm_message_node_get_attribute(message->node, "from");
	type = lm_message_node_get_attribute(message->node, "type");

	if (from) {
		player_quit = type && strcmp(type, "unavailable") == 0;
		
		/* player join/part info */
		msg = message_create('N', 'S');
		message_append(msg, MESSAGE_STRING, player_quit ? "Pp":"Pj",
				MESSAGE_STRING, from,
				MESSAGE_END);
		message_to_file(msg, stdout, 0);
		message_destroy(msg);
		
		if (!player_quit && (nick = strchr(from, '/'))) {
			nick++;
			
			msg = message_create('N', 'S');
			message_append(msg, MESSAGE_STRING, "Pn",
					MESSAGE_STRING, from,
					MESSAGE_STRING, nick,
					MESSAGE_END);
			message_to_file(msg, stdout, 0);
			message_destroy(msg);
		}
	}
	

	return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}


LmHandlerResult connection_message_cb (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer user_data)
{
	LmMessageNode *error = NULL, *x = NULL;

	/* message received -- show it */
	print_msg(D_VERBOSE, "Received Message: %s", lm_message_node_to_string(message->node));

	error = lm_message_node_get_child(message->node, "error");

	if (error) {
		print_msg(D_ERR, "Error received: %s (code %s)",
				lm_message_node_get_value(error),
				lm_message_node_get_attribute(error, "code"));
		lm_message_node_unref(error);
		/* TODO */
		exit(EXIT_FAILURE);
	} else {
		x = lm_message_node_get_child(message->node, "x");
		if (!x || (strcmp(lm_message_node_get_attribute(x, "xmlns"), "jabber:x:delay") == 0
				&& strcmp(start_time, lm_message_node_get_attribute(x, "stamp"))) < 0)
		{
			process_message(message);
		} else {
			print_msg(D_DEBUG, "Message dropped.")
		}
	}

	return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}


int main(int argc, char **argv)
{
	GMainContext *main_context;
	GOptionContext *context;
	LmConnection *connection;
	LmMessageHandler *message_handler, *presence_handler;
	time_t t;
	struct tm *tmp;

	setlocale(LC_ALL, "");
	message_init();

	/* init start time yyyymmddThh:mm:ss */
	t = time(NULL);
	tmp = gmtime(&t);
	if (!tmp) {
		perror("gmtime");
		return EXIT_FAILURE;
	}
	if (strftime(start_time, sizeof(start_time), "%Y%m%dT%T", tmp) == 0) {
	       fprintf(stderr, "strftime returned 0");
	       exit(EXIT_FAILURE);
	}

	/* parse cmd line options */
	context = g_option_context_new("- test send message asynchronously");
	g_option_context_add_main_entries(context, entries, NULL);
	g_option_context_parse(context, &argc, &argv, NULL);
	g_option_context_free(context);

	if (!j_server || !j_username || !j_password || !j_room || !j_nick) {
		print_msg(D_ERR, "For usage, try %s --help", argv[0]);
		return EXIT_FAILURE;
	}

	if (j_username && strchr(j_username, '@') == NULL) {
		print_msg(D_ERR, "Username must have an '@' included");
		return EXIT_FAILURE;
	}

	/* set debug level */
	verbose = 1;
	if (quiet_flag)
		verbose = 0;
	if (verbose_flag)
		verbose = 2;
	if (debug_flag)
		verbose = 3;
	
	print_msg(D_VERBOSE, "Verbose level: %s (%d)", verbose_level_str[verbose], verbose);
	print_msg(D_VERBOSE, "Start time: %s", start_time);

	/* build j_*_complete */
	j_from_complete = g_strdup_printf("%s/%s", j_username, j_resource);
	j_room_complete = g_strdup_printf("%s/%s", j_room, j_nick);
	
	/* initialize main loop */
	main_context = g_main_context_default();
	connection = lm_connection_new_with_context(j_server, main_context);
	lm_connection_set_port(connection, j_port);
	lm_connection_set_jid(connection, j_username);

	/* initialize message handler */
	message_handler = lm_message_handler_new(connection_message_cb, NULL, NULL);
	lm_connection_register_message_handler(connection, message_handler, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_FIRST);

	/* initialize presence handler */
	presence_handler = lm_message_handler_new(connection_presence_cb, NULL, NULL);
	lm_connection_register_message_handler(connection, presence_handler, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_FIRST);

	/* check ssl fingerprint */
	if (j_fingerprint) {
		LmSSL *ssl;
		char *p;
		int i;

		if (j_port == LM_CONNECTION_DEFAULT_PORT) {
			lm_connection_set_port(connection, LM_CONNECTION_DEFAULT_PORT_SSL);
		}

		for (i = 0, p = j_fingerprint; *p && *(p + 1); i++, p += 3) {
			expected_fingerprint[i] = (unsigned char) g_ascii_strtoull(p, NULL, 16);
		}

		ssl = lm_ssl_new(expected_fingerprint, (LmSSLFunction) ssl_cb, NULL, NULL);

		lm_connection_set_ssl(connection, ssl);
		lm_ssl_unref(ssl);
	}

	/* open connection */
	print_msg(D_NORMAL, "Connecting to %s:%d ...", j_server, j_port);
	if (!lm_connection_open(connection, (LmResultFunction) connection_open_cb, NULL, NULL, NULL)) {
		print_msg(D_ERR, "Could not open a connection");
		return EXIT_FAILURE;
	}
	print_msg(D_VERBOSE, "Connection established.");

	/* run main loop */
	main_loop = g_main_loop_new(main_context, FALSE);
	g_main_loop_run(main_loop);

	/* say bye */
	print_msg(D_NORMAL, "Bye.");
	lm_connection_close(connection, NULL);

	return (test_success ? EXIT_SUCCESS : EXIT_FAILURE);
}

/* vim:set encoding=utf8: */
