/*
 * Implementation file for a Brutus sample and test application.
 * Copyright (C) 2004-2007 OMC Denmark ApS.
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 program. If not, see <http://www.gnu.org/licenses/>.
 */

// Test system includes
#include <brutus_utils/brutus_macros.h>
#include <config/config.h>
#include <bresult/bresult.h>
#include <init_orb/init_orb.h>
#include <brutus_utils/brutus_utils.h>

// System includes
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <orbit/orbit.h>

// Brutus includes
#include <idl_output/BrutusLogOn.h>
#include <idl_output/IMAPISession.h>
#include <idl_output/IMAPIFolder.h>

// Lorica
#include <idl_output/ReferenceMapper.h>

#include <servant_impl/types_impl.c>
#include <servant_impl/BrutusCheck_impl.c>
#include <servant_impl/IMAPIAdviseSink_impl.c>

// Defines
#define CONFIG_FILE "../../config/client.conf"
#define FLUSH() do { fflush(NULL); } while (0)


// The global ORB object
static CORBA_ORB global_orb = CORBA_OBJECT_NIL;
static PortableServer_POA root_poa = CORBA_OBJECT_NIL;	/* root POA */

// gloabl to make signal handling kill a notification thread
static volatile int global_notice = 0;

// Multithread conditionals
static GCond* proceed_orb_ok = NULL;
static GCond* proceed_orb_destroy = NULL;
static GCond* terminate = NULL;
static GMutex* proceed_orb_ok_mutex = NULL;
static GMutex* proceed_orb_destroy_mutex = NULL;
static GMutex* term_mutex = NULL;
static gboolean corba_ready = FALSE;
static gboolean orb_destroy = FALSE;
static gboolean shutting_down = FALSE;

// Arguments to ORB_run() thread
struct orb_thread_arg {
	char *argv0;
	const unsigned char ssl;
	const char *logon_obj_server_key;
	const char *brutus_server;
	const uint16_t brutus_port;
	PortableServer_POA *root_poa;
};


// Arguments to notification thread
struct notification_thread_arg {
	volatile int *notice;
	CORBA_unsigned_long mask;
	CORBA_unsigned_long connection;
	BRUTUS_IMsgStore msg_store;
	PortableServer_POA root_poa;
	BRUTUS_IMAPIAdviseSink sink;
};

static void
print_ref(const char *desc,
	  CORBA_Object obj)
{
	CORBA_string ior = NULL;
	CORBA_Environment ev[1];

	CORBA_exception_init(ev);

	ior = CORBA_ORB_object_to_string(global_orb, obj, ev);
	g_print("\n%s\n%s\n", desc, ior);

	if (ior)
		CORBA_free(ior);
	CORBA_exception_free(ev);
}

void
signal_handler(int signal)
{
	global_notice = 1;
}

// shutdown global ORB
static void
main_shutdown(int sig)
{
	CORBA_Environment ev[1];
	CORBA_exception_init(ev);

	// kill any pending notification threads
	global_notice = 1;

	if (CORBA_OBJECT_NIL != global_orb) {
		CORBA_ORB_shutdown(global_orb, TRUE, ev);
		if (ORBIT2_EX(ev))
			ORBIT2_PRINT_EX("CORBA_ORB_shutdown() : ", ev);
	}

	// signal to the main thread that the ORB is shut down
	g_mutex_lock(term_mutex);
	shutting_down = TRUE;
	g_cond_signal(terminate);
	g_mutex_unlock(term_mutex);

	CORBA_exception_free(ev);
}


// ORB run() thread. Must call ORB_init()!!
static gpointer
orb_run_thread_func(gpointer data)
{
	CORBA_Environment ev[1];
	CORBA_exception_init(ev);

	g_print("    Invoking ORB_Init().. ");
	{
		// dummy argv[0]
		char **argv0 = &((struct orb_thread_arg*)data)->argv0;
		global_orb = create_orbit_orb(1,
					      argv0,
					      ((struct orb_thread_arg*)data)->ssl,
					      "BrutusLogOn",
					      ((struct orb_thread_arg*)data)->logon_obj_server_key,
					      ((struct orb_thread_arg*)data)->brutus_server,
					      ((struct orb_thread_arg*)data)->brutus_port,
					      "Brutus-Client-ORB_orbit-io-thread",
					      ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("create_orbit_orb() : ", ev);
			goto out_no_orb;
		}
		if (CORBA_Object_is_nil((CORBA_Object)global_orb, ev)) {
			g_print("ERROR - could not initialize the ORB\n");
			goto out_no_orb;
		}

		g_print("OK\n");
	}

	g_print("    Getting root POA.. ");
	{
		*((struct orb_thread_arg*)data)->root_poa = get_root_poa(global_orb, ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("get_root_poa() : ", ev);
			goto out;
		}
		if (CORBA_Object_is_nil((CORBA_Object) *((struct orb_thread_arg*)data)->root_poa, ev)) {
			g_print("ERROR - could not get the root POA\n");
			goto out;
		}
		g_print("OK\n");
	}

	// signal to the main thread that the ORB is initiated and the Root POA aquired
	g_mutex_lock(proceed_orb_ok_mutex);
	corba_ready = TRUE;
	g_cond_signal(proceed_orb_ok);
	g_mutex_unlock(proceed_orb_ok_mutex);

	CORBA_ORB_run(global_orb, ev);
	if (ORBIT2_EX(ev)) {
		ORBIT2_PRINT_EX("CORBA_ORB_run() : ", ev);
		goto out;
	}

out:
	// wait for destroy signal
	g_mutex_lock(proceed_orb_destroy_mutex);
	while (!orb_destroy)
		g_cond_wait(proceed_orb_destroy, proceed_orb_destroy_mutex);
	g_mutex_unlock(proceed_orb_destroy_mutex);

	// destroy global ORB
	CORBA_ORB_destroy(global_orb, ev);
	if (ORBIT2_EX(ev))
		ORBIT2_PRINT_EX("ORB_destroy() : ", ev);

	CORBA_Object_release((CORBA_Object)global_orb, ev);
	if (ORBIT2_EX(ev))
		ORBIT2_PRINT_EX("Object_release() : ", ev);

out_no_orb:
	CORBA_exception_free(ev);

	return NULL;
}

