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

    - Various auxiliary functions

    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 <silcincludes.h>
#include <silcclient.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include <glib/gprintf.h>
#include <errno.h>

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

#include "keys.h"
#include "log.h"
#include "commands.h"
#include "support.h"
#include "preferences.h"
#include "gui.h"

#include "ui_console.h"
#include "ui_channel.h"

#include "common.h"

GHashTable *mimetable; /* parsed mime.types */

extern GladeXML *xmlmain;
extern silkyStruct *silky;


extern gchar *homedir;
extern SilkyChannel *channel_active;
extern SilkyQuery *query_active;

/* Returns HH:MM */
gchar * utf_timestamp (void) {
  time_t curtime;
  struct tm *loctime;
  gchar *utf_time;
  curtime = time(0);
  loctime = localtime (&curtime);
  utf_time = g_strdup_printf("%02d:%02d", loctime->tm_hour, loctime->tm_min);

  return utf_time;
}












/* marks active the channel tab of given channel ch,
   called usually when some text was printed there and
   user needs to be notified about activity in given channel struct
*/
void hilight_channel_window_by_ch(SilkyChannel *ch, gint type) {
  if (ch == channel_active) {
    debug("channel is active, doing nothing");
    return;
  }


  switch (type) {
  case HILIGHT_MOTION: /* wtf? */
      debug("HILIGHT_MOTION");
      gtk_label_set_markup( (gpointer)ch->label_text, g_strconcat("<b>", CHANNEL_NAME(ch), "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)ch->label_image, GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON );

      break;

  case HILIGHT_SPEECH: /* any public message */
      debug("HILIGHT_SPEECH");
      gtk_label_set_markup( (gpointer)ch->label_text, g_strconcat("<b>", CHANNEL_NAME(ch), "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)ch->label_image, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_BUTTON );

      break;

  case HILIGHT_WORD: /* wordlist match */
      debug("HILIGHT_WORD");
      gtk_label_set_markup( (gpointer)ch->label_text, g_strconcat("<b>", CHANNEL_NAME(ch), "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)ch->label_image, GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_BUTTON );

      break;

  case HILIGHT_NICK: /* my nick match */
      debug("HILIGHT_NICK");
      gtk_label_set_markup( (gpointer)ch->label_text, g_strconcat("<b>", CHANNEL_NAME(ch), "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)ch->label_image, GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_BUTTON );

      break;

  default:
      debug("default hilight, this should not happen?");
      break;
  }
}



/* marks active the query tab of given channel ch,
   called usually when some text was printed there and
   user needs to be notified about activity in given channel struct
*/
void hilight_query_window_by_q(SilkyQuery *q, gint type) {
  if (q == query_active) {
    debug("query is active, doing nothing");
    return;
  }


  switch (type) {
  case HILIGHT_MOTION: /* wtf? */
      debug("HILIGHT_MOTION");
      gtk_label_set_markup( (gpointer)q->label_text, g_strconcat("<b>", q->client_entry->nickname, "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)q->label_image, GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON );

      break;

  case HILIGHT_SPEECH: /* any public message */
      debug("HILIGHT_SPEECH");
      gtk_label_set_markup( (gpointer)q->label_text, g_strconcat("<b>", q->client_entry->nickname, "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)q->label_image, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_BUTTON );

      break;

  case HILIGHT_WORD: /* wordlist match */
      debug("HILIGHT_WORD");
      gtk_label_set_markup( (gpointer)q->label_text, g_strconcat("<b>", q->client_entry->nickname, "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)q->label_image, GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_BUTTON );

      break;

  case HILIGHT_NICK: /* my nick match */
      debug("HILIGHT_NICK");
      gtk_label_set_markup( (gpointer)q->label_text, g_strconcat("<b>", q->client_entry->nickname, "</b>", NULL));
      gtk_image_set_from_stock( (gpointer)q->label_image, GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_BUTTON );

      break;

  default:
      debug("default hilight, this should not happen?");
      break;
  }
}
void add_user_to_nicklist (GtkListStore *liststore, SilcChannelEntry channel, SilcChannelUser user) {
  GtkTreeIter iter, i;
  gchar *nick = NULL;
  int x = 0, pos = 0;

  if (!liststore) {
    debug("Missing liststore");
    return;
  }
  if (!channel) {
    debug("Missing channel");
    return;
  }
  if (!user) {
    debug("Missing user");
    return;
  }

  /* new line, sort alphabetically */
  if( gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &i) ) {
    do {
      gtk_tree_model_get(GTK_TREE_MODEL(liststore), &i, 2, &nick, -1);
      if( g_utf8_collate( g_utf8_casefold(user->client->nickname, 256), g_utf8_casefold(nick, 256) ) <= 0) {
	g_free(nick);
	gtk_list_store_insert_before(liststore, &iter, &i);
	x++;
      } else pos++;
    } while ( gtk_tree_model_iter_next(GTK_TREE_MODEL(liststore), &i) && !x );
  }

  if (!x) gtk_list_store_append(liststore, &iter);

  debug("Added new line to liststore");
  debug("Adding nick '%s'", user->client->nickname);

  channel_member_add(channel_find_by_entry(channel), user);


  /* founder column*/
  if (user->mode & SILC_CHANNEL_UMODE_CHANFO) {
    gtk_list_store_set (liststore, &iter,
			0,  "*", -1);
  }

  /* muted? using same column as founder can not be muted :) */
  if (user->mode & SILC_CHANNEL_UMODE_QUIET) {
    gtk_list_store_set (liststore, &iter,
			0,  "&", -1);
  }

  /* chanop column */
  if (user->mode & SILC_CHANNEL_UMODE_CHANOP) {
    gtk_list_store_set (liststore, &iter,
			1,  "@", -1);
  }

  /* nickname column */
  gtk_list_store_set (liststore, &iter,
		      2,  user->client->nickname, -1);

  /* clientid column */
  gtk_list_store_set (liststore, &iter,
		      3 , user->client->id, -1);

  debug("--------------");
}






/* delete the list, call this just before exiting Silky */
void mime_types_list_destroy (void) {
  debug("mime_types_list_destroy()");
  if (!mimetable) {
    return;
  }

  g_hash_table_destroy(mimetable);

}

gchar *mime_types_get_type(gchar *filename) {
  gchar *extension;

  debug ("mime_types_get_type()");
  if (!mimetable || !filename) {
    debug ("No mimetable or filename specified");
    return NULL;
  }

  extension = strrchr(filename, '.');
  if (!extension || *(extension+1) == 0) {
    debug("unknown extension");
    return NULL;
  }

  extension++;
  return g_hash_table_lookup(mimetable, extension);

}



/* read given mime.types file and initialize the hash */
GHashTable *mime_init(gchar *file) {
  FILE *f1;
  gchar str[4096];
  GHashTable *mimetable;

  debug("mime_init()");

  f1 = fopen(file, "r");
  if (!f1) {
    debug("file open failed for '%s'", file);
    return NULL;
  }

  /* init the table */
  mimetable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);

  if (!mimetable) {
    debug("was not able to initialize mimetable. out of mem?");
    return NULL;
  }

  /* parse the mime.types file */
  while (fgets(str, sizeof(str), f1)) {
    gchar *key, *value;
    gchar *tmp;
    //    gchar buf[4096];

    // ignore lines starting with '#'
    if (str[0] == '#') {
      continue;
    }

    // ignore empty lines
    if (strlen(str) < 3) {
      continue;
    }

    str[strlen(str) - 1] = 0; /* nullify the end of the string */

    tmp = strrchr(str, '\t'); /* ?? */
    if (!tmp) {
      continue;
    }

    *tmp = 0;
    tmp++;
    if (*tmp == 0) {
      continue;
    }

    key = tmp;

    tmp = strchr(str, '\t'); /* ?? */
    if (!tmp) {
      continue;
    }

    *tmp = 0;

    value = str;

    tmp = strtok(key, " ");
    if (!tmp) {
      g_hash_table_insert(mimetable, strdup(key), strdup(value));
    }  else {
      g_hash_table_insert(mimetable, strdup(tmp), strdup(value));

      while ( ( tmp = strtok(NULL, " ") )) {
        g_hash_table_insert(mimetable, strdup(tmp), strdup(value));
      }

    }
  }

  return mimetable;
}

/* called when an event was triggered for a <nickname> in the text view */
gboolean context_nick_event(GtkTextTag *texttag, GObject *widget, GdkEvent *event, GtkTextIter *iter, gpointer user_data) {
  GtkMenu *menu;
  GtkWidget *menuitem;
  SilcClientID *clientid;
  gchar *target_nick = NULL;
  GtkTextIter *start_iter, *end_iter;
  GtkTextBuffer *text_buffer;

  if (event->type == GDK_BUTTON_PRESS || event->type == GDK_2BUTTON_PRESS) {

    /* Get the nickname from the text */
    start_iter = gtk_text_iter_copy (iter);
    end_iter = gtk_text_iter_copy (iter);
    while (!gtk_text_iter_begins_tag (start_iter, texttag)) {
      gtk_text_iter_backward_char (start_iter);
    }
    while (!gtk_text_iter_ends_tag (end_iter, texttag)) {
      gtk_text_iter_forward_char (end_iter);
    }

    text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
    target_nick = gtk_text_buffer_get_text (text_buffer, start_iter, end_iter, FALSE);

    if (!target_nick) {
      debug("no tagtext found?!");
    }

    gtk_text_iter_free (start_iter);
    gtk_text_iter_free (end_iter);

  }


  /* single right-click */
  if (target_nick && event->type == GDK_BUTTON_PRESS && event->button.button == 3 ) {
    debug ("right-clicked on '%s'", target_nick );

    // temp, should get one from the tags
    clientid = silky->conn->local_entry->id;

    /* create the menu */
    menu = GTK_MENU(gtk_menu_new());

    /* ADD MENU items and signal handlers*/

    /* WHOIS */
    menuitem = gtk_menu_item_new_with_label(_("Who is"));
    g_signal_connect(menuitem, "activate", G_CALLBACK(handle_command_callback), g_strdup_printf("WHOIS %s", target_nick));
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show (menuitem);

    /* send a message */
    menuitem = gtk_menu_item_new_with_label(_("Send message"));
    g_signal_connect(menuitem, "activate", G_CALLBACK(handle_command_callback), g_strdup_printf("QUERY %s", target_nick));
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show (menuitem);

    /* a separator here */
    menuitem = gtk_separator_menu_item_new();
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show(menuitem);

    /* cumode +o user */
    menuitem = gtk_menu_item_new_with_label(_("Op"));
    g_signal_connect(menuitem, "activate", G_CALLBACK(handle_command_callback), g_strdup_printf("OP %s", target_nick));
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show(menuitem);

    /* cumode -o user */
    menuitem = gtk_menu_item_new_with_label(_("Deop"));
    g_signal_connect(menuitem, "activate", G_CALLBACK(handle_command_callback), g_strdup_printf("DEOP %s", target_nick));
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show(menuitem);

    /* cumode +q user */
    menuitem = gtk_menu_item_new_with_label(_("Mute"));
    g_signal_connect(menuitem, "activate", G_CALLBACK(handle_command_callback), g_strdup_printf("MUTE %s", target_nick));
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show(menuitem);

    /* cumode -q user */
    menuitem = gtk_menu_item_new_with_label(_("Unmute"));
    g_signal_connect(menuitem, "activate", G_CALLBACK(handle_command_callback), g_strdup_printf("UNMUTE %s", target_nick));
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show(menuitem);

    /* a separator here */
    menuitem = gtk_separator_menu_item_new();
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show(menuitem);

    /* kick user */
    menuitem = gtk_menu_item_new_with_label(_("Kick"));
    g_signal_connect(menuitem, "activate", G_CALLBACK(handle_command_callback), g_strdup_printf("KICK %s", target_nick));
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    gtk_widget_show(menuitem);


    /* kill user */
    /* FIXME, the on_command_kill needs some work
       menuitem = gtk_menu_item_new_with_label(_("Kill"));
       g_signal_connect(menuitem, "activate", G_CALLBACK(on_command_kill), clientid);
       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
       gtk_widget_show(menuitem);
    */

    /* show the menu */
    gtk_menu_popup (menu, NULL, NULL, NULL, NULL,
		    event->button.button, event->button.time);

  }

  /* right button released, should hide a context sensitive menu if it was visible */
  if (event->type == GDK_BUTTON_RELEASE && event->button.button == 3 ) {
    debug ("Released right-click");
  }

  /* double left-click */
  if (target_nick && event->type == GDK_2BUTTON_PRESS && event->button.button == 1 ) {
    debug ("Double left-clicked on '%s'", target_nick);
    handle_command(g_strdup_printf("QUERY %s", target_nick));
  }

  g_free(target_nick);
  return TRUE;
}

SilcChannelUser get_channel_user_by_entry(SilcChannelEntry channel, SilcClientEntry client) {
  SilcChannelUser chu;
  SilcHashTableList htl;

  if( !channel ) {
    debug("channel == NULL");
    return NULL;
  }

  if (!channel->user_list) {
    debug("no ulist, returning");
    return NULL;
  }

  debug("calling silc_hash_table_list()");
  silc_hash_table_list(channel->user_list, &htl);
  debug("looping");

  while(silc_hash_table_get(&htl, NULL, (void *)&chu)) {
    if( client == chu->client ) return chu;
  }
  return NULL;
}



/*
  Tries to initialize mimetable.
*/
gboolean silky_read_mimetable() {

  gint i;

  /*
    First NULL is reserved for later use..
  */
  gchar *mimefiles[] = {
    NULL,
    "/etc/mime.types",		/* default	*/
    "/usr/pkg/etc/mime.types",	/* pkgsrc	*/
    "/usr/lib/mime.types",		/* IRIX 6.5	*/
    "/usr/local/etc/mime.types",	/* default?	*/
    "mime.types",			/* Current dir	*/
    NULL
  };

  if (!homedir) {
    g_error("No homedir\n");
  }

#ifdef SILKY_WIN32
  mimefiles[0] = g_strconcat(homedir, "\\mime.types", NULL);
#else
  mimefiles[0] = g_strconcat(homedir, "/.mime.types", NULL);
#endif

  /* Check all paths from mimefiles to find working "mime.types" file, stops on first one */
  for (i=0; mimefiles[i]; i++) {
    debug("trying %d: '%s'", i, mimefiles[i]);
    mimetable = mime_init(mimefiles[i]);
    if (mimetable) {
      break;
    }
  }

  /* We didn't find any mime.table: give error and quit */
  if (!mimetable) {
    debug("mime.types not found at all");
    return FALSE;
  }
  else {
      debug("found mime.types. Hurray!");
    return TRUE;
  }

}

gchar *silky_get_homedir() {
  gchar *homedir;

  homedir = g_strdup(g_get_home_dir());
  if (!homedir) {
    debug("homedir not found, can not continue");
    return NULL;
  }
  else {
    return homedir;
  }

}

/*
  returns full path to user's silkydir
*/

gchar *silky_get_silkydir(gchar *homedir) {

#ifdef SILKY_WIN32
  return g_strconcat(homedir, "\\silky", NULL);
#else
  return g_strconcat(homedir, "/.silky/", NULL);
#endif

}


/*
  Create silkydir.
*/
gboolean silky_create_silkydir(gchar *silkydir) {
  gint ret;

  if( g_file_test(silkydir, G_FILE_TEST_EXISTS) ) {
    if( !g_file_test(silkydir, G_FILE_TEST_IS_DIR) ) {
      debug("Error: '%s' exists, but is NOT a directory. Aborting", silkydir);
      return FALSE;
    }
    debug("'%s' exists and is a directory, continuing", silkydir);
    return TRUE;
  }

  debug("Directory '%s' does not exist, trying to create it", silkydir);

#ifdef SILKY_WIN32
  ret = mkdir(silkydir);
#else
  ret = mkdir(silkydir, 0700);
#endif

   if (ret == -1) {
    debug("error creating '%s' (error %d: %s)", silkydir, errno, strerror(errno));

    if (errno == EEXIST) {
      debug("'%s' exists, using it", silkydir);
    }
    else {
      debug("Can not create '%s'", silkydir);
      return FALSE;
    }
  }
  else {
    debug ("'%s' created", silkydir);
  }

   return TRUE;

}


/*
  creates serverkeydir
*/
gboolean silky_create_serverkeydir(gchar *serverkeydir) {
  gint ret;

  debug("trying to create '%s'", serverkeydir);
  if (!serverkeydir) return FALSE;

#ifdef SILKY_WIN32
  ret = mkdir(serverkeydir);
#else
  ret = mkdir(serverkeydir, 0700);
#endif
  if (ret == -1) {
    debug("error creating '%s' (error %d: %s)", serverkeydir, errno, strerror(errno));

    if (errno == EEXIST) {
      debug("'%s' exists, using it", serverkeydir);
    }
    else {
      g_error("Can not create '%s'\n", serverkeydir);
      return FALSE;
    }
  }
  else {
    debug ("'%s' created", serverkeydir);
  }

  return TRUE;

}

/* fixme: is this, and why, needed? */
void silky_nickname_parse(const char *nickname, char **ret_nickname) {
  //  strncpy (&ret_nickname, nickname, sizeof(&ret_nickname)-1);
  debug("got '%s'", nickname);
  *ret_nickname = strdup(nickname);
}

/* Returns a random positive number between 'min' and 'max-1' */
unsigned int get_random_number(const unsigned int min,
    const unsigned int max) {
  unsigned int range, ret;
  SilcUInt32 random;

  /* Largish random number */
  random = silc_rng_get_rn32(silky->rng);

  /* Random number in range of given values */
  range = max - min;
  ret = random % range;

  return ret + min;
}


/* inputs null, returns active textview widget */
void * silky_get_active_textview () {
  GtkTextView *textview = NULL;
  GtkNotebook *storednotebook = NULL;
  GladeXML *stored_xml;
  GtkWidget *active_page = NULL;
  
  storednotebook = GTK_NOTEBOOK(glade_xml_get_widget (xmlmain, "tabs"));
  active_page = gtk_notebook_get_nth_page(storednotebook, gtk_notebook_get_current_page(GTK_NOTEBOOK(storednotebook) ) ); /* active page from stored g_object*/
  stored_xml = glade_get_widget_tree(active_page);
  textview = GTK_TEXT_VIEW(glade_xml_get_widget (stored_xml, "textviewwidget"));
  return textview;
}

int verify_message_signature(SilcClientEntry sender, SilcMessageSignedPayload sig, SilcMessagePayload message) {
  SilcPublicKey pk, cached_pk = NULL;
  const gchar *filename;
  gchar *fingerprint, *fingerprint2;

  unsigned char *pk_data;
  SilcUInt32 pk_datalen;

  struct stat st;
  int ret = SILC_MSG_SIGNED_VERIFIED;

  if( !sig ) return SILC_MSG_SIGNED_UNKNOWN;

  /* get public key from the signature payload and compare it with the
     one stored in the client entry */
  pk = silc_message_signed_get_public_key(sig, &pk_data, &pk_datalen);

  if( pk ) {
    fingerprint = silc_fingerprint(pk_data, pk_datalen);

    if (sender->fingerprint) {
      fingerprint2 = silc_fingerprint(sender->fingerprint, sender->fingerprint_len);
      if (strcmp(fingerprint, fingerprint2)) {
        /* since the public key differs from the senders public key, the 
           verification _failed_ */
        silc_pkcs_public_key_free(pk);
        silc_free(fingerprint);
        ret = SILC_MSG_SIGNED_UNKNOWN;
      }
      silc_free(fingerprint2);
    }
  } else if( sender->fingerprint ) {
    fingerprint = silc_fingerprint(sender->fingerprint, sender->fingerprint_len);
  } else
    /* no idea, who or what signed that message ... */
    return SILC_MSG_SIGNED_UNKNOWN;

  /* search our local client key cache */
  filename = gen_keyfile_path(fingerprint, "client");
  silc_free(fingerprint);

  if( stat(filename, &st) < 0 )
    /* we don't have the public key cached ... use the one from the sig */
    ret = SILC_MSG_SIGNED_UNKNOWN;
  else {
    /* try to load the file */
    if (!silc_pkcs_load_public_key(filename, &cached_pk, SILC_PKCS_FILE_PEM) && 
        !silc_pkcs_load_public_key(filename, &cached_pk,
				    SILC_PKCS_FILE_BIN)) {
      debug("Couldn't load client key.");
      if( !pk ) return SILC_MSG_SIGNED_UNKNOWN;
        else ret = SILC_MSG_SIGNED_UNKNOWN;
    }

    if( cached_pk ) {
      if( pk ) silc_pkcs_public_key_free(pk);
      pk = cached_pk;
    }
  }

  /* the public key is now in pk, our "level of trust" in ret */
  if( (pk) && silc_message_signed_verify(sig, message, pk, silky->client->sha1hash)!= SILC_AUTH_OK )
    ret = SILC_MSG_SIGNED_FAILED;

  if( pk )
    silc_pkcs_public_key_free(pk);

  return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1