  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2013 Wilmer van der Gaast and others                *
  \********************************************************************/

/* The IRC-based UI - Representing (virtual) channels.                  */

/*
  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 with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  Suite 330, Boston, MA  02111-1307  USA
*/

#include "bitlbee.h"

static char *set_eval_channel_type( set_t *set, char *value );
static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ );
static const struct irc_channel_funcs control_channel_funcs;

extern const struct irc_channel_funcs irc_channel_im_chat_funcs;

irc_channel_t *irc_channel_new( irc_t *irc, const char *name )
{
	irc_channel_t *ic;
	set_t *s;
	
	if( !irc_channel_name_ok( name ) || irc_channel_by_name( irc, name ) )
		return NULL;
	
	ic = g_new0( irc_channel_t, 1 );
	ic->irc = irc;
	ic->name = g_strdup( name );
	strcpy( ic->mode, CMODE );
	
	irc_channel_add_user( ic, irc->root );
	
	irc->channels = g_slist_append( irc->channels, ic );
	
	set_add( &ic->set, "auto_join", "false", set_eval_bool, ic );
	
	s = set_add( &ic->set, "type", "control", set_eval_channel_type, ic );
	s->flags |= SET_NOSAVE;    /* Layer violation (XML format detail) */
	
	if( name[0] == '&' )
		set_setstr( &ic->set, "type", "control" );
	else /* if( name[0] == '#' ) */
		set_setstr( &ic->set, "type", "chat" );
	
	return ic;
}

irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name )
{
	GSList *l;
	
	for( l = irc->channels; l; l = l->next )
	{
		irc_channel_t *ic = l->data;
		
		if( irc_channel_name_cmp( name, ic->name ) == 0 )
			return ic;
	}
	
	return NULL;
}

irc_channel_t *irc_channel_get( irc_t *irc, char *id )
{
	irc_channel_t *ic, *ret = NULL;
	GSList *l;
	int nr;
	
	if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
	{
		for( l = irc->channels; l; l = l->next )
		{
			ic = l->data;
			if( ( nr-- ) == 0 ) 
				return ic;
		}
		
		return NULL;
	}
	
	/* Exact match first: Partial match only sucks if there's a channel
	   #aa and #aabb */
	if( ( ret = irc_channel_by_name( irc, id ) ) )
		return ret;
	
	for( l = irc->channels; l; l = l->next )
	{
		ic = l->data;
		
		if( strstr( ic->name, id ) )
		{
			/* Make sure it's a unique match. */
			if( !ret )
				ret = ic;
			else
				return NULL;
		}
	}
	
	return ret;
}

int irc_channel_free( irc_channel_t *ic )
{
	irc_t *irc;
	GSList *l;
	
	if( ic == NULL )
		return 0;
	irc = ic->irc;
	
	if( ic->flags & IRC_CHANNEL_JOINED )
		irc_channel_del_user( ic, irc->user, IRC_CDU_KICK, "Cleaning up channel" );
	
	if( ic->f->_free )
		ic->f->_free( ic );
	
	while( ic->set )
		set_del( &ic->set, ic->set->key );
	
	irc->channels = g_slist_remove( irc->channels, ic );
	while( ic->users )
	{
		g_free( ic->users->data );
		ic->users = g_slist_remove( ic->users, ic->users->data );
	}
	
	for( l = irc->users; l; l = l->next )
	{
		irc_user_t *iu = l->data;
		
		if( iu->last_channel == ic )
			iu->last_channel = irc->default_channel;
	}
	
	if( ic->pastebuf_timer ) b_event_remove( ic->pastebuf_timer );
	
	g_free( ic->name );
	g_free( ic->topic );
	g_free( ic->topic_who );
	g_free( ic );
	
	return 1;
}

struct irc_channel_free_data
{
	irc_t *irc;
	irc_channel_t *ic;
	char *name;
};

