/*
 *
 * netboard-jabber  --  network backend for netboard
 *
 * Copyright (C) 2006-2007 Josef Kufner <jk@myserver.cz>
 * 
 * Based on example from loudmouth documentation.
 * 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 <unistd.h>
#include <time.h>

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

/* global vars */
gchar *j_configure = NULL;
gchar *j_profile = NULL;
gint verbose = D_NORMAL;
gchar *j_server = NULL;
gint j_port = 0;
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;
int j_use_ssl = 0, j_lax_ssl = 0;
gchar *j_fingerprint = NULL;
gchar *j_dbg_level_string = NULL;
gint verbose_flag = FALSE, quiet_flag = FALSE, debug_flag = FALSE;
gchar *j_room_complete, *j_from_complete;
gchar expected_fingerprint[20];

LmConnection *connection = NULL;
GMainContext *main_context = NULL;
GMainLoop *main_loop;

gchar start_time[32];

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

struct s_opt_query opt_query[] = {
	{ "server",		MESSAGE_STRING,		&j_server, 		0 },
	{ "port",		MESSAGE_INTEGER,	&j_port,		0 },
	{ "resource",		MESSAGE_STRING,		&j_resource,		0 },
	{ "nick",		MESSAGE_STRING,		&j_nick,		0 },
	{ "username",		MESSAGE_STRING,		&j_username,		0 },
	{ "password",		MESSAGE_STRING,		&j_password,		0 },
	{ "room_password",	MESSAGE_STRING,		&j_room_passwd,		0 },
	{ "use_ssl",		MESSAGE_INTEGER,	&j_use_ssl,		0 },
	{ "lax_ssl",		MESSAGE_INTEGER,	&j_lax_ssl,		0 },
	{ "fingerprint",	MESSAGE_STRING,		&j_fingerprint,		0 },
	{ NULL,			MESSAGE_END,		NULL,			0 }
};

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


static GIOChannel *stdin_channel;
static unsigned int stdin_channel_tag;

static GOptionEntry entries[] = {
	{"configure", 'C', 0, G_OPTION_ARG_STRING, &j_configure,
	 "Show configuration dialog only", NULL},
	{"profile", 'P', 0, G_OPTION_ARG_STRING, &j_profile,
	 "Load configuration from Net Board 2 options system", NULL},
	{"server", 's', 0, G_OPTION_ARG_STRING, &j_server,
	 "Server to connect to", NULL},
	{"port", 'o', 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", 'A', 0, G_OPTION_ARG_STRING, &j_room,
	 "Chat room to send the message to (e.g. user@server.org)", NULL},
	{"use-ssl", 'S', 0, G_OPTION_ARG_NONE, &j_fingerprint,
	 "Use SSL", NULL},
	{"lax-ssl", 'L', 0, G_OPTION_ARG_NONE, &j_lax_ssl,
	 "Every SSL error is ignored.", 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}
};


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);
}


