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

     - Channel list management functions
     - Channel member list management functions
     - Nickname TAB-completion functions
     - Generic SILC channel 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 <glade/glade.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <silcincludes.h>
#include <silcclient.h>

#include "common.h"
#include "gui.h"

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

#include "ui_channel.h" /* for refresh_gui_nicklist() */

#include "support.h"
#include "log.h"

extern silkyStruct *silky;

SilkyChannel *channels = NULL;
SilkyChannel *channel_active = NULL;

/* channel_find_*()	*/
SilkyChannel *channel_find_by_name( gchar *name ) {
  SilkyChannel *ch;
    if (name == NULL) {
      return (NULL);
    }

    for( ch = channels; ch; ch = ch->next ) {
      if( name && !strcmp(CHANNEL_NAME(ch), name) ) {
        debug("Channel found.");
        return(ch);
      }
    }
    debug("Channel not found, returning NULL.");
    return(NULL);
}

SilkyChannel *channel_find_by_pagenr( gint pagenr ) {
  SilkyChannel *ch;

    for( ch = channels; ch; ch = ch->next ) {
      if( ch->pagenr == pagenr ) {
	debug("found channel, page %d", ch->pagenr);
	return(ch);
      }
    }
    debug("Channel not found on page %d.", pagenr);
    return(NULL);
}

SilkyChannel *channel_find_by_entry( SilcChannelEntry entry ) {
    SilkyChannel *ch;

    if (!entry) {
	debug("entry == NULL !!");
	return(NULL);
    }
    for( ch = channels; ch; ch = ch->next ) {
	if( ch->channel_entry == entry ) return(ch);
    }
    debug("channel NOT found");
    return(NULL);
}

/* creates a new channel struct in memory */
SilkyChannel *channel_add( SilcChannelEntry entry ) {
    SilkyChannel *ch, *newch;

    if( channel_find_by_entry(entry) ) {
	debug("channel '%s' already exists", entry->channel_name);
	return(NULL);
    }

    debug("creating channel struct for '%s'", entry->channel_name);
    newch = malloc( sizeof( SilkyChannel ) );
    newch->next = NULL;
    newch->channel_entry = entry;
    newch->members = NULL;
    newch->tab_member = NULL;
    newch->tab_prefix = NULL;
    newch->tab_text = NULL;
    newch->pagenr = -1; /* gint - will be set later */
    newch->label_text = NULL; /* GtkLabel */
    newch->label_image = NULL; /* GtkImage */

    if( !channels ) {
	debug("first channel, creating linked list");
	channels = newch;
    } else {
	debug("there are already some channels, appending to linked list");
	for( ch = channels; ch->next; ch = ch->next ) {}
	ch->next = newch;
    }
    return( newch );
}

/* channel_remove_*()		*/
gboolean channel_remove_by_name( gchar *name ) {
    SilkyChannel *ch, *prevch;
    gint found = 0;

    debug("trying to remove channel '%s'", name);

    if( !channels ) {
      debug("No channels, returning");
      return FALSE;
    }

    for( ch = channels, prevch = channels; ch; ch = ch->next ) {
      if (CHANNEL_NAME(ch) == NULL) {
        debug("WARNING: Channel without a name found, this should not be!");
	continue;
      }

      if ( !g_ascii_strcasecmp(CHANNEL_NAME(ch), name) ) {
	debug("found it");
	found++;
	break;
      }
      prevch = ch;
    }
    if( !found ) {
	debug("could not find channel struct, returning");
	return FALSE;
    }
    if( channels == ch ) {
	debug("it is first channel");
	channels = channels->next;
    } else {
	debug("not first channel");
	prevch->next = ch->next;
    }
    debug("removing '%s'", CHANNEL_NAME(ch));
    g_free(ch);
    return TRUE;
}

gboolean channel_remove_by_entry( SilcChannelEntry entry ) {
    SilkyChannel *ch, *prevch;
    gint found = 0;

    if (!entry) {
	debug("entry == NULL !!");
	return FALSE;
    }
    debug("trying to remove channel '%s'", entry->channel_name);
    for( ch = channels, prevch = channels; ch; ch = ch->next ) {
	if ( ch->channel_entry == entry ) {
	    debug("found it");
	    found++;
	    break;
	}
	prevch = ch;
    }
    if( !found ) {
	debug("could not find channel struct, returning");
	return FALSE;
    }
    if( channels == ch ) {
	debug("it is first channel");
	channels = channels->next;
    } else {
	debug("not first channel");
	prevch->next = ch->next;
    }
    channel_member_free_all(ch);
    debug("removing '%s'", CHANNEL_NAME(ch));
    g_free(ch);
    return TRUE;
}