// thread entry class
static gpointer
notification_thread_func(gpointer data)
{
	CORBA_Environment ev[1];
	CORBA_exception_init(ev);

	struct notification_thread_arg *args = (struct notification_thread_arg*)data;

	// notify on all contained objects
	BRUTUS_ENTRYID *eid = BRUTUS_ENTRYID__alloc();

	CORBA_unsigned_long n;
	for (n=0; n<4; n++)
		eid->abFlags[n] = 0;
	eid->ab._maximum = 0;
	eid->ab._length = 0;
	eid->ab._buffer = NULL;

	BRUTUS_BRESULT br = BRUTUS_IMsgStore_Advise(args->msg_store,
						    eid,
						    args->mask,
						    args->sink,
						    &args->connection,
						    ev);
	CORBA_free(eid);
	if (BRUTUS_BRUTUS_S_OK != br) {
		g_print("ERROR - advise() returned %s\n", bresult_to_str(br));
		goto out;
	}

	while (!(*args->notice)) {
		g_print(".");
		FLUSH();
	}

out:
	CORBA_Object_release((CORBA_Object)args->msg_store, ev);
	CORBA_Object_release((CORBA_Object)args->root_poa, ev);

	return NULL;
};


// will realloc and strcat src to *dest, freeing *dest if
// not successfull. Returns a pointer to the longer string.
static inline char*
append_string(char **dest, const char *src)
{
	char *retv;
	size_t size;


	if (!*dest) {
		size = sizeof(char) * (strlen(src) + 1);
		retv = (char*)calloc(size, sizeof(char));
	} else {
		size = sizeof(char) * (strlen(*dest) + strlen(src) + 1);
		retv = (char*)realloc((void*)*dest, size);
	}
	if (!retv) {
		free(*dest);
		return NULL;
	}

	strcat(retv, src);
	*dest = retv;

	return *dest;
}


/*
 * Will return a pointer to a properly initialized
 * Config object or NULL on error.
 */
static struct Config*
get_config(const char *config_file,
	   const char *stack_token)
{
	struct Config *retv = (struct Config*)calloc(1, sizeof(struct Config));
	if (!retv) {
		g_print("No memory\n");
		FLUSH();
		goto err_out;
	}
	retv->config_file = config_file;
	retv->stack_token = stack_token;

	// load configuration
	int load_err = init_config(retv);
	if (load_err) {
		char msg[128] = {'\0'};
		if (load_err > 0)
			snprintf(msg, 128, "Configuration error: Error in config file at line %d\n", load_err);
		else
			snprintf(msg, 128, "Configuration error: Could not open %s\n", config_file);

		g_print(msg);
		goto err_out;
	}

	return retv;

err_out:
	free(retv);

	return NULL;
}


/*
 * Will check a string for conversion into an "bits" bit wide
 * unsigned integer number. Returns non-zero on error.
 */
static int
get_xbit_number(const unsigned char bits, const char *num_str, unsigned long *ul)
{
	unsigned long max;

	switch (bits) {
	case 8 :
		max = 0xff;
		break;
	case 16 :
		max = 0xffff;
		break;
	case 32 :
		max = 0xffffffff;
		break;
	default :
		return -1;
	}

	if (!num_str)
		return -1;

	if (*num_str == '-')
		return -1;

	char *tmp;
	unsigned long num = strtoul(num_str, &tmp, 10);
	if (errno == ERANGE)
		return -1;

	if (*tmp != '\0')
		return -1;

	if (num > max)
		return -1;

	*ul = (unsigned long)num;

	return 0;
}

