/*
    Silky - A GTK+ client for SILC.
    Copyright (C) 2003,2004 Toni Willberg

    - Initialization stuff

    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

    http://silky.sourceforge.net/

*/

#include <glib.h>
#include <glade/glade.h>
#include <gtk/gtk.h>
#include <silcincludes.h>
#include <silcclient.h>

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <string.h>
#include <getopt.h>

#include "xmlconfig.h"
#include "xmllayout.h"
#include "support.h"
#include "servers.h"
#include "keys.h"
#include "buddylist.h"
#include "gui.h"
#include "preferences.h"
#include "timer.h"
#include "files.h"
#include "log.h"
#include "common.h"


GladeXML *xmlmain;
silkyStruct *silky;		/* this is usually used like 'silky->conn' and 'silky->client' */

SilcClientOperations ops;

guint ftht;			/* used for timeout handler tracking */
gchar *previous_nick;
gchar *prefpath;		/* usually ~/.silky/silky.conf */
gchar *serverspath;		/* usually ~/.silky/servers.conf */
gchar *buddiespath;		/* usually ~/.silky/buddies.conf */
gchar *serverkeypath;
gchar *clientkeypath;
gchar *pubkey;			/* own key */
gchar *privkey;			/* own key */
gchar *homedir;
gchar *silkydir;

/* these will contain the glade file names and paths */
gchar *glade_silky_channel;
gchar *glade_silky_query;
gchar *glade_silky;

/* introduce some functions here */
void silky_start_cont (void);
void silky_start_firstrun (GtkWidget * widget, gpointer rbutton);
void silky_start_firstrun_go (void);

/* fixme: move these to a .h file */
gboolean firstrun_active;	/* used in runonce() */

extern xmlDocPtr config_main;
extern xmlDocPtr config_servers;
extern xmlDocPtr config_buddies;

extern GHashTable *mimetable;	/* parsed mime.types, from support.c */

extern SilkyChannel *channels;
void silky_keygen (SilkySilcKey *key);

gint debug_on = 0;

/***************************/



