/*
    Copyright (C) 2005-2007  Michel de Boer <michel@twinklephone.com>

    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 2 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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "twinkle_config.h"

#ifdef HAVE_KDE
#include <kapplication.h>
#include <kcmdlineargs.h>
#endif

#include <qapplication.h>
#include <qtranslator.h>
#include <qmime.h>
#include <qprogressdialog.h>
#include <qtextcodec.h>

#include "mphoneform.h"

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <unistd.h>

#include "address_book.h"
#include "address_finder.h"
#include "call_history.h"
#include "cmd_socket.h"
#include "events.h"
#include "listener.h"
#include "log.h"
#include "protocol.h"
#include "sender.h"
#include "transaction_mgr.h"
#include "user.h"
#include "util.h"
#include "phone.h"
#include "gui.h"
#include "qt_translator.h"
#include "command_args.h"
#include "sockets/interfaces.h"
#include "sockets/socket.h"
#include "threads/thread.h"
#include "audits/memman.h"


using namespace std;

// Class to initialize the random generator before objects of
// other classes are created. Initializing just from the main function
// is too late.
class t_init_rand {
public:
	t_init_rand();
};

t_init_rand::t_init_rand() { srand(time(NULL)); }

// Initialize random generator
t_init_rand init_rand;

// Language translator for the core of Twinkle
t_translator *translator = NULL;

// Indicates if application is ending (because user pressed Quit)
bool end_app;

// Memory manager for memory leak tracing
t_memman 		*memman;

// IP address on which the phone is running
string user_host;

// SIP socket for sending and receiving signaling
t_socket_udp *sip_socket;

// Event queue that is handled by the transaction manager thread
// The following threads write to this queue
// - UDP listener
// - transaction layer
// - timekeeper
t_event_queue		*evq_trans_mgr;

// Event queue that is handled by the UDP sender thread
// The following threads write to this queue:
// - phone UAS
// - phone UAC
// - transaction manager
t_event_queue		*evq_sender_udp;

// Event queue that is handled by the transaction layer thread
// The following threads write to this queue
// - transaction manager
// - timekeeper
t_event_queue		*evq_trans_layer;

// Event queue that is handled by the phone timekeeper thread
// The following threads write into this queue
// - phone UAS
// - phone UAC
// - transaction manager
t_event_queue		*evq_timekeeper;

// The timekeeper
t_timekeeper		*timekeeper;

// The transaction manager
t_transaction_mgr	*transaction_mgr;

// The phone
t_phone			*phone;

// User interface
t_userintf		*ui;

// Log file
t_log			*log_file;

// System config
t_sys_settings		*sys_config;

// Call history
t_call_history		*call_history;

// Local address book
t_address_book		*ab_local;

/** Command arguments. */
t_command_args g_cmd_args;

// Thread id of main thread
pthread_t		thread_id_main;

// Indicates if LinuxThreads or NPTL is active.
bool			threading_is_LinuxThreads;