/* channel_topic_set_*()	*/
void channel_topic_set_by_name( gchar *name, gchar *topic ) {
    SilkyChannel *ch = channel_find_by_name(name);

    if(ch) {
	debug("setting topic for '%s'", CHANNEL_NAME(ch));
	g_snprintf(ch->topic, 256, "%s", topic);
    }
}

void channel_topic_set_by_entry( SilcChannelEntry entry, gchar *topic ) {
    SilkyChannel *ch;
    if (!entry) {
	debug("entry == NULL !!");
	return;
    }

    ch = channel_find_by_entry( entry );

    if(ch) {
	debug("setting topic for '%s'", CHANNEL_NAME(ch));
	strncpy(ch->topic, topic, 256);
    }
}

/* channel_pagenr_set_*() */
void channel_pagenr_set_by_name( gchar *name, gint pagenr ) {
    SilkyChannel *ch = channel_find_by_name(name);

    if(ch) {
	debug("setting pagenr for '%s'", CHANNEL_NAME(ch));
	ch->pagenr = pagenr;
    }
}

void channel_pagenr_set_by_entry( SilcChannelEntry entry, gint pagenr ) {
    SilkyChannel *ch;
    if (!entry) {
	debug("entry == NULL !!");
	return;
    }

    ch = channel_find_by_entry( entry );

    if(ch) {
	debug("setting pagenr for '%s'", CHANNEL_NAME(ch));
	ch->pagenr = pagenr;
    }
}

/* channel_mode_set_*()		*/
void channel_mode_set_by_name( gchar *name, SilcUInt32 cmode ) {
    SilkyChannel *ch = channel_find_by_name( name );

    if(ch) {
	debug("setting cmode for '%s'", CHANNEL_NAME(ch));
	ch->channel_entry->mode = cmode;
    } else {
      debug("no channel found");
    }

}

void channel_mode_set_by_entry( SilcChannelEntry entry, SilcUInt32 cmode ) {
    SilkyChannel *ch;

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

    ch = channel_find_by_entry( entry );

    if(ch) {
	debug("setting cmode for '%s'", CHANNEL_NAME(ch));
	ch->channel_entry->mode = cmode;
    } else {
      debug("no channel found");
    }
}

/* channel_find_*()	*/
SilkyChannelMember *channel_member_find_by_nick( SilkyChannel *channel, gchar *name ) {
    SilkyChannelMember *ch;

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

    for( ch = channel->members; ch; ch = ch->next ) {
	if( !strcmp(CHMEMBER_NAME(ch), name) ) {
	    debug("found channel member '%s'", CHMEMBER_NAME(ch));
	    return(ch);
	}
    }
    debug("channel member '%s' NOT found in channel %s", name, CHANNEL_NAME(channel));
    return(NULL);
}

SilkyChannelMember *channel_member_find_by_entry( SilkyChannel *channel, SilcClientEntry entry ) {
    SilkyChannelMember *ch;

    if( !channel || !(channel->members)) {
	debug("channel == NULL");
	return(NULL);
    }

    for( ch = channel->members; ch; ch = ch->next ) {
	if( ch->chuser->client == entry ) {
	    debug("found channel member '%s'", CHMEMBER_NAME(ch));
	    return(ch);
	}
    }
    debug("channel member NOT found in channel %s", CHANNEL_NAME(channel));
    return(NULL);
}


/* creates a new channel member struct in memory  and links it to the position
   in the list indicated by pos (starting from 0)
   (if pos < 0, simply append) */