static Lorica_ReferenceMapper
get_lorica(CORBA_ORB orb)
{
	CORBA_char *corbaloc_str[512] = { '\0' };
        CORBA_boolean cb = CORBA_TRUE;
        Lorica_ReferenceMapper mapper = CORBA_OBJECT_NIL;
	CORBA_Environment ev[1];
	
	CORBA_exception_init(ev);

	snprintf((char*)corbaloc_str,
		 sizeof(corbaloc_str), 
		 "corbaloc:iiop:1.2@localhost:%u/%s",
		 Lorica_ReferenceMapper_lorica_in_port, 
		 Lorica_ReferenceMapper_IOR_TABLE_KEY);
        mapper = (Lorica_ReferenceMapper)CORBA_ORB_string_to_object(orb, (CORBA_char*)corbaloc_str, ev);
        if (ORBIT2_EX(ev)) 
                goto no_lorica;

        cb = CORBA_Object_is_nil((CORBA_Object)mapper, ev);
        if (ORBIT2_EX(ev)) 
		goto no_lorica;
	if (cb)
		goto no_lorica;
	
	cb = CORBA_Object_non_existent((CORBA_Object)mapper, ev);
	if (ORBIT2_EX(ev)) 
		goto no_lorica;
	if (cb)
		goto no_lorica;

	goto lorica_ok;
	
no_lorica:
	CORBA_exception_free(ev);	
        return CORBA_OBJECT_NIL;
	
lorica_ok:
	CORBA_exception_free(ev);	
        return mapper;
}