gboolean
silky_initialize ()
{
  gchar *string;
  gint ret;
  gchar *ui_key_passphrase;
  GtkWidget *widget;
  SilcClientParams params;

  debug_info("Initializing Silky");

  /* Number of maximum tasks the client library's scheduler can handle.
     If set to zero, the default value will be used (200). For WIN32
     systems this should be set to 64 as it is the hard limit dictated
     by the WIN32. */
#ifdef SILKY_WIN32
  params.task_max = 64;
#else
  params.task_max = 200;
#endif

  params.rekey_secs = 0; /* use default */
  params.connauth_request_secs = 0; /* use default */

  strncpy(params.nickname_format, "%n@%h%a", sizeof(params.nickname_format)-1);
  params.nickname_force_format = FALSE;
  params.nickname_parse = &silky_nickname_parse;
  params.ignore_requested_attributes = TRUE;


  /* Allocate silky structure */
  silky = silc_calloc (1, sizeof (*silky));
  if (!silky)
    {
      g_error ("Could not allocate memory for client");
    }

  /* Create the client */
  silky->client = silc_client_alloc (&ops, &params, silky, NULL);
  if (!silky->client)
    {
      g_error ("Could not allocate SILC client entity");
    }

#ifdef SILKY_WIN32
  /* We need to initialize network manually if running in Win32 */
  if (silc_net_win32_init () == FALSE)
    {
      debug_error ("Can not initialize Win32 network.");
      return FALSE;
    }
#endif


  /* Set user's info. Some environments don't provide username
     or some other parameter, so we just invent something.
     First we try to get stuff from user's preferences
   */

  debug_info ("Initializing the SILC Client Library");
  silky->client->username = silc_get_username ();
  if (!silky->client->username)
    {
      silky->client->username = "silkyuser";
    }
  debug_message ("SILC username: '%s'", silky->client->username);

  /* Set hostname */
  silky->client->hostname = silc_net_localhost ();	/* from OS */
  if (!silky->client->hostname)
    {
      silky->client->hostname = "localhost";	/* last resort */
    }
  debug_message ("SILC hostname: '%s'", silky->client->hostname);


  debug_info("Loading your configurations");
  if( !prefs_load_config() ) return FALSE;

  /* Set nickname */
  string = prefs_get("nickname");

  if ( !string || !g_utf8_strlen(string, -1) ) {		/* if unset */
      silky->client->nickname = silc_get_username ();	/* username from OS */
  } else {				/* if set */
      silky->client->nickname = g_strdup (string);
  }

  /* if both failed */
  if (!silky->client->nickname || g_utf8_strlen (silky->client->nickname, -1) < 1) {
      silky->client->nickname = "silkyuser";	/* last resort */
  }

  debug_message ("SILC nickname: '%s'", silky->client->nickname);

  if ( !string || !g_utf8_strlen(string, -1) ) {
      /* we got the nickname from somewhere else than from config, so let's
         store it there now */
      prefs_set("nickname", silky->client->nickname);
  }

  /* Set realname */
  string = prefs_get("realname");
  debug("realname from prefs: '%s'", string);

  if ( !string || strlen (string) < 1) {		/* if unset */
      silky->client->realname = silc_get_username ();	/* username from OS */
  }  else {				/* if set */
      silky->client->realname = g_strdup (string);
  }

  /* if both failed */
  if (silky->client->realname == NULL || strlen (silky->client->realname) < 1) {
      silky->client->realname = "Silky User";	/* last resort */
  }

  debug_message ("SILC realname: '%s'", silky->client->realname);

  if ( !string || !g_utf8_strlen(string, -1) )
    {
      /* we got the realname from somewhere else than from config, so let's
         store it there now */
      prefs_set("realname", silky->client->realname);
    }

  /* Store nickname here, it will be used later for nick comparison after user changed nick.
     Required by a bug/feature in the SILC Client Library, will be fixed later.
   */
  previous_nick = g_strdup (silky->client->nickname);


  /* init server list */
  if( !init_servers(serverspath) ) {
    debug("Could not initialize user's server list, trying site-wide list");
#ifdef SILKY_WIN32
    if( !init_servers(".\\data\\servers.conf") ) {
#else
    if( !init_servers(g_strconcat(GLADEDIR, "servers.conf", NULL)) ) {
#endif
      debug("Could not initialize site-wide server list, creating empty");
      config_servers = xml_create_config(CONFIG_SERVERS);
      xml_save_config(CONFIG_SERVERS, serverspath);
    }
  }

  /* init buddylist */
  if( !init_buddies(buddiespath) ) {
    debug("Could not initialize buddies list, creating empty.");
    config_buddies = xml_create_config(CONFIG_BUDDIES);
    xml_save_config(CONFIG_BUDDIES, buddiespath);
  }

  /*
     Initialize the client.
   */
  if (!silc_client_init (silky->client))
    {
      g_error ("Could not init client entity");
    }

  debug("Allocating RNG");
  /* When client has been created, init SilcRng */
  silky->rng = silc_rng_alloc();
  if (silky->rng == NULL)
  {
    g_error("Could not initialize RNG");
  }

  debug("Seeding RNG");
  silc_rng_init(silky->rng);


#ifdef SILKY_WIN32
  /* In Win32, it is required to add some seed material to SilcRng;
   * Windows just does not guarantee reasonable entropy for
   * cryptographic use
   */

  gchar *seedbuf, *sha1, *htime;
  GTimeVal gtv;
  SilcHash hash;
  gint i;

  debug_message("Windows does not provide enough entropy, re-seeding RNG");
  if (!silc_hash_register_default())
  {
    g_error("Failed to register hashes");
  }

  silc_hash_alloc("sha1", &hash); /* FIXME No check for success */
  htime = calloc(sizeof(gtv.tv_usec), sizeof(gchar));
  sha1 = calloc(20, sizeof(gchar));
  seedbuf = calloc(80, sizeof(gchar));

  /* Pull four rounds of current time, concatenate SHA1(usec) and
   * finally hash the result once more
   */
  for (i=0; i<= 3; i++) {
    debug("Round %d/4", i+1);

    /* Get current time */
    debug("Pulling microseconds from system clock...");
    g_get_current_time(&gtv);
    /* Use it for hashing */
    debug("Hashing...");
    memcpy(htime, &gtv.tv_usec, sizeof(gtv.tv_usec));
    silc_hash_make(hash, htime, sizeof(gtv.tv_usec), sha1);

    memcpy(seedbuf + i*20, sha1, 20);
  }

  debug("Hashing again...");
  silc_hash_make(hash, seedbuf, 80, sha1);
  silc_rng_add_noise(silky->rng, sha1, 20); /* Add entropy */
  debug("Added some entropy to RNG pool");

  /* Free instances */
  silc_hash_free(hash);
  g_free(seedbuf);
  g_free(sha1);
  g_free(htime);
#endif


  /* set logging callbacks for SILC client library */
  silc_log_set_callback (SILC_LOG_INFO, silky_silc_log_callback, NULL);
  silc_log_set_callback (SILC_LOG_WARNING, silky_silc_log_callback, NULL);
  silc_log_set_callback (SILC_LOG_ERROR, silky_silc_log_callback, NULL);
  silc_log_set_callback (SILC_LOG_FATAL, silky_silc_log_callback, NULL);

  if ( /* if any keys are missing ... */
      g_file_test (pubkey, G_FILE_TEST_EXISTS) == FALSE ||
      g_file_test (privkey, G_FILE_TEST_EXISTS) == FALSE)
    {

      /* show the first run dialog and ask some info from user, we'll create keys for him */
      debug ("showing the first run window for key creation...");
      ret = gtk_dialog_run (GTK_DIALOG(glade_xml_get_widget (xmlmain, "dialog_firstrun")));
      gtk_widget_hide (glade_xml_get_widget (xmlmain, "dialog_firstrun"));

      if (ret != GTK_RESPONSE_OK) {
	debug_info("User rejected the key, quitting.");
	return FALSE;
      }
      debug("returned from the dialog, hiding it");

      gtk_widget_hide(glade_xml_get_widget (xmlmain, "dialog_firstrun"));

      /* depending on the user's choise... */
      widget = glade_xml_get_widget (xmlmain, "keygen_radiobutton_automatic");
      if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)))
	{
	  debug_message("Manual key generation");

	  /* generate the keys */
	  while ( 1 )
	    {
	      /* run dialog */
	      ret = gtk_dialog_run(GTK_DIALOG(glade_xml_get_widget (xmlmain, "dialog_firstrun_manual")));
	      if (ret != GTK_RESPONSE_OK) {
		debug("user didn't press OK, returning and quitting.");
		return FALSE;
	      }

	      /* try generate the keys */
	      ret = silky_keygen_ui();
	      if (ret == -1) {
		debug("got -1, returning and quitting");
		return FALSE;
	      }
	      if (ret == 1) {
		debug("got 1, breaking and continuing");
		break;
	      }
	      debug("got 0, looping");

	    }

	  debug("returned, hiding it");
	  gtk_widget_hide(glade_xml_get_widget (xmlmain, "dialog_firstrun_manual"));
	}
      else {
	debug_message("Autogenerating the keys");

	/* generate the keys */
	while ( 1 )
	  {
	    ret = silky_keygen_ui();
	    if (ret == -1) {
	      debug("got -1, returning and quitting");
	      return FALSE;
	    }
	    if (ret == 1) {
	      debug("got 1, breaking and continuing");
	      break;
	    }
	    debug("got 0, looping");
	  }
      }
    }


  /* Try opening keys without passphrase */
  debug("Trying to load the keys without passphrase");
  ret =
    silc_load_key_pair (pubkey, privkey, "", &silky->client->pkcs,
			&silky->client->public_key,
			&silky->client->private_key);
  if (ret == TRUE)
    {
      debug_info ("User's key doesn't have a passphrase");
    }
  else
    {
      debug_info ("Asking passphrase for user's key");

      while ( 1) {
	debug ("now asking for the passphrase...");
	ret = gtk_dialog_run (GTK_DIALOG(glade_xml_get_widget(xmlmain, "dialog_loadkey_passphrase")));
	gtk_widget_hide(glade_xml_get_widget(xmlmain, "dialog_loadkey_passphrase"));
	/* select whole text in gui */
	gtk_entry_select_region(GTK_ENTRY((glade_xml_get_widget (xmlmain, "loadkey_passphrase"))), 0, -1);

	if (ret != GTK_RESPONSE_OK) {
	  debug("user didn't press OK, returning and quitting.");
	  return FALSE;
	}

	ui_key_passphrase =
	  g_strdup(gtk_entry_get_text(GTK_ENTRY (glade_xml_get_widget (xmlmain, "loadkey_passphrase"))));

	debug("re-trying to load the key, with passphrase");

	ret =
	  silc_load_key_pair (pubkey, privkey, ui_key_passphrase,
			      &silky->client->pkcs, &silky->client->public_key,
			      &silky->client->private_key);

	if (ret) {
	  debug("success, breaking and continuing");
	  break;
	}

	debug_info("Incorrect passphrase, asking again");
      }

    }


  return TRUE;

}