SilkyChannelMember *channel_member_add( SilkyChannel *channel, SilcChannelUser chuser ) {
    SilkyChannelMember *ch = NULL, *prevch, *newch;

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

    /* first we create the struct for new member */
    newch = malloc( sizeof( SilkyChannelMember ) );
    newch->chuser = chuser;
    newch->client = chuser->client;

    if( !channel->members ) {
	debug("first channel member, creating linked list");
	channel->members = newch;
	newch->next = NULL;
    } else {
	debug("there are already some channel members, adding into linked list");
	prevch = NULL;
	for( ch = channel->members; ch->next; ch = ch->next ) {
	  if( g_utf8_collate( g_utf8_casefold(CHMEMBER_NAME(newch), -1), g_utf8_casefold(CHMEMBER_NAME(ch), -1)) <= 0 ) {
	    if( !prevch ) {
	      debug("inserting '%s' to the beginning of list", CHMEMBER_NAME(newch));
	      channel->members = newch;
	      newch->next = ch;
	    } else {
	      debug("inserting '%s' before '%s' in list", CHMEMBER_NAME(newch), CHMEMBER_NAME(ch));
	      prevch->next = newch;
	      newch->next = ch;
	    }
	    return(newch);
	  }
	  prevch = ch;
	}
	debug("adding '%s' to the end of list", CHMEMBER_NAME(newch));
	ch->next = newch;
	newch->next = NULL;
	return(newch);
    }
    return(newch);
}

/* channel_member_remove_by_*() */
gint channel_member_remove_by_entry( SilkyChannel *channel, SilcClientEntry client ) {
  SilkyChannelMember *ch, *prevch;
  gint found = 0;

  if( !channel || !(channel->members) ) {
    debug("channel or channel->members is NULL!");
    return(0);
  }

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

  for( ch = channel->members, prevch = channel->members; ch; ch = ch->next ) {

    if ( ch->client == client ) {
      debug("found it");
      found++;
      break;
    }
    prevch = ch;
  }
  if( !found ) {
    debug("could not find channel member struct, returning");
    return(0);
  }
  if( channel->members == ch ) channel->members = channel->members->next;
  else prevch->next = ch->next;
  debug("removing '%s' from channel %s", CHMEMBER_NAME(ch), CHANNEL_NAME(channel));
  free(ch);
  return(1);
}

void channel_member_change_client_entry(SilkyChannel *channel, SilcClientEntry oldclient, SilcClientEntry newclient) {
  SilkyChannelMember *ch;

  if( !channel ) {
    debug("channel == NULL");
    return;
  }
  if( !newclient || !oldclient ) {
    debug("newclient or oldclient is NULL");
    return;
  }

  for( ch = channel->members; ch; ch = ch->next )
    if( ch->client == oldclient ) ch->client = newclient;
}

void channel_free_all( void ) {
    SilkyChannel *ch, *prevch = NULL;

    for( ch = channels; ch; ch = ch->next ) {
	if(prevch) {
	    debug("freeing channel struct '%s'", CHANNEL_NAME(prevch));
	    channel_member_free_all(prevch);
	    g_free(prevch);
	}
	prevch = ch;
	if( ch && !ch->next ) {
	    debug("freeing last channel struct '%s'", CHANNEL_NAME(ch));
	    channel_member_free_all(ch);
	    g_free(ch);
	    break;
	}
    }
    channels = NULL;
    channel_active = NULL;
}

void channel_member_free_all( SilkyChannel *channel ) {
    SilkyChannelMember *ch, *prevch = NULL;

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

  for( ch = channel->members; ch; ch = ch->next ) {
    if(prevch) {
      debug("freeing channel member struct '%s'", CHMEMBER_NAME(prevch));
      g_free(prevch);
    }
    prevch = ch;
    if( ch && !ch->next ) {
      debug("freeing last channel member struct '%s'", CHMEMBER_NAME(ch));
      g_free(ch);
      break;
    }
  }

  channel->members = NULL;
  channel->tab_member = NULL;
}


/*
  called when a user's nickname changes
  informs all channels' UI about the change
*/
void channels_nick_change (SilcClientEntry old_entry, SilcClientID *old_client_id, SilcClientEntry new_client_entry) {
  SilcHashTableList userhtl;
  SilcChannelEntry userchannel;
  gchar *old_nickname = old_entry->nickname;
  SilkyChannel *channel;

  /* channels of the nick */
  silc_hash_table_list(new_client_entry->channels, &userhtl);

    while (silc_hash_table_get(&userhtl, (void *)&userchannel, NULL)) {

      debug("\tchannel: '%s'", userchannel->channel_name);

      /* Show nick change in all channel windows */
      /* I18N This is a nickname change */
      printchannel(userchannel->channel_name, g_strdup_printf(_("'%s' is now known as '%s'."), old_nickname, new_client_entry->nickname));

      channel = channel_find_by_entry(userchannel);

      /* Update nicklist of the channel */
      channel_member_change_client_entry(channel, old_entry, new_client_entry);
      channel_member_remove_by_entry(channel, new_client_entry);
      channel_member_add(channel, get_channel_user_by_entry(channel->channel_entry, new_client_entry));
      refresh_gui_nicklist(channel);
    }
    silc_hash_table_list_reset(&userhtl);
    debug("Iteration done. Returning.");

    /* free? */

    return;
}