static gboolean irc_channel_free_callback( gpointer data, gint fd, b_input_condition cond )
{
	struct irc_channel_free_data *d = data;
	
	if( g_slist_find( irc_connection_list, d->irc ) &&
	    irc_channel_by_name( d->irc, d->name ) == d->ic &&
	    !( d->ic->flags & IRC_CHANNEL_JOINED ) )
		irc_channel_free( d->ic );

	g_free( d->name );
	g_free( d );
	return FALSE;
}

/* Free the channel, but via the event loop, so after finishing whatever event
   we're currently handling. */
void irc_channel_free_soon( irc_channel_t *ic )
{
	struct irc_channel_free_data *d = g_new0( struct irc_channel_free_data, 1 );
	
	d->irc = ic->irc;
	d->ic = ic;
	d->name = g_strdup( ic->name );
	
	b_timeout_add( 0, irc_channel_free_callback, d );
}

static char *set_eval_channel_type( set_t *set, char *value )
{
	struct irc_channel *ic = set->data;
	const struct irc_channel_funcs *new;
	
	if( strcmp( value, "control" ) == 0 )
		new = &control_channel_funcs;
	else if( ic != ic->irc->default_channel && strcmp( value, "chat" ) == 0 )
		new = &irc_channel_im_chat_funcs;
	else
		return SET_INVALID;
	
	/* TODO: Return values. */
	if( ic->f && ic->f->_free )
		ic->f->_free( ic );
	
	ic->f = new;
	
	if( ic->f && ic->f->_init )
		ic->f->_init( ic );
	
	return value;
}

int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu )
{
	irc_channel_user_t *icu;
	
	if( irc_channel_has_user( ic, iu ) )
		return 0;
	
	icu = g_new0( irc_channel_user_t, 1 );
	icu->iu = iu;
	
	ic->users = g_slist_insert_sorted( ic->users, icu, irc_channel_user_cmp );
	
	irc_channel_update_ops( ic, set_getstr( &ic->irc->b->set, "ops" ) );
	
	if( iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED )
	{
		ic->flags |= IRC_CHANNEL_JOINED;
		irc_send_join( ic, iu );
	}
	
	return 1;
}

int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg )
{
	irc_channel_user_t *icu;
	
	if( !( icu = irc_channel_has_user( ic, iu ) ) )
		return 0;
	
	ic->users = g_slist_remove( ic->users, icu );
	g_free( icu );
	
	if( !( ic->flags & IRC_CHANNEL_JOINED ) || type == IRC_CDU_SILENT ) {}
		/* Do nothing. The caller should promise it won't screw
		   up state of the IRC client. :-) */
	else if( type == IRC_CDU_PART )
		irc_send_part( ic, iu, msg );
	else if( type == IRC_CDU_KICK )
		irc_send_kick( ic, iu, ic->irc->root, msg );
	
	if( iu == ic->irc->user )
	{
		ic->flags &= ~IRC_CHANNEL_JOINED;
		
		if( ic->irc->status & USTATUS_SHUTDOWN )
		{
			/* Don't do anything fancy when we're shutting down anyway. */
		}
		else if( ic->flags & IRC_CHANNEL_TEMP )
		{
			irc_channel_free_soon( ic );
		}
		else
		{
			/* Flush userlist now. The user won't see it anyway. */
			while( ic->users )
			{
				g_free( ic->users->data );
				ic->users = g_slist_remove( ic->users, ic->users->data );
			}
			irc_channel_add_user( ic, ic->irc->root );
		}
	}
	
	return 1;
}

irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu )
{
	GSList *l;
	
	for( l = ic->users; l; l = l->next )
	{
		irc_channel_user_t *icu = l->data;
		
		if( icu->iu == iu )
			return icu;
	}
	
	return NULL;
}

/* Find a channel we're currently in, that currently has iu in it. */
struct irc_channel *irc_channel_with_user( irc_t *irc, irc_user_t *iu )
{
	GSList *l;
	
	for( l = irc->channels; l; l = l->next )
	{
		irc_channel_t *ic = l->data;
		
		if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 )
			continue;
		