static void
print_legalese(const char *prog_name)
{
	g_print("\n");
	g_print("                        * DISCLAIMER START *\n");
	g_print("\n");
	g_print("  * %s - A sample program that connects to a Brutus server.\n", prog_name);
	g_print("  * Copyright (C) 2004-2007 OMC Denmark ApS\n");
	g_print("  * \n");
	g_print("  * This program is free software; you can redistribute it and/or modify\n");
	g_print("  * it under the terms of the GNU General Public License as published by\n");
	g_print("  * the Free Software Foundation; either version 3 of the License, or\n");
	g_print("  * (at your option) any later version.\n");
	g_print("  * \n");
	g_print("  * This program is distributed in the hope that it will be useful,\n");
	g_print("  * but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
	g_print("  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
	g_print("  * GNU General Public License for more details.\n");
	g_print("  * \n");
	g_print("  * You should have received a copy of the GNU General Public License\n");
	g_print("  * along with this program;  If not, see <http://www.gnu.org/licenses/>\n");
	g_print("\n");
	g_print("                        * DISCLAIMER END *\n");
	g_print("\n");
}

/*
 * All local variables has been defined at the start of the
 * function. I would normally prefer to postpone the declarations
 * until they are actually needed, but my use of goto's (which would
 * predate some declarations) is making that approach sub-optimal.
 */
int
main(int argc, char **argv)
{
	// initiate signal handling
	signal(SIGTERM, main_shutdown);
	signal(SIGINT, signal_handler);

	int retv = 1;

	// Configuration
	unsigned char use_ssl; // if false use iiop
	unsigned char use_corbaloc; // if false use resolve_initial_references()
	unsigned char notifications; // establish an advise sink if true
	char *logon_obj_server_key = NULL;
	char *brutus_logon_corbaloc_str = NULL;
	char *brutus_server = NULL;
	char *profile = NULL;
	char *password = NULL;
	char *exch_server = NULL;
	char *exch_mailbox = NULL;
	char *windows_user = NULL;
	char *windows_user_domain = NULL;
	char *windows_user_password = NULL;
	uint16_t brutus_iiop_port = 0;
	uint16_t brutus_ssl_port = 0;

	// Configuration file helper class
	struct Config *conf = NULL;

	BRUTUS_BRESULT br = BRUTUS_BRUTUS_UNKNOWN_ERROR;
	CORBA_boolean bool = CORBA_FALSE;

	// Corba
	CORBA_Environment ev[1];
	CORBA_ORB orb = CORBA_OBJECT_NIL;
	PortableServer_POAManager root_poa_manager = CORBA_OBJECT_NIL;

	// Pure Brutus objects with no MAPI counterpart
	BRUTUS_BrutusLogOn brutus_logon = CORBA_OBJECT_NIL;
	BRUTUS_BrutusCheck brutus_check = CORBA_OBJECT_NIL;
	CORBA_unsigned_long instance_id = 0;
	BRUTUS_SERVER_VERSION server_version = { 0 };
	BRUTUS_BrutusLogOn_BrutusVersion brutus_server_version = {
		.major = 0,
		.minor = 0,
		.micro = 0,
		.release_tag = 0
	};

	// Brutus objects with a MAPI counterpart
	BRUTUS_IMAPISession mapi_session = CORBA_OBJECT_NIL;
	BRUTUS_IMsgStore msg_store = CORBA_OBJECT_NIL;
	BRUTUS_IMAPIFolder mapi_folder = CORBA_OBJECT_NIL;
	BRUTUS_IAddrBook addr_book = CORBA_OBJECT_NIL;
	BRUTUS_IABContainer mapi_book = CORBA_OBJECT_NIL;
	struct notification_thread_arg notify_args = {
		.notice = &global_notice,
		.mask = 0,
		.connection = 0,
		.msg_store = CORBA_OBJECT_NIL,
		.root_poa = CORBA_OBJECT_NIL,
	};

	// threads
	GThread *orb_thread = NULL;
	GThread *notify_thread = NULL;

	CORBA_exception_init(ev);

	if (!g_thread_supported())
		g_thread_init(NULL);

	// mutexes
	proceed_orb_ok_mutex = g_mutex_new(); // controls access to proceed_orb_ok condtional after ORB_init()
	proceed_orb_destroy_mutex = g_mutex_new(); // controls access to proceed_orb_destroyed condtional after ORB_shutdown()
	term_mutex = g_mutex_new(); // controls access to terminate conditional

	// conditionals
	proceed_orb_ok = g_cond_new();
	proceed_orb_destroy = g_cond_new();
	terminate = g_cond_new();
	corba_ready = FALSE;   // TRUE after successful ORB_init()
	orb_destroy = FALSE;   // TRUE when we are go for ORB_destroy()
	shutting_down = FALSE; // TRUE when shutting down


	// Print license information
	print_legalese(argv[0]);

	g_print("\nConfiguration file is \"%s\"\n", CONFIG_FILE);
	g_print("    Testing load of configuration file.. ");
	{
		conf = get_config(CONFIG_FILE, NULL);
		if (!conf) {
			g_print("ERROR\n");
			goto out;
		}
		g_print("OK\n");
	}

	g_print("\nParsing configuration file.. \n");
	g_print("    Use SSL.. ");
	{
		char *use_ssl_str = get_config_value("USE_SSL", conf);
		if ((use_ssl_str) && (!strcmp(use_ssl_str, "yes"))) {
			g_print("YES\n");
			use_ssl = 1;
		} else {
			g_print("NO\n");
			use_ssl = 0;
		}
		free(use_ssl_str);
	}

	g_print("    Getting BrutusLogOn server object key.. ");
	{
		logon_obj_server_key = get_config_value("BRUTUS_LOGON_OBJECT_SERVER_KEY", conf);
		if (logon_obj_server_key) {
			g_print("OK, \"%s\"\n", logon_obj_server_key);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting Brutus server address.. ");
	{
		brutus_server = get_config_value("BRUTUS_SERVER", conf);
		if (brutus_server) {
			g_print("OK, \"%s\"\n", brutus_server);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting Brutus server SSL port.. ");
	{
		char *ssl_port = get_config_value("BRUTUS_SERVER_SSL_PORT", conf);
		unsigned long n;
		if (ssl_port) {
			if (!get_xbit_number(16, ssl_port, &n)) {
				brutus_ssl_port = (uint16_t)n;
				g_print("OK, \"%hu\"\n", brutus_ssl_port);
			} else {
				free(ssl_port);
				g_print("CONVERSION ERROR\n");
				goto out;
			}
			free(ssl_port);
		} else {
			g_print("not defined\n");
		}
	}

	g_print("    Getting Brutus server IIOP port.. ");
	{
		char *iiop_port = get_config_value("BRUTUS_SERVER_IIOP_PORT", conf);
		unsigned long n;
		if (iiop_port) {
			if (!get_xbit_number(16, iiop_port, &n)) {
				brutus_iiop_port = (uint16_t)n;
				g_print("OK, \"%hu\"\n", brutus_iiop_port);
			} else {
				free(iiop_port);
				g_print("CONVERSION ERROR\n");
				goto out;
			}
			free(iiop_port);
		} else {
			g_print("not defined\n");
		}
	}

	g_print("        Sanity checking Brutus server ports.. ");
	{
		if (use_ssl) {
			if (!brutus_ssl_port) {
				g_print("ERROR - No SSL port defined\n");
				goto out;
			}
		} else {
			if (!brutus_iiop_port) {
				g_print("ERROR - No IIOP port defined\n");
				goto out;
			}
		}
		if (!brutus_ssl_port && !brutus_iiop_port) {
			g_print("ERROR - No ports defined\n");
			goto out;
		}
		g_print("OK, using port %hu\n", use_ssl ? brutus_ssl_port : brutus_iiop_port);
	}

	g_print("    Getting MAPI Profile name.. ");
	{
		profile = get_config_value("MAPI_PROFILE", conf);
		if (profile) {
			g_print("OK, \"%s\"\n", profile);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting MAPI Profile password.. ");
	{
		password = get_config_value("MAPI_PROFILE_PASSWORD", conf);
		if (password) {
			g_print("OK, \"%s\"\n", password);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting Exchange server.. ");
	{
		exch_server = get_config_value("EXCHANGE_SERVER", conf);
		if (exch_server) {
			g_print("OK, \"%s\"\n", exch_server);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting Exchange mailbox.. ");
	{
		exch_mailbox = get_config_value("EXCHANGE_MAILBOX", conf);
		if (exch_mailbox) {
			g_print("OK, \"%s\"\n", exch_mailbox);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting Windows user.. ");
	{
		windows_user = get_config_value("WINDOWS_USER", conf);
		if (windows_user) {
			g_print("OK, \"%s\"\n", windows_user);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting Windows user domain.. ");
	{
		windows_user_domain = get_config_value("WINDOWS_USER_DOMAIN", conf);
		if (windows_user_domain) {
			g_print("OK, \"%s\"\n", windows_user_domain);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Getting Windows user password.. ");
	{
		windows_user_password = get_config_value("WINDOWS_USER_PASSWORD", conf);
		if (windows_user_password) {
			g_print("OK, \"%s\"\n", windows_user_password);
		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Choosing initial bootstrap method.. ");
	{
		char *use_corbaloc_str = get_config_value("USE_CORBALOC_BOOTSTRAP", conf);
		if ((use_corbaloc_str) && (!strcmp(use_corbaloc_str, "yes"))) {
			g_print("corbaloc\n");
			use_corbaloc = 1;
		} else {
			g_print("resolve_initial_references()\n");
			use_corbaloc = 0;
		}
		free(use_corbaloc_str);
	}
	if (use_corbaloc) {
		g_print("    Composing corbaloc strings.. ");

		char brutus_port[32] = { '\0' };
		snprintf(brutus_port, sizeof(brutus_port), "%hu", use_ssl ? brutus_ssl_port : brutus_iiop_port);

		append_string(&brutus_logon_corbaloc_str, use_ssl ? "corbaloc:ssliop:" : "corbaloc:iiop:1.2@");
		append_string(&brutus_logon_corbaloc_str, (const char*)brutus_server);
		append_string(&brutus_logon_corbaloc_str, ":");
		append_string(&brutus_logon_corbaloc_str, (const char*)brutus_port);
		append_string(&brutus_logon_corbaloc_str, "/");
		append_string(&brutus_logon_corbaloc_str, (const char*)logon_obj_server_key);

		if (brutus_logon_corbaloc_str) {
			g_print("OK.\n");
			g_print("        BrutusLogOn corbaloc string   : \"%s\"\n", brutus_logon_corbaloc_str);

		} else {
			g_print("ERROR\n");
			goto out;
		}
	}

	g_print("    Test notifications.. ");
	{
		char *notify_str = get_config_value("ADVISE_SINKS", conf);
		if ((notify_str) && (!strcmp(notify_str, "yes"))) {
			g_print("YES\n");
			notifications = 1;
		} else {
			g_print("NO\n");
			notifications = 0;
		}
		free(notify_str);
	}

	g_print("\nInitializing CORBA.. \n");
	{

		struct orb_thread_arg args = {
			.argv0 = argv[0],
			.ssl = use_ssl,
			.logon_obj_server_key = logon_obj_server_key,
			.brutus_server = brutus_server,
			.brutus_port = use_ssl ? brutus_ssl_port : brutus_iiop_port,
			.root_poa = &root_poa,
		};

		orb_thread = g_thread_create((GThreadFunc)orb_run_thread_func,
					     (gpointer)&args,
					     TRUE,
					     NULL);

		// wait for the ORB to initialize
		g_mutex_lock(proceed_orb_ok_mutex);
		while (!corba_ready)
			g_cond_wait(proceed_orb_ok, proceed_orb_ok_mutex);
		g_mutex_unlock(proceed_orb_ok_mutex);

		orb = (CORBA_ORB)CORBA_Object_duplicate((CORBA_Object)global_orb, ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("CORBA_Object_duplicate() : ", ev);
			goto out;
		}
	}

	g_print("    Getting root POAManager.. ");
	{
		root_poa_manager = PortableServer_POA__get_the_POAManager(root_poa, ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("get_the_POAManager() : ", ev);
			goto out;
		}
		if (CORBA_Object_is_nil((CORBA_Object)root_poa_manager, ev)) {
			g_print("ERROR - could not get the root POAManager\n");
			goto out;
		}
		PortableServer_POAManager_activate(root_poa_manager, ev);

		CORBA_Object_release((CORBA_Object)root_poa_manager, ev);

		g_print("OK\n");
	}

	if (!(ORBit_proto_use("IPv4") || ORBit_proto_use("IPv6"))) {
		g_print("\nNo IPv4 or IPv6 support in ORBit2.. \n");
		exit(0);
	} else
		g_print("\nIPv4 or IPv6 is supported in ORBit2.. \n");

	g_print("\nBootstrapping Brutus.. \n");
	g_print("    Getting reference to BrutusLogOn object.. ");
	{
		/*
		 * Get a reference to the BrutusLogOn object.
		 *
		 * Use can use the resolve_initial_references() method
		 * or a corbaloc stringified object reference.
		 *
		 * I'll really recommend the former method as it is the
		 * standard way of bootstrapping a corba server, but you
		 * may be unfortunate enough to be using an ORB where
		 * this method is unavailable.
		 */
		CORBA_Object obj = CORBA_OBJECT_NIL;
		g_print("\n        resolving.. ");
		if (use_corbaloc) {
			obj = CORBA_ORB_string_to_object(orb, brutus_logon_corbaloc_str, ev);
			if (ORBIT2_EX(ev)) {
				ORBIT2_PRINT_EX("string_to_object() : ", ev);
				goto out;
			}
		} else {
			obj = CORBA_ORB_resolve_initial_references(orb, "BrutusLogOn", ev);
			if (ORBIT2_EX(ev)) {
				ORBIT2_PRINT_EX("resolve_initial_references() : ", ev);
				goto out;
			}
		}
		if (CORBA_Object_is_nil(obj, ev)) {
			g_print("ERROR - Could not resolve BrutusLogOn, NIL object returned\n");
			goto out;
		}

		brutus_logon = (BRUTUS_BrutusLogOn)CORBA_Object_duplicate(obj, ev);
		CORBA_Object_release(obj, ev);

		g_print("OK\n");
	}

	g_print("\nRegistering in Lorica.. ");
	{
		int tries = 3;
		BRUTUS_BrutusLogOn brutus_logon_mapped = CORBA_OBJECT_NIL;
		Lorica_ReferenceMapper lorica = get_lorica(global_orb);
		if (CORBA_Object_is_nil(lorica, ev)) {
			g_print("Not using Lorica\n");
			goto proceed;
		}

		if (use_corbaloc) {
			CORBA_Object_release(brutus_logon, ev);
		again:
			brutus_logon_mapped = Lorica_ReferenceMapper_as_client_with_corbaloc(lorica, 
											     brutus_logon_corbaloc_str, 
											     "IDL:omc.brutus/BRUTUS/BrutusLogOn:1.0",
											     ev);
			if (ORBIT2_EX(ev)) {
				ORBIT2_PRINT_EX("Lorica::ReferenceMapper::as_client_with_corbaloc() : ", ev);
				if (!tries) {
					g_print("\nCould not map in Lorica\n");
					goto out;
				}
				tries--;
				sleep(1);
				goto again;
			}
		} else {
			brutus_logon_mapped = Lorica_ReferenceMapper_as_client(lorica, brutus_logon, ev);
			if (ORBIT2_EX(ev)) {
				ORBIT2_PRINT_EX("Lorica::ReferenceMapper::as_client() : ", ev);
				goto out;
			}
			CORBA_Object_release(brutus_logon, ev);
		}
		CORBA_Object_release(lorica, ev);
		if (CORBA_Object_is_nil(brutus_logon_mapped, ev)) {
			g_print("Could not map BrutusLogOn as client\n");
			goto proceed;
		}

		brutus_logon = brutus_logon_mapped;

		g_print("OK\n");
	}
	proceed:

	g_print("\nStarting Brutus operations.. \n");
	g_print("    Getting Brutus Server version.. ");
	{
		BRUTUS_BrutusLogOn_GetVersion(brutus_logon, &brutus_server_version, ev);
		print_ref("BrutusLogOn reference:", brutus_logon);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("BrutusLogOn->GetVersion() : ", ev);
			goto out;
		}
		g_print("%d.%d.%d-%d ", brutus_server_version.major, brutus_server_version.minor, brutus_server_version.micro, brutus_server_version.release_tag);
		g_print("OK\n");
	}

	g_print("    Logging on using the MAPI profile.. ");
	{
		brutus_check = impl_BRUTUS_BrutusCheck__create(root_poa, ev);
		print_ref("BrutusCheck reference:", brutus_check);
		br = BRUTUS_BrutusLogOn_Logon(brutus_logon,
					      brutus_check,
					      profile,
					      "",
					      windows_user,
					      windows_user_domain,
					      windows_user_password,
					      exch_mailbox,
					      exch_server,
					      BRUTUS_BRUTUS_MAPI_EXPLICIT_PROFILE,
					      0,
					      BRUTUS_SERVER_TYPE_EXCHANGE, // or BRUTUS_SERVER_TYPE_DOMINO
					      &instance_id,
					      &mapi_session,
					      ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("BrutusLogOn->Logon() : ", ev);
			goto out;
		}
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - BrutusLogOn->Logon() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Pinging mailbox.. ");
	{
		print_ref("IMAPISession reference:", mapi_session);
		BRUTUS_IMAPISession_ping(mapi_session, ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("IMAPISession->ping() : ", ev);
			goto out;
		} else
			g_print("OK\n");
	}
	return EXIT_SUCCESS;

	g_print("    Getting Exchange server version.. ");
	{
		bool = BRUTUS_IMAPISession_GetServerVersion(mapi_session,
							    &server_version,
							    ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("IMAPISession->GetServerVersion() : ", ev);
			goto out;
		}
		if (CORBA_TRUE == bool) {
			g_print("\n        MajorVersion = %u\n", server_version.MajorVersion);
			g_print("        MinorVersion = %u\n", server_version.MinorVersion);
			g_print("        Build = %u\n", server_version.Build);
			g_print("        MinorBuild = %u\n", server_version.MinorBuild);
			g_print("OK\n");
		} else {
			g_print("ERROR - IMAPISession->GetServerVersion() returned FALSE\n");
//			goto out;
		}
	}

	g_print("    Initializing mailbox.. ");
	{
		br = BRUTUS_IMAPISession_InitMailbox(mapi_session,
						     ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("IMAPISession->InitMailbox() : ", ev);
			goto out;
		}
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - IMAPISession->InitMailbox() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Getting address book.. ");
	{
		br = BRUTUS_IMAPISession_OpenAddressBook(mapi_session,
							 "",
							 0,
							 &addr_book,
							 ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("IMAPISession->OpenAddressBook() : ", ev);
			goto out;
		}
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - IMAPISession->OpenAddressBook() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Getting GAL.. ");
	{
		br = BRUTUS_IAddrBook_GAL(addr_book, &mapi_book, ev);
		if (ORBIT2_EX(ev)) {
			ORBIT2_PRINT_EX("IAddrBook->GAL() : ", ev);
			goto out;
		}
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - IAddrBook->GAL() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Dumping GAL.. ");
	{
		br = dump_container((BRUTUS_IMAPIContainer)mapi_book,
				    global_orb,
				    ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - dump_container() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Dumping GAL contents table row 0.. ");
	{
		br = dump_contentstable_row((BRUTUS_IMAPIContainer)mapi_book,
					    0,
					    ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - dump_contentstable_row() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Dumping GAL contents table row 2.. ");
	{
		br = dump_contentstable_row((BRUTUS_IMAPIContainer)mapi_book,
					    2,
					    ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - dump_contentstable_row() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Getting default message store.. ");
	{
		br = get_default_message_store(mapi_session,
					       root_poa,
					       &msg_store,
					       ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - get_default_message_store() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Printing default message store properties.. \n");
	{
		br = print_properties(msg_store, ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - print_properties() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Getting Outlook Contacts folder.. ");
	{
		br = get_outlook_folder(msg_store,
					BRUTUS_BRUTUS_PR_OUTLOOK_CONTACTS,
					&mapi_folder,
					ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - get_outlook_folder() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Dumping PAB Contacts.. ");
	{
		br = dump_container((BRUTUS_IMAPIContainer)mapi_folder,
				    global_orb,
				    ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - dump_container() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	g_print("    Dumping PAB Contacts contents table row 1 (one).. ");
	{
		br = dump_contentstable_row((BRUTUS_IMAPIContainer)mapi_folder,
					    1,
					    ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - dump_contentstable_row() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}


	g_print("    Listing Outlook folder content by PR_SUBJECT.. \n");
	{
		br = list_container(mapi_folder,
				    BRUTUS_BRUTUS_PR_SUBJECT,
				    ev);
		if (BRUTUS_BRUTUS_S_OK == br)
			g_print("OK\n");
		else {
			g_print("ERROR - list_folder() returned %s\n", bresult_to_str(br));
			goto out;
		}
	}

	/* g_print("    Listing recurrence details for Outlook calendar folder content.. \n"); */
	/* { */
	/* 	br = summarize_calendar_recurrences(msg_store, ev); */
	/* 	if ((BRUTUS_BRUTUS_S_OK == br) || (BRUTUS_BRUTUS_MAPI_W_ERRORS_RETURNED == br)) { */
	/* 		br = BRUTUS_BRUTUS_S_OK; */
	/* 		g_print("OK\n"); */
	/* 	} else { */
	/* 		g_print("ERROR - summarize_calendar_recurrences() returned %s\n", bresult_to_str(br)); */
	/* 		goto out; */
	/* 	} */
	/* } */

	g_print("    Establishing a notification sink on the default message store.. ");
	{
		if (notifications) {
			notify_args.msg_store = (BRUTUS_IMsgStore)CORBA_Object_duplicate((CORBA_Object)msg_store, ev);
			notify_args.root_poa = (PortableServer_POA)CORBA_Object_duplicate((CORBA_Object)root_poa, ev);
			notify_args.sink = impl_BRUTUS_IMAPIAdviseSink__create(notify_args.notice, root_poa, ev);
			notify_args.mask = BRUTUS_BRUTUS_fnevCriticalError
				| BRUTUS_BRUTUS_fnevNewMail
				| BRUTUS_BRUTUS_fnevObjectCreated
				| BRUTUS_BRUTUS_fnevObjectDeleted
				| BRUTUS_BRUTUS_fnevObjectModified
				| BRUTUS_BRUTUS_fnevObjectMoved
				| BRUTUS_BRUTUS_fnevObjectCopied
				| BRUTUS_BRUTUS_fnevSearchComplete
				| BRUTUS_BRUTUS_fnevExtended;

			notify_thread = g_thread_create((GThreadFunc)notification_thread_func,
							(gpointer)&notify_args,
							TRUE,
							NULL);
			if (!notify_thread) {
				g_print("ERROR - could not create notification thread\n");
				goto out;
			}
			g_print("OK\n");
		} else
			g_print("skipped\n");
	}

	g_print("\n    Waiting for event.. ");
	{
		if (notifications) {
			g_thread_join(notify_thread);
			if (notify_args.connection) {
				BRUTUS_IMsgStore_Unadvise(msg_store, notify_args.connection, ev);
				g_print("\n    Unadvised ");
			}
			PortableServer_ObjectId *oid = PortableServer_POA_reference_to_id(root_poa, notify_args.sink, ev);
			PortableServer_POA_deactivate_object(root_poa, oid, ev);
			CORBA_free(oid);
			CORBA_Object_release((CORBA_Object)notify_args.sink, ev);

			g_print("OK\n");
		} else
			g_print("skipped\n");
	}

	// No errors it seems...
	retv = 0;

out:
	if (!CORBA_Object_is_nil(mapi_session, ev)) {
		BRUTUS_IMAPISession_Destroy(mapi_session, instance_id, ev);
		if (ORBIT2_EX(ev))
			ORBIT2_PRINT_EX("IMAPISession_Destroy() : ", ev);
		CORBA_Object_release(mapi_session, ev);
	}
	return retv ? EXIT_FAILURE : EXIT_SUCCESS;
	
	// Brutus
	if (!CORBA_Object_is_nil(brutus_logon, ev))
		CORBA_Object_release(brutus_logon, ev);

	// All Brutus objects must be Destroy()'ed to prevent leaks
	// on the server side except for the BrutusLogOn object, which
	// is a permanent object that will serve any client which
	// attempts to logon. The session object should be destroyed
	// last. You could in princible just destroy the session as
	// it will drag all other object down with it, but it would
	// not be the clean way to do it.

	// MAPI
	if (!CORBA_Object_is_nil(mapi_book, ev)) {
		BRUTUS_IABContainer_Destroy(mapi_book, 0, ev);
		CORBA_Object_release(mapi_book, ev);
	}
	if (!CORBA_Object_is_nil(addr_book, ev)) {
		BRUTUS_IAddrBook_Destroy(addr_book, 0, ev);
		CORBA_Object_release(addr_book, ev);
	}
	if (!CORBA_Object_is_nil(mapi_folder, ev)) {
		BRUTUS_IMAPIFolder_Destroy(mapi_folder, 0, ev);
		CORBA_Object_release(mapi_folder, ev);
	}
	if (!CORBA_Object_is_nil(msg_store, ev)) {
		BRUTUS_IMsgStore_Destroy(msg_store, 0, ev);
		CORBA_Object_release(msg_store, ev);
	}
	if (!CORBA_Object_is_nil(mapi_session, ev)) {
		BRUTUS_IMAPISession_Destroy(mapi_session, instance_id, ev);
		if (ORBIT2_EX(ev))
			ORBIT2_PRINT_EX("IMAPISession_Destroy() : ", ev);
		CORBA_Object_release(mapi_session, ev);
	}

	// Free the lifeline object
	if (!CORBA_Object_is_nil(brutus_check, ev))
		CORBA_Object_release(brutus_check, ev);

	if (conf) {
		free_config(conf);
		free(conf);
	}
	free(brutus_logon_corbaloc_str);
	free(logon_obj_server_key);
	free(brutus_server);
	free(exch_server);
	free(exch_mailbox);
	free(profile);
	free(password);
	free(windows_user);
	free(windows_user_domain);
	free(windows_user_password);

	// initiate shutdown
	main_shutdown(SIGTERM);

	g_print("\nWaiting for CORBA shutdown.. ");
	{
		// wait for termination signal
		g_mutex_lock(term_mutex);
		while (!shutting_down)
			g_cond_wait(terminate, term_mutex);
		g_mutex_unlock(term_mutex);

		// Deactivate POA
		deactivate_orbit_poa(root_poa, ev);

		// destroy POA
		PortableServer_POA_destroy(root_poa, TRUE, FALSE, ev);
		if (ORBIT2_EX(ev))
			ORBIT2_PRINT_EX("POA_destroy() : ", ev);

		CORBA_Object_release((CORBA_Object)root_poa, ev);
		if (ORBIT2_EX(ev))
			ORBIT2_PRINT_EX("Object_release() : ", ev);

		// release local copy of global_orb
		CORBA_Object_release((CORBA_Object)orb, ev);
		if (ORBIT2_EX(ev))
			ORBIT2_PRINT_EX("Object_release() : ", ev);

		// signal to the ORB_run() thread that the ORB can be destroyed
		g_mutex_lock(proceed_orb_destroy_mutex);
		orb_destroy = TRUE;
		g_cond_signal(proceed_orb_destroy);
		g_mutex_unlock(proceed_orb_destroy_mutex);

		// wait for ORB to destroy cleanly
		g_thread_join(orb_thread);

		CORBA_exception_free(ev);

		g_print("OK\n");
	}

	g_print("\nCleaning up.. ");
	{
		// mutexes
		g_mutex_free(proceed_orb_ok_mutex);
		g_mutex_free(proceed_orb_destroy_mutex);
		g_mutex_free(term_mutex);

		// conditionals
		g_cond_free(proceed_orb_destroy);
		g_cond_free(proceed_orb_ok);
		g_cond_free(terminate);

		g_print("OK\n");
	}
	if (retv)
		g_print("\n\nFAILURE\n\n");
	else
		g_print("\nSUCCESS, all tests passed\n\n");

	return 0;
}