gchar *print_finger(const gchar *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 void send_configuration_dialog (void)
{
	MESSAGE *msg;

	/* send defaults */
	msg = message_create(0, 0);
	message_append(msg, MESSAGE_STRING, "Oa",
			MESSAGE_STRING, "network", MESSAGE_STRING, j_configure,
			MESSAGE_STRING, "port", MESSAGE_INTEGER, 5223,
			MESSAGE_END);
	message_to_file(msg, stdout, 1);
	message_destroy(msg);
	
	msg = message_create(0, 0);
	message_append(msg, MESSAGE_STRING, "Oa",
			MESSAGE_STRING, "network", MESSAGE_STRING, j_configure,
			MESSAGE_STRING, "resource", MESSAGE_STRING, "netboard",
			MESSAGE_END);
	message_to_file(msg, stdout, 1);
	message_destroy(msg);
	
	msg = message_create(0, 0);
	message_append(msg, MESSAGE_STRING, "Oa",
			MESSAGE_STRING, "network", MESSAGE_STRING, j_configure,
			MESSAGE_STRING, "use_ssl", MESSAGE_INTEGER, 1,
			MESSAGE_END);
	message_to_file(msg, stdout, 1);
	message_destroy(msg);
	
	/* send dialog */
	msg = message_create(0, 0);
	message_append(msg, MESSAGE_STRING, "Odo",
			MESSAGE_STRING, "network",
			MESSAGE_STRING, j_configure,
			MESSAGE_STRING, PROG_NAME,
			MESSAGE_STRING, "[Připojení k serveru",
			MESSAGE_STRING, "$Jabber server",	MESSAGE_STRING, "server",
			MESSAGE_STRING, "#TCP port",		MESSAGE_STRING, "port",
								MESSAGE_INTEGER, 0,
								MESSAGE_INTEGER, 65535,
			MESSAGE_STRING, "$Jabber resource",	MESSAGE_STRING, "resource",
			MESSAGE_STRING, "[Autentizace",
			MESSAGE_STRING, "$Uživatelské jméno",	MESSAGE_STRING, "username",
			MESSAGE_STRING, "*Heslo uživatele",	MESSAGE_STRING, "password",
			MESSAGE_STRING, "=Uživatelské jméno má být ve tvaru <tt>user@server.tld</tt>. ",
			MESSAGE_STRING, "[Šifrování",
			MESSAGE_STRING, "?Použít SSL",		MESSAGE_STRING, "use_ssl",
			MESSAGE_STRING, "?Ignorovat všechny chyby SSL",	MESSAGE_STRING, "lax_ssl",
			MESSAGE_STRING, "$SSL Fingerprint",	MESSAGE_STRING, "fingerprint",
			MESSAGE_STRING, "=Pokud uvedete fingerprint SSL klíče, bude kontrolován při připojení.",
			MESSAGE_STRING, "[Místnost",
			MESSAGE_STRING, "$JID místnosti",	MESSAGE_STRING, "default_address",
			MESSAGE_STRING, "$Přezdívka uživatele",	MESSAGE_STRING, "nick",
			MESSAGE_STRING, "*Heslo do místnosti",	MESSAGE_STRING, "room_password",
			MESSAGE_END);
	message_to_file(msg, stdout, 1);
	message_destroy(msg);
}


static void query_configuration (char *cat, char *mod)
{
	MESSAGE *msg = NULL;
	struct s_opt_query *p_opt = NULL;

	for (p_opt = opt_query; p_opt->opt; p_opt++) {
		msg = message_create(0, 0);
		message_append(msg, MESSAGE_STRING, "Og",
				MESSAGE_STRING, cat,
				MESSAGE_STRING, mod,
				MESSAGE_STRING, p_opt->opt,
				MESSAGE_END);
		message_to_file(msg, stdout, 1);
		message_destroy(msg);
	}

}


void open_connection(void) {
	LmMessageHandler *message_handler, *presence_handler;
	time_t t;
	struct tm *tmp;

	/* check */
	if (j_username && strchr(j_username, '@') == NULL) {
		print_msg(D_ERR, "Username must have an '@' included");
		exit(EXIT_FAILURE);
	}
	
	/* init start time yyyymmddThh:mm:ss */
	t = time(NULL);
	tmp = gmtime(&t);
	if (!tmp) {
		perror("gmtime");
		exit(EXIT_FAILURE);
	}
	if (strftime(start_time, sizeof(start_time), "%Y%m%dT%T", tmp) == 0) {
		fprintf(stderr, "strftime returned 0");
		exit(EXIT_FAILURE);
	}
	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 LM */
	connection = lm_connection_new_with_context(j_server, main_context);
	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_use_ssl) {
		LmSSL *ssl;
		char *p;
		int i;

		print_msg(D_VERBOSE, "Initializing SSL ...");

		if (j_port == 0) {
			j_port = LM_CONNECTION_DEFAULT_PORT_SSL;
		}

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

		ssl = lm_ssl_new(j_fingerprint && j_fingerprint[0] ? expected_fingerprint : NULL, (LmSSLFunction) ssl_cb, NULL, NULL);

		lm_connection_set_ssl(connection, ssl);
		lm_ssl_unref(ssl);
	} 
	if (j_port == 0) {
		j_port = LM_CONNECTION_DEFAULT_PORT;
	}
	lm_connection_set_port(connection, j_port);

	/* 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");
		exit(EXIT_FAILURE);
	}
	print_msg(D_VERBOSE, "Connection established.");

}


void m_send(LmMessage *m, LmConnection *connection)
{
	static GTimer *timer = NULL;
	const gdouble minimal_delay = 0.35;

	GError *error = NULL;
	gdouble since_last_msg;

	/* prepare anti-flood timer */
	if (!timer)
		timer = g_timer_new();

	/* flood protection */
	since_last_msg = g_timer_elapsed(timer, NULL);
	if (since_last_msg < minimal_delay) {
		usleep((unsigned long)((minimal_delay - since_last_msg) * 1e6));
	}
	g_timer_start(timer);
		
	/* send message */
	if (!lm_connection_send(connection, m, &error)) {
		print_msg(D_ERR, "Failed to send message: '%s'", lm_message_node_to_string(m->node));
		exit(EXIT_FAILURE);
	} else {
		print_msg(D_VERBOSE, "Sent message: '%s'", lm_message_node_to_string(m->node));
	}
}


int main(int argc, char **argv)
{
	GMainContext *main_context = NULL;
	GOptionContext *context = NULL;
	int i;

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

	fprintf(stderr, "Command line:");
	for (i = 0; argv[i]; i++) {
		fprintf(stderr, " %s", argv[i]);
	}
	fprintf(stderr, "\n");

	/* parse cmd line options */
	context = g_option_context_new("- Jabber backend for Netboard 2");
	g_option_context_add_main_entries(context, entries, NULL);
	g_option_context_parse(context, &argc, &argv, NULL);
	g_option_context_free(context);

	if (j_configure) {
		send_configuration_dialog();
		return 0;
	}
	
	if (!(j_profile && j_room) && !(j_server && j_username && j_password && j_room && j_nick)) {
		print_msg(D_ERR, "For usage, try %s --help", argv[0]);
		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);

	/* query configuration from netboard */
	if (j_profile) {
		query_configuration("network", j_profile);
	}
	
	/* initialize main loop */
	main_context = g_main_context_default();

	/* open connection if no profile is given */
	if (!j_profile)
		open_connection();
	
	/* 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, NULL);

	/* 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: */