		if( ( ic->flags & IRC_CHANNEL_JOINED ) &&
		    irc_channel_has_user( ic, iu ) )
			return ic;
	}
	
	/* If there was no match, try once more but just see if the user
	   *would* be in the channel, i.e. if s/he were online. */
	if( iu->bu == NULL )
		return NULL;
	
	for( l = irc->channels; l; l = l->next )
	{
		irc_channel_t *ic = l->data;
		
		if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 )
			continue;
		
		if( ( ic->flags & IRC_CHANNEL_JOINED ) &&
		    irc_channel_wants_user( ic, iu ) )
			return ic;
	}
	
	return NULL;
}

int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *iu )
{
	g_free( ic->topic );
	ic->topic = g_strdup( topic );
	
	g_free( ic->topic_who );
	if( iu )
		ic->topic_who = g_strdup_printf( "%s!%s@%s", iu->nick, iu->user, iu->host );
	else
		ic->topic_who = NULL;
	
	ic->topic_time = time( NULL );
	
	if( ic->flags & IRC_CHANNEL_JOINED )
		irc_send_topic( ic, TRUE );
	
	return 1;
}

void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags )
{
	irc_channel_user_t *icu = irc_channel_has_user( ic, iu );
	
	if( !icu || icu->flags == flags )
		return;
	
	if( ic->flags & IRC_CHANNEL_JOINED )
		irc_send_channel_user_mode_diff( ic, iu, icu->flags, flags );
	
	icu->flags = flags;
}

void irc_channel_set_mode( irc_channel_t *ic, const char *s )
{
	irc_t *irc = ic->irc;
	char m[128], st = 1;
	const char *t;
	int i;
	char changes[512], *p, st2 = 2;
	
	memset( m, 0, sizeof( m ) );
	
	for( t = ic->mode; *t; t ++ )
		if( *t < sizeof( m ) )
			m[(int)*t] = 1;
	
	p = changes;
	for( t = s; *t; t ++ )
	{
		if( *t == '+' || *t == '-' )
			st = *t == '+';
		else if( strchr( CMODES, *t ) )
		{
			if( m[(int)*t] != st)
			{
				if( st != st2 )
					st2 = st, *p++ = st ? '+' : '-';
				*p++ = *t;
			}
			m[(int)*t] = st;
		}
	}
	*p = '\0';
	
	memset( ic->mode, 0, sizeof( ic->mode ) );
	
	for( i = 'A'; i <= 'z' && strlen( ic->mode ) < ( sizeof( ic->mode ) - 1 ); i ++ )
		if( m[i] )
			ic->mode[strlen(ic->mode)] = i;
	
	if( *changes && ( ic->flags & IRC_CHANNEL_JOINED ) )
		irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->root->nick,
		           irc->root->user, irc->root->host, ic->name,
		           changes );
}

void irc_channel_auto_joins( irc_t *irc, account_t *acc )
{
	GSList *l;
	
	for( l = irc->channels; l; l = l->next )
	{
		irc_channel_t *ic = l->data;
		gboolean aj = set_getbool( &ic->set, "auto_join" );
		char *type;
		
		if( acc &&
		    ( type = set_getstr( &ic->set, "chat_type" ) ) &&
		    strcmp( type, "room" ) == 0 )
		{
			/* Bit of an ugly special case: Handle chatrooms here, we
			   can only auto-join them if their account is online. */
			char *acc_s;
			
			if( !aj && !( ic->flags & IRC_CHANNEL_JOINED ) )
				/* Only continue if this one's marked as auto_join
				   or if we're in it already. (Possible if the
				   client auto-rejoined it before identyfing.) */
				continue;
			else if( !( acc_s = set_getstr( &ic->set, "account" ) ) )
				continue;
			else if( account_get( irc->b, acc_s ) != acc )
				continue;
			else if( acc->ic == NULL || !( acc->ic->flags & OPT_LOGGED_IN ) )
				continue;
			else
				ic->f->join( ic );
		}
		else if( aj )
		{
			irc_channel_add_user( ic, irc->user );
		}
	}
}

void irc_channel_printf( irc_channel_t *ic, char *format, ... )
{
	va_list params;
	char *text;
	
	va_start( params, format );
	text = g_strdup_vprintf( format, params );
	va_end( params );
	
	irc_send_msg( ic->irc->root, "PRIVMSG", ic->name, text, NULL );
	g_free( text );
}