void parse_main_args(int argc, char **argv, bool &cli_mode, bool &override_lock_file,
		     list<string> &config_files) 
{
	cli_mode = false;
	override_lock_file = false;
	config_files.clear();

	for (int i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
			// Help
			cout << "Usage: twinkle [options]\n\n";
			cout << "Options:\n";
			cout << " -c";
			cout << "\t\tRun in command line interface (CLI) mode\n";
			cout << endl;
			cout << " --share <dir>";
			cout << "\tSet the share directory.\n";
			cout << endl;
			cout << " -f <profile>";
			cout << "\tStartup with a specific profile. You will not be requested\n";
			cout << "\t\tto choose a profile at startup. The profiles that you created\n";
			cout << "\t\tare the .cfg files in your .twinkle directory.\n";
			cout << "\t\tYou may specify multiple profiles separated by spaces.\n";
			cout << endl;
			cout << " --force";
			cout << "\tIf a lock file is detected at startup, then override it\n";
			cout << "\t\tand startup.\n";
			cout << endl;
			cout << " -i <IP addr>";
			cout << "\tIf you have multiple IP addresses on your computer,\n";
			cout << "\t\tthen you can supply the IP address to use here.\n";
			cout << endl;
			cout << " --sip-port <port>\n";
			cout << "\t\tPort for SIP UDP signalling.\n";
			cout << "\t\tThis port overrides the port from the system settings.\n";
			cout << endl;
			cout << " --rtp-port <port>\n";
			cout << "\t\tPort for RTP.\n";
			cout << "\t\tThis port overrides the port from the system settings.\n";
			cout << endl;
			cout << " --nic <NIC>";
			cout << "\tIf you have multiple NICs on your computer,\n";
			cout << "\t\tthen you can supply the NIC name to use here (e.g. eth0).\n";
			cout << endl;
			cout << " --call <address>\n";
			cout << "\t\tInstruct Twinkle to call the address.\n";
			cout << "\t\tWhen Twinkle is already running, this will instruct the running\n";
			cout << "\t\tprocess to call the address.\n";
			cout << "\t\tThe address may be a full or partial SIP URI. A partial SIP URI\n";
			cout << "\t\twill be completed with the information from the user profile.\n";
			cout << endl;
			cout << "\t\tA subject may be passed by appending '?subject=<subject>'\n";
			cout << "\t\tto the address.\n";
			cout << endl;
			cout << "\t\tExamples:\n";
			cout << "\t\ttwinkle --call 123456\n";
			cout << "\t\ttwinkle --call sip:example@example.com?subject=hello\n";
			cout << endl;
			cout << " --cmd <cli command>\n";
			cout << "\t\tInstruct Twinkle to execute the CLI command. You can run\n";
			cout << "\t\tall commands from the command line interface mode.\n";
			cout << "\t\tWhen Twinkle is already running, this will instruct the running\n";
			cout << "\t\tprocess to execute the CLI command.\n";
			cout << endl;
			cout << "\t\tExamples:\n";
			cout << "\t\ttwinkle --cmd answer\n";
			cout << "\t\ttwinkle --cmd mute\n";
			cout << "\t\ttwinkle --cmd 'transfer 12345'\n";
			cout << endl;
			cout << " --immediate";
			cout << "\tThis option can be used in conjunction with --call or --cmd\n";
			cout << "\t\tIt indicates the the command or call is to be performed\n";
			cout << "\t\timmediately without asking the user for any confirmation.\n";
			cout << endl;
			cout << " --set-profile <profile>\n";
			cout << "\t\tMake <profile> the active profile.\n";
			cout << "\t\tWhen using this option in conjuction with --call and --cmd,\n";
			cout << "\t\tthen the profile is activated before executing --call or \n";
			cout << "\t\t--cmd.\n";
			cout << endl;
			cout << " --show";
			cout << "\t\tInstruct a running instance of Twinkle to show the main window\n";
			cout << "\t\tand take focus.\n";
			cout << endl;
			cout << " --hide";
			cout << "\t\tInstruct a running instance of Twinkle to hide in the sytem tray.\n";
			cout << "\t\tIf no system tray is used, then Twinkle will minimize.\n";
			cout << endl;
			cout << " --help-cli [cli command]\n";
			cout << "\t\tWithout a cli command this option lists all available CLI\n";
			cout << "\t\tcommands. With a CLI command this option prints help on\n";
			cout << "\t\tthe CLI command.\n";
			cout << endl;
			cout << " --version";
			cout << "\tGet version information.\n";
			exit(0);
		} else if (strcmp(argv[i], "--version") == 0) {
			// Get version
			QString s = sys_config->about(false).c_str();
			cout << s;
			exit(0);
		} else if (strcmp(argv[i], "-c") == 0) {
			// CLI mode
			cli_mode = true;
		} else if (strcmp(argv[i], "--share") == 0) {
			if (i < argc - 1 && argv[i+1][0] != '-') {
				i++;
				sys_config->set_dir_share(argv[i]);
			} else {
				cout << argv[0] << ": ";
				cout << "Directory missing for option '-share'.\n";
				exit(0);
			}
		} else if (strcmp(argv[i], "-f") == 0) {
			if (i < argc - 1 && argv[i+1][0] != '-') {
				while (i < argc -1 && argv[i+1][0] != '-') {
					i++;
					// Config file name
					QString config_file = argv[i];
					if (!config_file.endsWith(USER_FILE_EXT)) 
					{
						config_file += USER_FILE_EXT;
					}
					config_files.push_back(config_file.ascii());
				}
			} else {
				cout << argv[0] << ": ";
				cout << "Config file name missing for option '-f'.\n";
				exit(0);
			}
		} else if (strcmp(argv[i], "--force") == 0) {
			override_lock_file = true;
		} else if (strcmp(argv[i], "-i") == 0) {
			if (i < argc - 1) {
				i++;
				// IP address
				user_host = argv[i];
				if (!exists_interface(user_host)) {
					cout << argv[0] << ": ";
					cout << "There is no interface with IP address ";
					cout << user_host << endl;
					exit(0);
				}
			} else {
				cout << argv[0] << ": ";
				cout << "IP address missing for option '-i'.\n";
				exit(0);
			}
		} else if (strcmp(argv[i], "--sip-port") == 0) {
			if (i < argc - 1) {
				i++;
				g_cmd_args.override_sip_udp_port = atoi(argv[i]);
			} else {
				cout << argv[0] << ": ";
				cout << "Port missing for option '--sip-port'\n";
			}
		} else if (strcmp(argv[i], "--rtp-port") == 0) {
			if (i < argc - 1) {
				i++;
				g_cmd_args.override_rtp_port = atoi(argv[i]);
			} else {
				cout << argv[0] << ": ";
				cout << "Port missing for option '--rtp-port'\n";
			}	
		} else if (strcmp(argv[i], "--nic") == 0) {
			if (i < argc - 1) {
				i++;
				// NIC name, e.g. eth0
				string user_dev = argv[i];
				string ip;
				if (exists_interface_dev(user_dev, ip)) {
				        user_host = ip;
				} else {
					cout << argv[0] << ": ";
					cout << "There is no network interface ";
					cout << user_dev << endl;
					exit(0);
				}
			} else {
				cout << argv[0] << ": ";
				cout << "NIC name missing for option '-d'.\n";
				exit(0);
			}
		} else if (strcmp(argv[i], "--call") == 0) {
			if (i < argc - 1) {
				i++;
				// SIP URI
				g_cmd_args.callto_destination = argv[i];
				
				if (g_cmd_args.callto_destination.isEmpty()) {
					cout << argv[0] << ": ";
					cout << "--call argument may not be empty.\n";
					exit(0);
				}
			} else {
				cout << argv[0] << ": ";
				cout << "SIP URI missing for option '--call'.\n";
				exit(0);
			}
		} else if (strcmp(argv[i], "--cmd") == 0) {
			if (i < argc - 1) {
				i++;
				// CLI command
				g_cmd_args.cli_command = argv[i];
				
				if (g_cmd_args.cli_command.isEmpty()) {
					cout << argv[0] << ": ";
					cout << "--cmd argument may not be empty.\n";
					exit(0);
				}
			} else {
				cout << argv[0] << ": ";
				cout << "CLI command missing for option '--cmd'.\n";
				exit(0);
			}
		} else if (strcmp(argv[i], "--immediate") == 0) {
			// Immediate mode
			g_cmd_args.cmd_immediate_mode = true;
		} else if (strcmp(argv[i], "--set-profile") == 0) {
			if (i < argc - 1) {
				i++;
				// Set profile
				g_cmd_args.cmd_set_profile = argv[i];
			} else {
				cout << argv[0] << ": ";
				cout << "Profile missing for option '--set-profile'.\n";
				exit(0);
			}	
		} else if (strcmp(argv[i], "--show") == 0) {
			// Show main window
			g_cmd_args.cmd_show = true;
		} else if (strcmp(argv[i], "--hide") == 0) {
			// Hide main window
			g_cmd_args.cmd_hide = true;
		} else if (strcmp(argv[i], "--help-cli") == 0) {
			string cmd_help("help ");
			if (i < argc -1) {
				i++;
				// Help CLI
				cmd_help += argv[i];
			}
			
			t_phone p;
			t_userintf u(&p);
			u.exec_command(cmd_help);
			exit(0);
		} else {
			cout << argv[0] << ": ";
			cout << "Unknown option '" << argv[i] << "'." << endl;
			cout << argv[0] << ": ";
			cout << "Use --help to get a list of available command line options.\n";
			exit(0);
		}
	}
	
	if (!g_cmd_args.callto_destination.isEmpty() && !g_cmd_args.cli_command.isEmpty()) {
		cout << argv[0] << ": ";
		cout << "--call and --cmd cannot be used at the same time.\n";
		exit(0);
	}
	
	return;
}