/* This will be run n-times per second from GTK's loop.
   Adjust rate from main() if needed.
*/

gboolean
silky_gtk_main ()
{
  if (silky && silky->client) {
    silc_client_run_one (silky->client);
  }

  return TRUE;
}


/*
 This gets called after GTK mainloop is quitted.
*/

int
main_quit (gpointer data)
{
  debug_info ("Quit handler called");
  save_buddies();

/* fixme; remove timers! */

  silc_client_free(silky->client);
  debug("client freed");

  silc_rng_free(silky->rng);
  debug("RNG freed");

  mime_types_list_destroy ();	/* free the mime.types list */

  debug_message ("Quitting, bye!");

  xmlFreeDoc (config_main);
  xmlFreeDoc (config_servers);

  return 0;			/* asks gtk to remove this call handler */
}






/*
   MAIN
*/
int
main (int argc, char *argv[])
{
  int c, option_index;
  gint optarg_int;
  static struct option long_options[] = {
    {"debug", 0, 0, 0},
    {"debug-level", 1, 0, 0},
    {0, 0, 0, 0}
  };

  debug_level = LOG_MESSAGE;
  gtk_init (&argc, &argv);
  glade_gnome_init ();

  /* get options */
  while (1) {
    option_index = 0;
    c = getopt_long (argc, argv, "dhv", long_options, &option_index);

    if (c == -1) {
      break;
    }

    switch (c) {
    case 0:
      debug_message ("long option '%s'", long_options[option_index].name);
      if (optarg)
	debug_message  (" with arg '%s'", optarg);

      if (option_index == 0) { /* debug */
	debug_on = 1;
	debug_message  ("* debugging turned on");
      }
      if (option_index == 1) {
	if (!debug_on) { /* could be turned on earlier */
	  debug_on = 1;
	  debug_message ("* debugging turned on");
	}
	optarg_int = strtol(optarg, NULL, 10);
	debug_message  ("* debug_level = '%d'", optarg_int);
	debug_level = optarg_int;
      }

      break;

    case 'd':
      if (!debug_on) { /* could be turned on earlier */
	debug_on = 1;
	debug_message ("* debugging turned on");
      }
      break;

    case 'v':
      printf ("%s version %s\n", argv[0], SILKY_VERSION);
      return 0;
      break;

    case 'h':
    default:
      printf (_("Usage: %s [-h][-v][-d][--debug-level=N]\n"), argv[0] );
      printf (_("\t-h : Shows this help page\n"));
      printf (_("\t-v : Shows the version number\n"));
      printf (_("\t-d : Enables debugging with level 0\n"));
      printf (_("\t--debug-level=N : Enables debugging with level N\n"));
      printf (_("\tSupported debug levels: 0,1 and 2\n"));
      printf (_("\tError messages are always printed, regardless of debug options.\n"));
      return -1;
    }

  }

  /* this prints all "left over" parameters
  if (optind < argc) {
    printf ("non-option ARGV-elements: ");
    while (optind < argc)
      printf ("%s ", argv[optind++]);
    printf ("\n");
  }
  */


  /*
     Redirect STDERR of libglade
  */
    g_log_set_handler ("libglade", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL
                     | G_LOG_FLAG_RECURSION, silky_print_stderr, NULL);


  /* init threads, used for creating the keys */
  if (!g_thread_supported ())
    {
      g_thread_init (NULL);
    }
  else
    {
      g_error ("Silky needs threads, your system doesn't support threads.\n");
    }



  /* init Silky stuff */
  homedir = silky_get_homedir ();
  silkydir = silky_get_silkydir (homedir);
  if (!silky_create_silkydir (silkydir))
    {
      g_error ("Can not create '%s'. Can not continue.\n", silkydir);
    }
#ifdef SILKY_WIN32
  pubkey = g_strconcat (silkydir, "\\silky.pub", NULL);
  privkey = g_strconcat (silkydir, "\\silky.prv", NULL);
  prefpath = g_strconcat (silkydir, "\\silky.conf", NULL);	/* global variable prefpath */
  serverspath = g_strconcat (silkydir, "\\servers.conf", NULL);	/* global variable serverspath */
  buddiespath = g_strconcat (silkydir, "\\buddies.conf", NULL);	/* global variable serverspath */
  serverkeypath = g_strconcat (silkydir, "\\serverkeys\\", NULL);
  clientkeypath = g_strconcat (silkydir, "\\clientkeys\\", NULL);
#else
  pubkey = g_strconcat (silkydir, "silky.pub", NULL);
  privkey = g_strconcat (silkydir, "silky.prv", NULL);
  prefpath = g_strconcat (silkydir, "silky.conf", NULL);	/* global variable prefpath */
  serverspath = g_strconcat (silkydir, "servers.conf", NULL);	/* global variable serverspath */
  buddiespath = g_strconcat (silkydir, "buddies.conf", NULL);	/* global variable serverspath */
  serverkeypath = g_strconcat (silkydir, "serverkeys", NULL);
  clientkeypath = g_strconcat (silkydir, "clientkeys", NULL);
#endif
  if (!silky_create_serverkeydir (serverkeypath))
    {
      g_error ("Can not create '%s'. Can not continue.\n", serverkeypath);
    }
  if (!silky_create_serverkeydir (clientkeypath))
    {
      g_error ("Can not create '%s'. Can not continue.\n", clientkeypath);
    }


#ifdef ENABLE_NLS
  debug_info("Translations enabled");
  setlocale (LC_ALL, "");
  bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);