gboolean irc_channel_name_ok( const char *name_ )
{
	const unsigned char *name = (unsigned char*) name_;
	int i;
	
	if( name_[0] == '\0' )
		return FALSE;
	
	/* Check if the first character is in CTYPES (#&) */
	if( strchr( CTYPES, name_[0] ) == NULL )
		return FALSE;
	
	/* RFC 1459 keeps amazing me: While only a "few" chars are allowed
	   in nicknames, channel names can be pretty much anything as long
	   as they start with # or &. I'll be a little bit more strict and
	   disallow all non-printable characters. */
	for( i = 1; name[i]; i ++ )
		if( name[i] <= ' ' || name[i] == ',' )
			return FALSE;
	
	return TRUE;
}

void irc_channel_name_strip( char *name )
{
	int i, j;
	
	for( i = j = 0; name[i]; i ++ )
		if( name[i] > ' ' && name[i] != ',' )
			name[j++] = name[i];
	
	name[j] = '\0';
}

int irc_channel_name_cmp( const char *a_, const char *b_ )
{
	static unsigned char case_map[256];
	const unsigned char *a = (unsigned char*) a_, *b = (unsigned char*) b_;
	int i;
	
	if( case_map['A'] == '\0' )
	{
		for( i = 33; i < 256; i ++ )
			if( i != ',' )
				case_map[i] = i;
		
		for( i = 0; i < 26; i ++ )
			case_map['A'+i] = 'a' + i;
		
		case_map['['] = '{';
		case_map[']'] = '}';
		case_map['~'] = '`';
		case_map['\\'] = '|';
	}
	
	if( !irc_channel_name_ok( a_ ) || !irc_channel_name_ok( b_ ) )
		return -1;
	
	for( i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i ++ )
	{
		if( case_map[a[i]] == case_map[b[i]] )
			continue;
		else
			return case_map[a[i]] - case_map[b[i]];
	}
	
	return case_map[a[i]] - case_map[b[i]];
}

static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ )
{
	const irc_channel_user_t *a = a_, *b = b_;
	
	return irc_user_cmp( a->iu, b->iu );
}