bool open_sip_socket(bool cli_mode) {
	// Open socket for SIP signaling
	try {
		sip_socket = new t_socket_udp(sys_config->get_sip_udp_port(true));
		MEMMAN_NEW(sip_socket);
		if (sip_socket->enable_icmp()) {
			log_file->write_report("ICMP processing enabled.", "::main");
		} else {
			log_file->write_report("ICMP processing disabled.", "::main");
		}
	} catch (int err) {		
		string msg;
		if (cli_mode) {
			msg = QString("Failed to create a UDP socket (SIP) on port %1")
			   .arg(sys_config->get_sip_udp_port()).ascii();
		} else {
			msg = qApp->translate("GUI", "Failed to create a UDP socket (SIP) on port %1")
			   .arg(sys_config->get_sip_udp_port()).ascii();
		}
		msg += "\n";
		msg += get_error_str(err);
		log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL);
		ui->cb_show_msg(msg, MSG_CRITICAL);
		return false;
	}
	
	return true;
}

QApplication *create_user_interface(bool cli_mode, char **argv, QTranslator *qtranslator) {
	QApplication *qa = NULL;
	
	if (cli_mode) {
		// CLI mode
		ui = new t_userintf(phone);
		MEMMAN_NEW(ui);
	} else {
		// GUI mode
		
#ifdef HAVE_KDE
		// Store the defualt mime source factory for the embedded icons.
		// This is created by Qt. The KApplication constructor seems to destroy
		// this default.
		QMimeSourceFactory *factory_qt = QMimeSourceFactory::takeDefaultFactory();
		
		// Initialize the KApplication
		KCmdLineArgs::init(1, argv, "twinkle", PRODUCT_NAME, "Soft phone",
				   PRODUCT_VERSION);
		qa = new KApplication();
		MEMMAN_NEW(qa);
		
		// Store the KDE mime source factory
		QMimeSourceFactory *factory_kde = QMimeSourceFactory::takeDefaultFactory();
		
		// Make the Qt factory the default to make the embedded icons work.
		QMimeSourceFactory::setDefaultFactory(factory_qt);
		
		// Add the KDE factory
		QMimeSourceFactory::addFactory(factory_kde);
#else
		int tmp = 1;
		qa = new QApplication(tmp, argv);
		MEMMAN_NEW(qa);
#endif
		QTextCodec::setCodecForCStrings(QTextCodec::codecForName("utf8"));
		QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8"));
		
		// Install Qt translator
		// Do not report to memman as the translator will be deleted
		// automatically when the QApplication is deleted.
		qtranslator = new QTranslator(0);
		qtranslator->load(QString("twinkle_") + QTextCodec::locale(), 
			QString(sys_config->get_dir_lang().c_str()));
		qa->installTranslator(qtranslator);
		
		// Create translator for translation of strings from the core
		translator = new t_qt_translator(qa);
		MEMMAN_NEW(translator);

		ui = new t_gui(phone);
		MEMMAN_NEW(ui);
	}
	
	return qa;
}