#endif

  if (!silky_read_mimetable ()) {
    g_error ("Can not find mime.types file. Can not continue.\n");
  }

  /* set paths for glade files */
  glade_silky = g_strdup ("silky.glade");
  glade_silky_channel = g_strdup ("silky-channel.glade");
  glade_silky_query = g_strdup ("silky-query.glade");

  /* first try from local dir, then under GLADEDIR (Makefile.am) */
  if (g_file_test (glade_silky, G_FILE_TEST_EXISTS) == FALSE) {
      debug ("glade files not found in current dir...");
      g_free (glade_silky);
      g_free (glade_silky_channel);
      g_free (glade_silky_query);
      glade_silky = g_strdup (GLADEDIR "silky.glade");
      glade_silky_channel = g_strdup (GLADEDIR "silky-channel.glade");
      glade_silky_query = g_strdup (GLADEDIR "silky-query.glade");
      debug ("using files from installation directory");
  } else {
    debug ("glade files were found in current dir");
  }


  /* add quit handler, gets called after GTK quits */
  gtk_quit_add (0, main_quit, NULL);

  /*
     get xml for main window
     "xmlmain" can be used after these lines everywhere
   */
  xmlmain = glade_xml_new (glade_silky, NULL, NULL);
  if (!xmlmain) {
    g_error ("Can not load glade file '%s'\n", glade_silky);
  } else {
    debug_info ("Loaded .glade file '%s'", glade_silky);
  }

  debug ("scheduling silky_gtk_main");

  if (!g_timeout_add (200, silky_gtk_main, NULL)) { /* in timer.c */
    g_error ("wasn't able to schedule silky_gtk_main\n");
  }

  debug("calling silky_initialize ()");
  if (!silky_initialize ()) {
    g_error("Can not initialize Silky. Can not continue.");
  }


  debug ("connecting gtk signals");
  /* connect signals to their respective handlers */
  glade_xml_signal_autoconnect (xmlmain);

  debug ("scheduling silky_ping");
  /* set lag detector, once per two seconds */
  if (!g_timeout_add (10000, silky_ping, NULL))
    {				/* in timer.c */
      g_error ("wasn't able to schedule silky_ping\n");
      /* quitting here */
    }

  silky_gui_show_main();

  /* give control to GTK */
  debug_info ("Starting the main loop");

  gtk_main();

  /* we never reach this point, see _quit function */
  return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1