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

    - Handlers for SILC_NOTIFY_* server messages

    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>

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

#include "ui_buddylist.h"
#include "ui_channel.h"
#include "ui_console.h"
#include "common.h"
#include "support.h"
#include "gui.h"
#include "silc-notify.h"
#include "log.h"
#include "channel.h"
#include "callbacks.h"

extern silkyStruct *silky;
extern GladeXML *xmlmain;
extern gchar *previous_nick; /* my nickname just before sending NICK command */

extern gchar * glade_silky_channel;
extern gchar * glade_silky_channel_label;


extern SilkyChannel *channels;

extern gint lastpong;

void silc_notify(SilcClient client, SilcClientConnection conn,
                        SilcNotifyType type, ...)
{
  SilcIdType idtype;
  void *entry;
  SilcClientEntry client_entry;
  SilcClientEntry client_entry2;
  SilcChannelEntry channel_entry = NULL;
  SilcClientID *client_id;
  SilcStatus status;
  char *str;
  gchar *str_clientid;
  gchar *str_fingerprint;
  GtkNotebook *storednotebook;
  gchar *str_client_id;
  gchar *str_client_id2;
  GtkNotebook *notebook;
  GtkWidget *stored_query_widget;
  GtkWidget *wid_tab_label;
  GladeXML *stored_query_xml;
  GtkLabel *label_nickname;
  GtkWidget *wid_channel_topic;
  SilcUInt32 mode;
  gchar *changer = NULL;
  SilcServerEntry serverentry;
  SilcChannelEntry channelentry;
  gchar *str_mode;
  SilcHashTableList souserhtl;
  SilcChannelUser chu = NULL;
  SilcBuffer channel_pubkeys;
  SilcNotifyType notification;

  va_list va;
  debug("silc_notify()");
  va_start(va, type);

  lastpong = time(NULL); /* update date of last received data, used in timer.c */

  /*
    This is the part where we receive all kind of different data from the
    server. silky is only interested in receiving the less important
    stuff...
  */
  switch(type) {

  case SILC_NOTIFY_TYPE_NONE:
    debug_info("SILC_NOTIFY_TYPE_NONE");
    str = va_arg(va, char *);
    printconsole(g_strdup_printf("%s", str));
    break;

  case SILC_NOTIFY_TYPE_MOTD:
    debug_info("SILC_NOTIFY_TYPE_MOTD");
    str = va_arg(va, char *);
    printconsole(g_strdup_printf("%s", str));
    break;

  case SILC_NOTIFY_TYPE_ERROR:
    status = va_arg(va, int );
    debug_info("SILC_NOTIFY_TYPE_ERROR (%d)", status);

    switch(status) {

      /* After sending a private message; There was no such user */
    case SILC_STATUS_ERR_NO_SUCH_CLIENT_ID:
      debug_info("No such client id");
      client_id = va_arg(va, SilcClientID *);
      if (!client_id) {
	debug("Did not get client_id in _NO_SUCH_CLIENT_ID");
      }

      str_clientid = silc_id_id2str(client_id, SILC_ID_CLIENT);

      client_entry = silc_client_get_client_by_id(silky->client, silky->conn, client_id);

      if (!client_entry) {
	debug("Did not find client_entry");
      }
      else {
	debug_info("\tnick: '%s'", client_entry->nickname);
      }

      printconsole(_("No such user."));
      break;

    default:
      debug("Unhandled error");

      break;

    }

    break;


  case SILC_NOTIFY_TYPE_KICKED:
    debug_info("SILC_NOTIFY_TYPE_KICKED");
    client_entry = va_arg(va, SilcClientEntry);
    str = va_arg(va, char *);
    client_entry2 = va_arg(va, SilcClientEntry);
    channel_entry = va_arg(va, SilcChannelEntry);

    if (SILC_ID_COMPARE(client_entry->id, conn->local_entry->id, sizeof(SilcClientID) ) ) {
      /* I GOT KICKED */
      debug_info("I have been kicked out from the channel '%s'", channel_entry->channel_name);

      /* Close the tab in UI */
      storednotebook = GTK_NOTEBOOK(glade_xml_get_widget (xmlmain, "tabs"));
      gtk_widget_destroy(get_stored_channel_window(channel_entry->channel_name));
      g_object_set_data(G_OBJECT(storednotebook), channel_entry->channel_name, NULL);

      /* check that all tabs are associated with correct data structures, since
	 their numbers may have changed */
      gui_renumber_tabs();

      /* remove from ch */
      channel_remove_by_entry(channel_entry);

      printconsole(g_strdup_printf(_("%s kicked me from channel '%s' with reason '%s'."),
		   client_entry2->nickname,
		   channel_entry->channel_name,
		   str));
    }
    else {
      printchannel(channel_entry->channel_name, g_strdup_printf(_("%s kicked %s from from channel '%s' with reason '%s'."),
								client_entry2->nickname, /* kicker */
								client_entry->nickname, /* target */
								channel_entry->channel_name,
								str));

      debug("freeing channel member from list...");
      channel_member_remove_by_entry(channel_find_by_entry(channel_entry), client_entry);

      /* refresh the nicklist */
      refresh_gui_nicklist(channel_find_by_entry(channel_entry));

    }

    break;

  case SILC_NOTIFY_TYPE_JOIN:
    debug_info("SILC_NOTIFY_TYPE_JOIN");

    client_entry = va_arg(va, SilcClientEntry);
    channel_entry = va_arg(va, SilcChannelEntry);
    debug_info("'%s' (%s@%s) joined '%s'",  client_entry->nickname, client_entry->username, client_entry->hostname, channel_entry->channel_name);
    printchannel(channel_entry->channel_name, g_strdup_printf(_("%s (%s@%s) has joined %s"),  client_entry->nickname, client_entry->username, client_entry->hostname, channel_entry->channel_name));
    /* not yet fully implemented, disabled for now
    silky_print(MSG_MOTION | MSG_PUBLIC, channel_entry, "%s joined %s", client_entry->nickname, channel_entry->channel_name);
    */

    /* Did I join? */
    if (!SILC_ID_COMPARE(client_entry->id, conn->local_entry->id, sizeof(SilcClientID) ) ) {
      debug("Added user to the nicklist.");
      channel_member_add(channel_find_by_entry(channel_entry), get_channel_user_by_entry(channel_entry, client_entry));

      refresh_gui_nicklist(channel_find_by_entry(channel_entry));

    }
    else {
      debug("I joined. No need to add myself to the userlist.");
    }


    break;

  case SILC_NOTIFY_TYPE_LEAVE:
    debug_info("SILC_NOTIFY_TYPE_LEAVE");
    client_entry = va_arg(va, SilcClientEntry);
    channel_entry = va_arg(va, SilcChannelEntry);

    debug_info("'%s' (%s@%s) left '%s'",  client_entry->nickname, client_entry->username, client_entry->hostname, channel_entry->channel_name);
    printchannel(channel_entry->channel_name, g_strdup_printf(_("%s (%s@%s) left %s"), client_entry->nickname, client_entry->username, client_entry->hostname, channel_entry->channel_name));

    debug("freeing channel member from list...");
    channel_member_remove_by_entry(channel_find_by_entry(channel_entry), client_entry);

    /* Ask the client library to re-resolve the channel */
    refresh_gui_nicklist(channel_find_by_entry(channel_entry));

    break;

  case SILC_NOTIFY_TYPE_NICK_CHANGE:
    debug("SILC_NOTIFY_TYPE_NICK_CHANGE");

    client_entry = va_arg(va, SilcClientEntry);
    client_entry2 = va_arg(va, SilcClientEntry);

	str_client_id = silc_id_id2str(client_entry->id, SILC_ID_CLIENT);
	str_client_id2 = silc_id_id2str(client_entry2->id, SILC_ID_CLIENT);

    debug_info("NICK_CHANGE: '%s' is now known as '%s'",
	  client_entry->nickname, client_entry2->nickname);


    /* Update the clientid and nickname of the user to the GUI */

    /* get notebook widget */
	notebook = GTK_NOTEBOOK(glade_xml_get_widget (xmlmain, "tabs"));

    /* get query widget, if any  */
	stored_query_widget = g_object_get_data(G_OBJECT(notebook), str_client_id);
    if (stored_query_widget) {



      /* remove the old clientid */
      g_object_set_data(G_OBJECT(notebook), str_client_id, NULL);

      /* make the new clientid to point to the old query widget */
      g_object_set_data(G_OBJECT(notebook), str_client_id2, stored_query_widget);
      g_object_set_data(G_OBJECT(stored_query_widget), "str_clientid", str_client_id2);
      g_object_set_data(G_OBJECT(stored_query_widget), "clientid", client_entry2->id);

      g_object_set_data(G_OBJECT(stored_query_widget), "cliententry", client_entry2);

      /* get the tab widget for nickname */
	wid_tab_label = g_object_get_data(G_OBJECT(stored_query_widget), "tablabel");

      if (!wid_tab_label) {
	debug("Did not find wid_tab_label");
      }

      /* Update the nickname into the top of query window */
	stored_query_xml = glade_get_widget_tree(stored_query_widget);
	label_nickname =  GTK_LABEL(glade_xml_get_widget (stored_query_xml, "query_nickname"));
      gtk_label_set_text(label_nickname, client_entry2->nickname);

      /* Update the nickname into the tab label */
      gtk_label_set_text(GTK_LABEL(wid_tab_label), client_entry2->nickname);

    }
    else {
      debug("Did not get stored_query_widget. This means I have no query open with the user who just changed his nick.");
    }

    /* FIXME: update other widgets too */

    /* This must be last */
    /* Update the nicklist of channel in the GUI */
    channels_nick_change(client_entry, client_entry->id, client_entry2);

    break;


  case SILC_NOTIFY_TYPE_TOPIC_SET:
    debug_info("SILC_NOTIFY_TYPE_TOPIC_SET");
    /* SilcIdType setter_id_type, void *setter_entry, char *topic, SilcChannelEntry channel */

   idtype = va_arg(va, int);
   entry = va_arg(va, void *);
   str = va_arg(va, char *);
   channel_entry = va_arg(va, SilcChannelEntry);


   if (idtype == SILC_ID_CLIENT) {
     client_entry = (SilcClientEntry)entry;

     /* Set new topic to UI */
     wid_channel_topic = get_stored_channel_widget(channel_entry->channel_name, "channeltopic");
     gtk_entry_set_text(GTK_ENTRY(wid_channel_topic), str);

     channel_topic_set_by_entry( channel_entry, str );

     debug_info(channel_entry->channel_name, g_strdup_printf(_("%s (%s@%s) changed topic for channel %s to '%s'"),
		       client_entry->nickname, client_entry->username, client_entry->hostname, channel_entry->channel_name, str));
     printchannel(channel_entry->channel_name, g_strdup_printf(_("%s (%s@%s) changed topic for channel %s to '%s'"),
		  client_entry->nickname, client_entry->username, client_entry->hostname, channel_entry->channel_name, str));

  }
   else {
     printchannel(channel_entry->channel_name, _("UNHANDLED TOPIC SET EVENT: Please report a bug to Silky development team"));
   }

   break;



  case SILC_NOTIFY_TYPE_CMODE_CHANGE:
    debug_info("SILC_NOTIFY_TYPE_CMODE_CHANGE:");

    /* get attributes from va_list */
    idtype = va_arg(va, int); /* changer's id */
    entry = va_arg(va, void *);
    mode = va_arg(va, SilcUInt32);
    (void)va_arg(va, char *); /* cipher_name */
    (void)va_arg(va, char *); /* hmac_name */
    (void)va_arg(va, char *); /* passphrase */
    (void)va_arg(va, SilcPublicKey); /* founder_key */
    channel_pubkeys = va_arg(va, SilcBuffer);
    channel_entry = va_arg(va, SilcChannelEntry);


    /* verify we got the channel_entry */
    if (channel_entry == NULL) {
      debug("No channel entry, skipping.");
      return;
    }

    /* Set some UI stuff. */
    str_mode = set_cmode_window_widgets(channel_entry->channel_name, mode); /* set modes in the UI */
    channel_mode_set_by_entry( channel_entry, mode );

    /* look up the type of the changer */
    if (idtype == SILC_ID_CLIENT) {
      client_entry2 = (SilcClientEntry)entry;
      changer = client_entry2->nickname;
      printchannel(channel_entry->channel_name, g_strdup_printf(_("%s (%s@%s) changed mode on channel '%s' to %s"), changer,  client_entry2->username, client_entry2->hostname, channel_entry->channel_name, str_mode));
    }
    if (idtype == SILC_ID_SERVER) {
      serverentry = (SilcServerEntry)entry;
      changer = serverentry->server_name;
      printchannel(channel_entry->channel_name, g_strdup_printf(_("%s changed mode on channel '%s' to %s"), changer, channel_entry->channel_name, str_mode));
    }
    if (idtype == SILC_ID_CHANNEL) {
      channelentry = (SilcChannelEntry)entry;
      changer =  channelentry->channel_name;
      printchannel(channel_entry->channel_name, g_strdup_printf(_("Mode on '%s' is now '%s'"), channel_entry->channel_name, str_mode));
    }

    silky_gui_check_channel_cmodes(channel_entry, str_mode);

    debug_info("*** '%s' changed mode '%s' on '%s'", changer, str_mode, channel_entry->channel_name);

    break;


  case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
    debug_info("SILC_NOTIFY_TYPE_CUMODE_CHANGE:");

    idtype = va_arg(va, int);
    entry = va_arg(va, void *);
    mode = va_arg(va, unsigned int);
    client_entry = va_arg(va, SilcClientEntry);
    channel_entry = va_arg(va, SilcChannelEntry);

    /*
      Refresh UI
    */

    if (!channel_entry) {
      debug("No channel entry, skipping.");
      return;
    }

    /* Update GUI nicklist */
    refresh_gui_nicklist(channel_find_by_entry(channel_entry));

    /* look up the type of the changer */
    if (idtype == SILC_ID_CLIENT) {
      client_entry2 = (SilcClientEntry)entry;
      changer = client_entry2->nickname;
    }
    if (idtype == SILC_ID_SERVER) {
      serverentry = (SilcServerEntry)entry;
      changer = serverentry->server_name;

    }
    if (idtype == SILC_ID_CHANNEL) {
      channelentry = (SilcChannelEntry)entry;
      changer =  channelentry->channel_name;
    }



    /* set all modes cleared */
    str_mode = g_strdup(""); /* clear dynamic string */


    /* generate mode string for UI */

    if (mode & SILC_CHANNEL_UMODE_NONE) {
      str_mode = g_strdup("none");
    }

    if (mode & SILC_CHANNEL_UMODE_CHANFO) {
      str_mode = g_strconcat(str_mode, "f", NULL);
    }

    /* CHANOP */
    if (mode & SILC_CHANNEL_UMODE_CHANOP) {
      str_mode = g_strconcat(str_mode, "o", NULL);

      /* if I got CHANOP, let me change the topic... */
      if (client_entry == silky->conn->local_entry) {
	debug("I got @");

	/* "change topic" -button */
	wid_channel_topic = get_stored_channel_widget(channel_entry->channel_name, "button_change_topic");
	gtk_widget_set_sensitive(wid_channel_topic, TRUE);

	/* text input */
	wid_channel_topic = get_stored_channel_widget(channel_entry->channel_name, "channeltopic");
	gtk_widget_set_sensitive(wid_channel_topic, TRUE);
      }
    }
    else {
     /* if I lost CHANOP, don't let me change the topic... */
      if (client_entry == silky->conn->local_entry) {
	debug("I lost @");

	/* "change topic" -button */
	wid_channel_topic = get_stored_channel_widget(channel_entry->channel_name, "button_change_topic");
	gtk_widget_set_sensitive(wid_channel_topic, FALSE);

	/* text input */
	wid_channel_topic = get_stored_channel_widget(channel_entry->channel_name, "channeltopic");
	gtk_widget_set_sensitive(wid_channel_topic, FALSE);

      }
    }


    if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES) {
      str_mode = g_strconcat(str_mode, "b", NULL);
    }

    if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES_USERS) {
      str_mode = g_strconcat(str_mode, "u", NULL);
    }

    if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES_ROBOTS) {
      str_mode = g_strconcat(str_mode, "r", NULL);
    }

    if (mode & SILC_CHANNEL_UMODE_QUIET) {
      str_mode = g_strconcat(str_mode, "q", NULL);
    }

    debug_info("*** '%s' changed mode '%s' for '%s' on '%s'", changer, str_mode, client_entry->nickname, channel_entry->channel_name);
    printchannel(channel_entry->channel_name, g_strdup_printf(_("%s changed mode for '%s' on '%s' to %s"),
		 changer, client_entry->nickname, channel_entry->channel_name, str_mode));
    break;


    /* INVITE: SilcClientChannel channel, char *channel_name, SilcClientEntry inviter */
  case SILC_NOTIFY_TYPE_INVITE:
    debug_info("SILC_NOTIFY_TYPE_INVITE:");
    channel_entry = va_arg(va, SilcChannelEntry);
    str = va_arg(va, char *);
    client_entry = va_arg(va, SilcClientEntry);

    debug_info("I was invited to channel '%s' by '%s (%s@%s)", str, client_entry->nickname, client_entry->username, client_entry->hostname);
    printconsole(g_strdup_printf(_("%s (%s@%s) invites you to channel %s."), client_entry->nickname, client_entry->username, client_entry->hostname, str));

    gtk_label_set_markup(GTK_LABEL(glade_xml_get_widget(xmlmain, "label_recinv_topic")),
	g_strdup_printf("<b>Join channel '%s'?</b>", str));

    gtk_label_set_markup(GTK_LABEL(glade_xml_get_widget(xmlmain, "label_recinv_text")),
	g_strdup_printf(_("You were invited by %s\n(%s@%s)"),
	client_entry->nickname, client_entry->username, client_entry->hostname));

    gtk_widget_show(glade_xml_get_widget(xmlmain, "dialog_received_invite"));

    g_signal_connect(glade_xml_get_widget(xmlmain, "button_received_invite_show_user_info"), "activate", G_CALLBACK(on_received_invite_user_info), client_entry);
    g_signal_connect(glade_xml_get_widget(xmlmain, "button_received_invite_show_user_info"), "released", G_CALLBACK(on_received_invite_user_info), client_entry);

    /* store channelname, so dialog 'Yes' callback function can access it */
    g_object_set_data(G_OBJECT(glade_xml_get_widget(xmlmain, "dialog_received_invite")), "invited_channel", g_strdup(str));

    break;

    /* SIGNOFF */
  case SILC_NOTIFY_TYPE_SIGNOFF:
    debug_info("SILC_NOTIFY_TYPE_SIGNOFF:");

    client_entry = va_arg(va, SilcClientEntry); /* user */
    str = va_arg(va, char *); /* reason */

    if (!client_entry) {
      debug("Did not get client_entry");
      return;
    }
    debug_info("User: '%s' quitted with reason '%s'", client_entry->nickname, str);


    debug("Starting iteration of userchannels");

    /* if the user was on some channel we are too */
    if (client_entry->channels) {

      silc_hash_table_list(client_entry->channels, &souserhtl); /* init list */
      debug("hash_table initialized");

      /* loop through all channels of this user */
      while (silc_hash_table_get(&souserhtl, NULL, (void *)&chu )) {
	debug("'%s' (%s@%s) signed off from '%s' (%s)", client_entry->nickname, client_entry->username, client_entry->hostname, chu->channel->channel_name, str);

        debug("freeing channel member from list...");
        channel_member_remove_by_entry(channel_find_by_entry(chu->channel), client_entry);

	/* print to the channel tab */
	printchannel(chu->channel->channel_name, g_strdup_printf(_("%s (%s@%s) has signed off (%s)"), client_entry->nickname, client_entry->username, client_entry->hostname, str));
	/* remove user from the lists, not sure if this is safe?! FIXME */
	silc_hash_table_del(chu->client->channels, chu->channel);
	silc_hash_table_del(chu->channel->user_list, chu->client);

	/* refresh the UI */
	refresh_gui_nicklist(channel_find_by_entry(chu->channel));

      }
      silc_hash_table_list_reset(&souserhtl); /* reset the list */



      debug("Iteration done.");
    }
    else {
      debug_message("Got NULL client_entry->channels. This must be a bug in the SILC Client library.");
    }


    break;


    /* WATCH */
  case SILC_NOTIFY_TYPE_WATCH:
    /* SilcClientEntry watched_client, char *new_nickname, SilcUInt32 user_mode, SilcNotifyType notification */
    debug_info("SILC_NOTIFY_TYPE_WATCH");

    client_entry = va_arg(va, SilcClientEntry);
    str = va_arg(va, char *);
    mode = va_arg(va, SilcUInt32);
    notification = va_arg(va, int);

    str_fingerprint = silc_fingerprint(client_entry->fingerprint, client_entry->fingerprint_len);
    buddy_set_clientid_by_fp(str_fingerprint, client_entry->id);

    switch (notification) {
    case SILC_NOTIFY_TYPE_NICK_CHANGE:

      debug_info("SILC_NOTIFY_TYPE_NICK_CHANGE");
      debug_info("WATCH '%s' is now known as '%s'", str_fingerprint, client_entry->nickname);

      if (buddy_set_nick_by_fp(str_fingerprint, client_entry->nickname)) {
 	refresh_gui_buddylist();
      } else {
	debug("fingerprint not found from the buddy list");
      }

      break;

    case SILC_NOTIFY_TYPE_UMODE_CHANGE:
      debug_info("SILC_NOTIFY_TYPE_UMODE_CHANGE");
      debug_info("WATCH '%s' has changed umode %d", client_entry->nickname, mode);
      if (buddy_set_mode_by_fp(silc_fingerprint(client_entry->fingerprint, client_entry->fingerprint_len), mode)) {
	debug("mode set in the buddy list");
	refresh_gui_buddylist();
      } else {
	debug("user not found in the buddy list");
      }


      break;

    default:
      debug_info("unhandled notification type: %d", notification);
      break;

    }


    break;

   /* UNHANDLED NOTIFIES */
  default:
    debug_message("unhandled SILC_NOTIFY_TYPE (%d)", type);
    break;
  }

  va_end(va);


  debug("returning");
}


syntax highlighted by Code2HTML, v. 0.9.1