int main( int argc, char ** argv )
{
	string error_msg;
	bool cli_mode;
	bool override_lock_file;
	list<string> config_files;
	
	// Initialize globals
	end_app = false;
	
	// Determine threading implementation
	threading_is_LinuxThreads = t_thread::is_LinuxThreads();

	QApplication *qa = NULL;
	QTranslator *qtranslator = NULL;
	
	// Store id of main thread
	thread_id_main = t_thread::self();
	
	memman = new t_memman();
	MEMMAN_NEW(memman);
	evq_trans_mgr = new t_event_queue();
	MEMMAN_NEW(evq_trans_mgr);
	evq_sender_udp = new t_event_queue();
	MEMMAN_NEW(evq_sender_udp);
	evq_trans_layer = new t_event_queue();
	MEMMAN_NEW(evq_trans_layer);
	evq_timekeeper = new t_event_queue();
	MEMMAN_NEW(evq_timekeeper);
	timekeeper = new t_timekeeper();
	MEMMAN_NEW(timekeeper);
	transaction_mgr = new t_transaction_mgr();
	MEMMAN_NEW(transaction_mgr);
	phone = new t_phone();
	MEMMAN_NEW(phone);
	
	// Create system configuration object
	sys_config = new t_sys_settings();
	MEMMAN_NEW(sys_config);
	
	// Parse command line arguments
	parse_main_args(argc, argv, cli_mode, override_lock_file, config_files);
	sys_config->set_override_sip_udp_port(g_cmd_args.override_sip_udp_port);
	sys_config->set_override_rtp_port(g_cmd_args.override_rtp_port);
	
	// Checking the environment and creating the lock is done at
	// this early stage to improve performance of the --call parameter.
	// Creation of the QApplication object for the GUI is slow.
	// However for errors, the user interface must be created to give
	// either a message box or text formatted error.
	
	// Check requirements on environment
	// If check fails, then display error after user interface has been
	// created.
	string env_error_msg;
	bool env_check_ok = sys_config->check_environment(env_error_msg);
	
	// Create a lock file to guarantee that the application runs only once.
	bool already_running;
	bool lock_created;
	string lock_error_msg;	
	if (env_check_ok &&
	    !(lock_created = sys_config->create_lock_file(lock_error_msg, already_running))) 
	{
		bool must_exit = false;
		
		// Show the main window of the running Twinkle process.
		if (already_running && g_cmd_args.cmd_show) {
			cmdsocket::cmd_show();
			must_exit = true;
		}
		
		// Hide the main window of the running Twinkle process.
		if (already_running && g_cmd_args.cmd_hide) {
			cmdsocket::cmd_hide();
			must_exit = true;
		}
		
		// Activate a profile in the running Twinkle process.
		if (already_running && !g_cmd_args.cmd_set_profile.isEmpty()) {
			cmdsocket::cmd_cli(string("user ") + g_cmd_args.cmd_set_profile.ascii(), true);
			// Do not exit now as this option may be used in conjuction
			// with --call or --cmd
			must_exit = true;
		}
		
		// If Twinkle is running already and the --call parameter
		// is present, then send the call destination to the running
		// Twinkle process.
		if (already_running && !g_cmd_args.callto_destination.isEmpty()) {
			cmdsocket::cmd_call(g_cmd_args.callto_destination.ascii(), 
					    g_cmd_args.cmd_immediate_mode);
			exit(0);
		}
		
		// If the --cmd parameter is present, send the cli command
		// to the running Twinkle process
		if (already_running && !g_cmd_args.cli_command.isEmpty()) {
			cmdsocket::cmd_cli(g_cmd_args.cli_command.ascii(), 
					   g_cmd_args.cmd_immediate_mode);
			exit(0);
		}
		
		// Exit if an instruction for a running instance was given.
		if (must_exit) {
			exit(0);
		}
	}
	
	// Read system configuration
	bool sys_config_read = sys_config->read_config(error_msg);
	qa = create_user_interface(cli_mode, argv, qtranslator);
	if (!sys_config_read) {
		ui->cb_show_msg(error_msg, MSG_CRITICAL);
		exit(1);
	}
	
	// Get default values from system configuration
	if (config_files.empty()) {
		list<string> start_user_profiles = sys_config->get_start_user_profiles();
		for (list<string>::iterator i = start_user_profiles.begin();
		i != start_user_profiles.end(); i++)
		{
			QString config_file = (*i).c_str();
			config_file += USER_FILE_EXT;
			config_files.push_back(config_file.ascii());
		}
	}
	if (user_host.empty()) {
		string ip;
		if (exists_interface(sys_config->get_start_user_host())) {
			user_host = sys_config->get_start_user_host();
		} else if (exists_interface_dev(sys_config->get_start_user_nic(), ip)) {
			user_host = ip;
		}
	}
	
	if (!env_check_ok) {
		// Environment is not good
		// Call the check_environment once more to get proper translation
		// of the error message. The previous check was done before
		// the QApplication was created.
		(void)sys_config->check_environment(env_error_msg);
		ui->cb_show_msg(env_error_msg, MSG_CRITICAL);
		exit(1);
	}
	
	// Show error if lock file could not be created
	if (!lock_created) {	
		string msg;
		// Call create lock file once more to get proper translation of
		// error message.
		if (!sys_config->create_lock_file(msg, already_running)) {
			if (already_running) {
				if (!cli_mode) {
					msg += "\n\n";
					msg += qApp->translate("GUI",
						"Override lock file and start anyway?").ascii();
				}
				if (override_lock_file || ui->cb_ask_msg(msg, MSG_WARNING)) {
					sys_config->delete_lock_file();
					if (!sys_config->create_lock_file(msg, 
						already_running))
					{
						ui->cb_show_msg(msg, MSG_CRITICAL);
						exit(1);
					}
				} else {
					exit(1);
				}
			} else {
				ui->cb_show_msg(msg, MSG_CRITICAL);
				exit(1);
			}
		}
		// If for some obscure reason the lock file could be
		// created this time, then continue.
	}
	
	// Create log file
	log_file = new t_log();
	MEMMAN_NEW(log_file);
	
	// Write threading implementation to log file. May be useful for debugging.
	if (threading_is_LinuxThreads) {
		log_file->write_report("Threading implementation is LinuxThreads.",
			"::main", LOG_NORMAL, LOG_INFO);
	} else {
		log_file->write_report("Threading implementation is NPTL.",
			"::main", LOG_NORMAL, LOG_INFO);
	}
	
	bool profile_selected = false;
	while(!profile_selected) {
		// Select user profile
		if (config_files.empty()) {
			if (!ui->select_user_config(config_files)) {
				sys_config->delete_lock_file();
				exit(1);
			}
		}
		
		for (list<string>::iterator i = config_files.begin();
		i != config_files.end(); i++)
		{	
			t_user user_config;
			
			// Read user configuration
			if (user_config.read_config(*i, error_msg)) {
				t_user *dup_user;
				if (phone->add_phone_user(
						user_config, &dup_user))
				{
					profile_selected = true;
				} else {
					if (cli_mode) {
						error_msg = QString("The following profiles are both for user %1").arg(user_config.get_name().c_str()).ascii();
					} else {
						error_msg = qApp->translate("GUI", "The following profiles are both for user %1").arg(user_config.get_name().c_str()).ascii();
					}
					error_msg += ":\n\n";
					error_msg += user_config.get_profile_name();
					error_msg += "\n";
					error_msg += dup_user->get_profile_name();
					error_msg += "\n\n";
					if (cli_mode) {
						error_msg += QString("You can only run multiple profiles for different users.").ascii();
						error_msg += "\n";
						error_msg += QString("If these are users for different domains, then enable the following option in your user profile (SIP protocol):");
						error_msg += "\n";
						error_msg += QString("Use domain name to create a unique contact header");
					} else {
						error_msg += qApp->translate("GUI", "You can only run multiple profiles for different users.").ascii();
						error_msg += "\n";
						error_msg += qApp->translate("GUI", "If these are users for different domains, then enable the following option in your user profile (SIP protocol)");
						error_msg += ":\n";
						error_msg += qApp->translate("GUI", "Use domain name to create a unique contact header");
					}
					ui->cb_show_msg(error_msg, MSG_CRITICAL);
					profile_selected = false;
					break;
				}
			} else {
				ui->cb_show_msg(error_msg, MSG_CRITICAL);
				profile_selected = false;
				break;
			}
		}
		
		if (profile_selected && !open_sip_socket(cli_mode)) {
			// Opening SIP socket failed. Let user pick a user profile
			// again, so he can make changes in settings to fix the error.
			profile_selected = false;
		}
		
		// In CLI mode the user cannot select another profile.
		if (!profile_selected) {
			if (cli_mode) {
				sys_config->delete_lock_file();
				exit(1);
			}
		}
		
		config_files.clear();
	}
	
	// Initialize RTP port settings.
	phone->init_rtp_ports();
	
	// Create call history
	call_history = new t_call_history();
	MEMMAN_NEW(call_history);
	
	// Read call history
	if (!call_history->load(error_msg)) {
		log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING);
	}
	
	// Create local address book
	ab_local = new t_address_book();
	MEMMAN_NEW(ab_local);
	
	// Read local address book
	if (!ab_local->load(error_msg)) {
		log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING);
		ui->cb_show_msg(error_msg, MSG_WARNING);
	}
	
	// Preload the address finder (KABC is only available in GUI mode)
	if (!cli_mode) {
		t_address_finder::preload();
	}
	
	// Pick network interface
	if (user_host.empty()) {
		user_host = ui->select_network_intf();
		if (user_host.empty()) {
			sys_config->delete_lock_file();
			exit(1);
		}
	}
	
	// Discover NAT type if STUN is enabled
	list<t_user *> user_list = phone->ref_users();
	ui->cb_nat_discovery_progress_start(user_list.size());
	list<string> msg_list;
	int progressStep = 0;
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) {
		ui->cb_nat_discovery_progress_step(progressStep);
		
		if (ui->cb_nat_discovery_cancelled()) {
			log_file->write_report("User aborted NAT discovery.", "::main");
			sys_config->delete_lock_file();
			exit(1);
		}
		
		if (!phone->stun_discover_nat(*i, error_msg)) {
			msg_list.push_back(error_msg);
		}
		
		progressStep++;
	}
	ui->cb_nat_discovery_finished();
	
	for (list<string>::iterator i = msg_list.begin();
	     i != msg_list.end(); i++)
	{
		ui->cb_show_msg(*i, MSG_WARNING);
	}
	
	// Open socket for external commands from the command line
	string cmd_sock_name = sys_config->get_dir_user();
	cmd_sock_name += '/';
	cmd_sock_name += CMD_SOCKNAME;
	t_socket_local *sock_cmd = NULL;
	try {

		
		// The local socket may still exist if Twinkle got killed
		// previously, so remove it if it is still there.
		unlink(cmd_sock_name.c_str());
		
		sock_cmd = new t_socket_local();
		MEMMAN_NEW(sock_cmd);
		sock_cmd->bind(cmd_sock_name);
		sock_cmd->listen(5);
		
		string log_msg = "Created local socket: ";
		log_msg += cmd_sock_name;
		log_file->write_report(log_msg, "::main");
	}
	catch (int e) {
		if (sock_cmd) {
			MEMMAN_DELETE(sock_cmd);
			delete sock_cmd;
			sock_cmd = NULL;
		}
		string log_msg = "Failed to create local socket: ";
		log_msg += cmd_sock_name;
		log_msg += "\n";
		log_msg += get_error_str(e);
		log_msg += "\n";
		log_file->write_report(log_msg, "::main", LOG_NORMAL, LOG_WARNING);
	}
	
	// Dedicated thread will catch SIGALRM, SIGINT, SIGTERM, SIGCHLD signals, 
	// therefore all threads must block these signals. Block now, then all
	// created threads will inherit the signal mask.
	// In LinuxThreads the sigwait does not work very well, so
	// in LinuxThreads a signal handler is used instead.
	if (!threading_is_LinuxThreads) {
		sigset_t sigset;
		sigemptyset(&sigset);
		sigaddset(&sigset, SIGALRM);
		sigaddset(&sigset, SIGINT);
		sigaddset(&sigset, SIGTERM);
		sigaddset(&sigset, SIGCHLD);
		sigprocmask(SIG_BLOCK, &sigset, NULL);
	} else {
		if (!phone->set_sighandler()) {
			string msg = "Failed to register signal handler.";
			log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL);
			ui->cb_show_msg(msg, MSG_CRITICAL);
			sys_config->delete_lock_file();
			exit(1);
		}
	}
				 
	// Create threads
	t_thread *thr_sender_udp;
	t_thread *thr_listen_udp;
	t_thread *thr_timekeeper;
	t_thread *thr_alarm_catcher;
	t_thread *thr_sig_catcher;
	t_thread *thr_trans_mgr;
	t_thread *thr_phone_uas;
	t_thread *thr_listen_cmd = NULL;
	
	try {
		// UDP sender thread
		thr_sender_udp = new t_thread(sender_udp, NULL);
		MEMMAN_NEW(thr_sender_udp);

		// UDP listener thread
		thr_listen_udp = new t_thread(listen_udp, NULL);
		MEMMAN_NEW(thr_listen_udp);

		// Timekeeper thread
		thr_timekeeper = new t_thread(timekeeper_main, NULL);
		MEMMAN_NEW(thr_timekeeper);
		
		if (!threading_is_LinuxThreads) {
			// Alarm catcher thread
			thr_alarm_catcher = new t_thread(timekeeper_sigwait, NULL);
			MEMMAN_NEW(thr_alarm_catcher);

			// Signal catcher thread
			thr_sig_catcher = new t_thread(phone_sigwait, NULL);
			MEMMAN_NEW(thr_sig_catcher);
		}

		// Transaction manager thread
		thr_trans_mgr = new t_thread(transaction_mgr_main, NULL);
		MEMMAN_NEW(thr_trans_mgr);

		// Phone thread (UAS)
		thr_phone_uas = new t_thread(phone_uas_main, NULL);
		MEMMAN_NEW(thr_phone_uas);
		
		// External command listener thread
		if (sock_cmd) {
			thr_listen_cmd = new t_thread(cmdsocket::listen_cmd, sock_cmd);
			MEMMAN_NEW(thr_listen_cmd);
		}
	} catch (int) {
		string msg = "Failed to create threads.";
		log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL);
		ui->cb_show_msg(msg, MSG_CRITICAL);
		sys_config->delete_lock_file();
		exit(1);
	}
	
	// Validate sound devices
	if (!sys_config->exec_audio_validation(true, true, true, error_msg)) {
		ui->cb_show_msg(error_msg, MSG_WARNING);
	}

	// Start UI event loop (CLI/QApplication/KApplication)
	try {
		ui->run();
	} catch (string e) {
		string msg = "Exception: ";
		msg += e;
		log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL);
		ui->cb_show_msg(msg, MSG_CRITICAL);
		sys_config->delete_lock_file();
		exit(1);
	} catch (...) {
		string msg = "Unknown exception";
		log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL);
		ui->cb_show_msg(msg, MSG_CRITICAL);
		sys_config->delete_lock_file();
		exit(1);
	}
	
	// Application is ending
	end_app = true;
	
	// Terminate threads
	// Kill the threads getting receiving input from the outside world first,
	// so no new inputs come in during termination.
	if (thr_listen_cmd) {
		thr_listen_cmd->cancel();
		thr_listen_cmd->join();
		log_file->write_report("thr_listen_cmd stopped.", "::main", LOG_NORMAL, LOG_DEBUG);
	}
	
	thr_listen_udp->cancel();
	thr_listen_udp->join();
	log_file->write_report("thr_listen_udp stopped.", "::main", LOG_NORMAL, LOG_DEBUG);
	
	evq_trans_layer->push_quit();
	thr_phone_uas->join();
	log_file->write_report("thr_phone_uas stopped.", "::main", LOG_NORMAL, LOG_DEBUG);
	
	evq_trans_mgr->push_quit();
	thr_trans_mgr->join();
	
	if (!threading_is_LinuxThreads) {
		try {
			thr_sig_catcher->cancel();
		} catch (int) {
			// Thread terminated already by itself
		}
		thr_sig_catcher->join();
		log_file->write_report("thr_sig_catcher stopped.", "::main", 
				       LOG_NORMAL, LOG_DEBUG);
		
		thr_alarm_catcher->cancel();
		thr_alarm_catcher->join();
		log_file->write_report("thr_alarm_catcher stopped.", "::main", 
				       LOG_NORMAL, LOG_DEBUG);
	}
	
	evq_timekeeper->push_quit();
	thr_timekeeper->join();
	log_file->write_report("thr_timekeeper stopped.", "::main", LOG_NORMAL, LOG_DEBUG);
	
	evq_sender_udp->push_quit();
	thr_sender_udp->join();
	log_file->write_report("thr_sender_udp stopped.", "::main", LOG_NORMAL, LOG_DEBUG);
	
	if (thr_listen_cmd) {
		MEMMAN_DELETE(thr_listen_cmd);
		delete thr_listen_cmd;
	}

	MEMMAN_DELETE(thr_phone_uas);
	delete thr_phone_uas;
	MEMMAN_DELETE(thr_trans_mgr);
	delete thr_trans_mgr;
	MEMMAN_DELETE(thr_timekeeper);
	delete thr_timekeeper;
	
	if (!threading_is_LinuxThreads) {
		MEMMAN_DELETE(thr_sig_catcher);
		delete thr_sig_catcher;
		MEMMAN_DELETE(thr_alarm_catcher);
		delete thr_alarm_catcher;
	}
	
	MEMMAN_DELETE(thr_listen_udp);
	delete thr_listen_udp;
	MEMMAN_DELETE(thr_sender_udp);
	delete thr_sender_udp;

	MEMMAN_DELETE(ab_local);
	delete ab_local;
	MEMMAN_DELETE(call_history);
	delete call_history;
	call_history = NULL;

	MEMMAN_DELETE(ui);
	delete ui;
	ui = NULL;
	
	MEMMAN_DELETE(sip_socket);
	delete sip_socket;
	
	if (sock_cmd) {
		MEMMAN_DELETE(sock_cmd);
		delete sock_cmd;
		unlink(cmd_sock_name.c_str());
	}

	MEMMAN_DELETE(phone);
	delete phone;
	MEMMAN_DELETE(transaction_mgr);
	delete transaction_mgr;
	MEMMAN_DELETE(timekeeper);
	delete timekeeper;
	MEMMAN_DELETE(evq_trans_mgr);
	delete evq_trans_mgr;
	MEMMAN_DELETE(evq_sender_udp);
	delete evq_sender_udp;
	MEMMAN_DELETE(evq_trans_layer);
	delete evq_trans_layer;
	MEMMAN_DELETE(evq_timekeeper);
	delete evq_timekeeper;
	
	if (translator) {
		MEMMAN_DELETE(translator);
		delete translator;
		translator = NULL;
	}
	
	if (qtranslator) {
		MEMMAN_DELETE(qtranslator);
		delete(qtranslator);
	}
	
	if (qa) {
		MEMMAN_DELETE(qa);
		delete qa;
	}

	// Report memory leaks
	// Report deletion of log_file and sys_config already to get a correct
	// report.
	MEMMAN_DELETE(sys_config);
	MEMMAN_DELETE(log_file);
	MEMMAN_DELETE(memman);
	MEMMAN_REPORT;
	
	delete log_file;
	delete memman;
	
	sys_config->delete_lock_file();
	delete sys_config;
}


syntax highlighted by Code2HTML, v. 0.9.1