/* Call this whenever something changes in the channel. This will resolve
   the channel from the server and refresh the UI nicklist */
void refresh_nicklist (SilcChannelEntry channel_entry) {
  SilcJoinResolve *r;
  SilkyChannelMember *ch;
  SilkyChannel *cha;

  debug("refresh_nicklist");

  if (!channel_entry) {
    debug("Did not get channel_entry in refresh_nicklist()");
  }

  r = silc_calloc(1, sizeof(*r));
  r->channel = channel_entry;
  r->retry = 0;
  debug("channel: '%s'", channel_entry->channel_name);

  debug("freeing list of members, so we can recreate it");
  channel_member_free_all(cha = channel_find_by_entry(channel_entry));

  silc_client_get_clients_by_channel(silky->client, silky->conn,
				     channel_entry, refresh_nicklist_resolved, r);

  debug("now displaying contents of channel members linked list:");
  for( ch = cha->members; ch; ch = ch->next )
   debug(">>> %s", CHMEMBER_NAME(ch));

  debug("refresh_nicklist returning");
}


/* this gets called when the userlist is resolved */
void refresh_nicklist_resolved(SilcClient client,
			       SilcClientConnection conn,
			       SilcClientEntry *clients,
			       SilcUInt32 clients_count,
                                       void *context) {


  SilcJoinResolve *r = context;
  SilcChannelEntry channel = r->channel;
  SilcHashTableList htl;
  SilcChannelUser chu;
  gint usercount;
  SilkyChannel *cha = channel_find_by_entry(channel);

  debug("refresh_nicklist_resolved");
  /* check that the resolving was complete */
  debug("channel: '%s'", channel->channel_name);

  if (!clients && r->retry < 1) {
    /* not, retry */
    r->retry++;
    silc_client_get_clients_by_channel(client, conn, channel,
                                       refresh_nicklist_resolved, context);
    return;
  }

  debug("Starting to list users...");

  /* iterate through the list */
  usercount = 0;
  silc_hash_table_list(channel->user_list, &htl);
  while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
    /* this client has no nickname. (WHY?) */
    if (!chu->client->nickname) {
      continue; /* skip */
    }
    usercount++;

    debug("\tnick: '%s' realname: '%s' ", chu->client->nickname,
	  chu->client->realname);

    channel_member_add(cha, chu);

  }
  /* end of while */

  if( usercount == 1) debug("1 user listed");
  else debug("%d users listed", usercount);

  /* now we update gui nicklist from channel struct */
  refresh_gui_nicklist(channel_find_by_entry(channel));

  /* This needs to be in the end of this function */
  silc_hash_table_list_reset(&htl);

  debug("returning...");
}

void silky_join_channel(const gchar *channel_name) {
  SilcBuffer idp = silc_id_payload_encode(silky->conn->local_id, SILC_ID_CLIENT);

  if( !idp ) {
    debug("Couldn't get idp in silc invite handler, cannot join.");
    return;
  }

  silc_client_command_send(silky->client, silky->conn, SILC_COMMAND_JOIN, 0, 2,
    1, channel_name, strlen(channel_name), 2, idp->data, idp->len);
  debug("Trying to join channel '%s'.", channel_name);
}

void silky_leave_channel(const gchar *channel_name) {
  if( !channel_name || !strlen(channel_name) ) {
    debug("No channel name given, can not leave.");
    return;
  }

  if( !CONNECTED ) return;

  debug("Leaving channel '%s'", channel_name);
  printconsole(g_strdup_printf(_("Leaving channel '%s'."), channel_name));
  silc_client_command_call(silky->client, silky->conn, NULL, "LEAVE", channel_name, NULL);
}


syntax highlighted by Code2HTML, v. 0.9.1