void irc_channel_update_ops( irc_channel_t *ic, char *value )
{
	irc_channel_user_set_mode( ic, ic->irc->root,
		( strcmp( value, "both" ) == 0 ||
		  strcmp( value, "root" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 );
	irc_channel_user_set_mode( ic, ic->irc->user,
		( strcmp( value, "both" ) == 0 ||
		  strcmp( value, "user" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 );
}

char *set_eval_irc_channel_ops( set_t *set, char *value )
{
	irc_t *irc = set->data;
	GSList *l;
	
	if( strcmp( value, "both" ) != 0 && strcmp( value, "none" ) != 0 && 
	    strcmp( value, "user" ) != 0 && strcmp( value, "root" ) != 0 )
		return SET_INVALID;
	
	for( l = irc->channels; l; l = l->next )
		irc_channel_update_ops( l->data, value );
	
	return value;
}

/* Channel-type dependent functions, for control channels: */
static gboolean control_channel_privmsg( irc_channel_t *ic, const char *msg )
{
	irc_t *irc = ic->irc;
	irc_user_t *iu;
	const char *s;
	
	/* Scan for non-whitespace chars followed by a colon: */
	for( s = msg; *s && !isspace( *s ) && *s != ':' && *s != ','; s ++ ) {}
	
	if( *s == ':' || *s == ',' )
	{
		char to[s-msg+1];
		
		memset( to, 0, sizeof( to ) );
		strncpy( to, msg, s - msg );
		while( *(++s) && isspace( *s ) ) {}
		msg = s;
		
		if( !( iu = irc_user_by_name( irc, to ) ) )
			irc_channel_printf( ic, "User does not exist: %s", to );
		else
			ic->last_target = iu;
	}
	else if( g_strcasecmp( set_getstr( &irc->b->set, "default_target" ), "last" ) == 0 &&
	         ic->last_target && g_slist_find( irc->users, ic->last_target ) )
		iu = ic->last_target;
	else
		iu = irc->root;
	
	if( iu && iu->f->privmsg )
	{
		iu->last_channel = ic;
		iu->f->privmsg( iu, msg );
	}
	
	return TRUE;
}

static gboolean control_channel_invite( irc_channel_t *ic, irc_user_t *iu )
{
	struct irc_control_channel *icc = ic->data;
	bee_user_t *bu = iu->bu;
	
	if( bu == NULL )
		return FALSE;
	
	if( icc->type != IRC_CC_TYPE_GROUP )
	{
		irc_send_num( ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name );
		return FALSE;
	}
	
	bu->ic->acc->prpl->add_buddy( bu->ic, bu->handle,
	                              icc->group ? icc->group->name : NULL );
	
	return TRUE;
}

static char *set_eval_by_account( set_t *set, char *value );
static char *set_eval_fill_by( set_t *set, char *value );
static char *set_eval_by_group( set_t *set, char *value );
static char *set_eval_by_protocol( set_t *set, char *value );
static char *set_eval_show_users( set_t *set, char *value );

static gboolean control_channel_init( irc_channel_t *ic )
{
	struct irc_control_channel *icc;
	
	set_add( &ic->set, "account", NULL, set_eval_by_account, ic );
	set_add( &ic->set, "fill_by", "all", set_eval_fill_by, ic );
	set_add( &ic->set, "group", NULL, set_eval_by_group, ic );
	set_add( &ic->set, "protocol", NULL, set_eval_by_protocol, ic );
	
	/* When changing the default, also change it below. */
	set_add( &ic->set, "show_users", "online+,away", set_eval_show_users, ic );
	
	ic->data = icc = g_new0( struct irc_control_channel, 1 );
	icc->type = IRC_CC_TYPE_DEFAULT;
	
	/* Have to run the evaluator to initialize icc->modes. */
	set_setstr( &ic->set, "show_users", "online+,away" );
	
	/* For scripts that care. */
	irc_channel_set_mode( ic, "+C" );
	
	return TRUE;
}

static gboolean control_channel_join( irc_channel_t *ic )
{
	bee_irc_channel_update( ic->irc, ic, NULL );
	
	return TRUE;
}

static char *set_eval_by_account( set_t *set, char *value )
{
	struct irc_channel *ic = set->data;
	struct irc_control_channel *icc = ic->data;
	account_t *acc;
	
	if( !( acc = account_get( ic->irc->b, value ) ) )
		return SET_INVALID;
	
	icc->account = acc;
	if( ( icc->type & IRC_CC_TYPE_MASK ) == IRC_CC_TYPE_ACCOUNT )
		bee_irc_channel_update( ic->irc, ic, NULL );
	
	return g_strdup( acc->tag );
}

static char *set_eval_fill_by( set_t *set, char *value )
{
	struct irc_channel *ic = set->data;
	struct irc_control_channel *icc = ic->data;
	char *s;
	
	icc->type &= ~( IRC_CC_TYPE_MASK | IRC_CC_TYPE_INVERT );
	
	s = value;
	if( s[0] == '!' )
	{
		icc->type |= IRC_CC_TYPE_INVERT;
		s ++;
	}
	
	if( strcmp( s, "all" ) == 0 )
		icc->type |= IRC_CC_TYPE_DEFAULT;
	else if( strcmp( s, "rest" ) == 0 )
		icc->type |= IRC_CC_TYPE_REST;
	else if( strcmp( s, "group" ) == 0 )
		icc->type |= IRC_CC_TYPE_GROUP;
	else if( strcmp( s, "account" ) == 0 )
		icc->type |= IRC_CC_TYPE_ACCOUNT;
	else if( strcmp( s, "protocol" ) == 0 )
		icc->type |= IRC_CC_TYPE_PROTOCOL;
	else
		return SET_INVALID;
	
	bee_irc_channel_update( ic->irc, ic, NULL );
	return value;
}

static char *set_eval_by_group( set_t *set, char *value )
{
	struct irc_channel *ic = set->data;
	struct irc_control_channel *icc = ic->data;
	
	icc->group = bee_group_by_name( ic->irc->b, value, TRUE );
	if( ( icc->type & IRC_CC_TYPE_MASK ) == IRC_CC_TYPE_GROUP )
		bee_irc_channel_update( ic->irc, ic, NULL );
	
	return g_strdup( icc->group->name );
}

static char *set_eval_by_protocol( set_t *set, char *value )
{
	struct irc_channel *ic = set->data;
	struct irc_control_channel *icc = ic->data;
	struct prpl *prpl;
	
	if( !( prpl = find_protocol( value ) ) )
		return SET_INVALID;
	
	icc->protocol = prpl;
	if( ( icc->type & IRC_CC_TYPE_MASK ) == IRC_CC_TYPE_PROTOCOL )
		bee_irc_channel_update( ic->irc, ic, NULL );
	
	return value;
}

static char *set_eval_show_users( set_t *set, char *value )
{
	struct irc_channel *ic = set->data;
	struct irc_control_channel *icc = ic->data;
	char **parts = g_strsplit( value, ",", 0 ), **part;
	char modes[4];
	
	memset( modes, 0, 4 );
	for( part = parts; *part; part ++ )
	{
		char last, modechar = IRC_CHANNEL_USER_NONE;
		
		if( **part == '\0' )
			goto fail;
		
		last = (*part)[strlen(*part+1)];
		if( last == '+' )
			modechar = IRC_CHANNEL_USER_VOICE;
		else if( last == '%' )
			modechar = IRC_CHANNEL_USER_HALFOP;
		else if( last == '@' )
			modechar = IRC_CHANNEL_USER_OP;
		
		if( strncmp( *part, "offline", 7 ) == 0 )
			modes[0] = modechar;
		else if( strncmp( *part, "away", 4 ) == 0 )
			modes[1] = modechar;
		else if( strncmp( *part, "online", 6 ) == 0 )
			modes[2] = modechar;
		else
			goto fail;
	}
	memcpy( icc->modes, modes, 4 );
	bee_irc_channel_update( ic->irc, ic, NULL );
	
	g_strfreev( parts );
	return value;
	
fail:
	g_strfreev( parts );
	return SET_INVALID;	
}

/* Figure out if a channel is supposed to have the user, assuming s/he is
   online or otherwise also selected by the show_users setting. Only works
   for control channels, but does *not* check if this channel is of that
   type. Beware! */
gboolean irc_channel_wants_user( irc_channel_t *ic, irc_user_t *iu )
{
	struct irc_control_channel *icc = ic->data;
	gboolean ret = FALSE;
	
	if( iu->bu == NULL )
		return FALSE;
	
	switch( icc->type & IRC_CC_TYPE_MASK )
	{
	case IRC_CC_TYPE_GROUP:
		ret = iu->bu->group == icc->group;
		break;
	case IRC_CC_TYPE_ACCOUNT:
		ret = iu->bu->ic->acc == icc->account;
		break;
	case IRC_CC_TYPE_PROTOCOL:
		ret = iu->bu->ic->acc->prpl == icc->protocol;
		break;
	case IRC_CC_TYPE_DEFAULT:
	default:
		ret = TRUE;
		break;
	}
	
	if( icc->type & IRC_CC_TYPE_INVERT )
		ret = !ret;
	
	return ret;
}

static gboolean control_channel_free( irc_channel_t *ic )
{
	struct irc_control_channel *icc = ic->data;
	
	set_del( &ic->set, "account" );
	set_del( &ic->set, "fill_by" );
	set_del( &ic->set, "group" );
	set_del( &ic->set, "protocol" );
	set_del( &ic->set, "show_users" );
	
	g_free( icc );
	ic->data = NULL;
	
	/* For scripts that care. */
	irc_channel_set_mode( ic, "-C" );
	
	return TRUE;
}

static const struct irc_channel_funcs control_channel_funcs = {
	control_channel_privmsg,
	control_channel_join,
	NULL,
	NULL,
	control_channel_invite,
	
	control_channel_init,
	control_channel_free,
};
