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

/* Some stuff to fetch, save and handle nicknames for your buddies      */

/*
  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
*/

#define BITLBEE_CORE
#include "bitlbee.h"

/* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's.
   With one difference, we allow dashes. These are used to do uc/lc conversions
   and strip invalid chars. */
static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|";
static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\";

/* Store handles in lower case and strip spaces, because AIM is braindead. */
static char *clean_handle( const char *orig )
{
	char *new = g_malloc( strlen( orig ) + 1 );
	int i = 0;
	
	do {
		if (*orig != ' ')
			new[i++] = tolower( *orig );
	}
	while (*(orig++));
	
	return new;
}

void nick_set_raw( account_t *acc, const char *handle, const char *nick )
{
	char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 );
	irc_t *irc = (irc_t *) acc->bee->ui_data;
	
	store_handle = clean_handle( handle );
	store_nick[MAX_NICK_LENGTH] = '\0';
	strncpy( store_nick, nick, MAX_NICK_LENGTH );
	nick_strip( irc, store_nick );
	
	g_hash_table_replace( acc->nicks, store_handle, store_nick );
}

void nick_set( bee_user_t *bu, const char *nick )
{
	nick_set_raw( bu->ic->acc, bu->handle, nick );
}

char *nick_get( bee_user_t *bu )
{
	static char nick[MAX_NICK_LENGTH+1];
	char *store_handle, *found_nick;
	irc_t *irc = (irc_t *) bu->bee->ui_data;
	
	memset( nick, 0, MAX_NICK_LENGTH + 1 );
	
	store_handle = clean_handle( bu->handle );
	/* Find out if we stored a nick for this person already. If not, try
	   to generate a sane nick automatically. */
	if( ( found_nick = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ) ) )
	{
		strncpy( nick, found_nick, MAX_NICK_LENGTH );
	}
	else if( ( found_nick = nick_gen( bu ) ) )
	{
		strncpy( nick, found_nick, MAX_NICK_LENGTH );
		g_free( found_nick );
	}
	else
	{
		/* Keep this fallback since nick_gen() can return NULL in some cases. */
		char *s;
		
		g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle );
		if( ( s = strchr( nick, '@' ) ) )
			while( *s )
				*(s++) = 0;
		
		nick_strip( irc, nick );
		if( set_getbool( &bu->bee->set, "lcnicks" ) )
			nick_lc( irc, nick );
	}
	g_free( store_handle );
	
	/* Make sure the nick doesn't collide with an existing one by adding
	   underscores and that kind of stuff, if necessary. */
	nick_dedupe( bu, nick );
	
	return nick;
}

char *nick_gen( bee_user_t *bu )
{
	gboolean ok = FALSE; /* Set to true once the nick contains something unique. */
	GString *ret = g_string_sized_new( MAX_NICK_LENGTH + 1 );
	char *rets;
	irc_t *irc = (irc_t *) bu->bee->ui_data;
	char *fmt = set_getstr( &bu->ic->acc->set, "nick_format" ) ? :
	            set_getstr( &bu->bee->set, "nick_format" );
	
	while( fmt && *fmt && ret->len < MAX_NICK_LENGTH )
	{
		char *part = NULL, chop = '\0', *asc = NULL, *s;
		int len = INT_MAX;
		
		if( *fmt != '%' )
		{
			g_string_append_c( ret, *fmt );
			fmt ++;
			continue;
		}
		
		fmt ++;
		while( *fmt )
		{
			/* -char means chop off everything from char */
			if( *fmt == '-' )
			{
				chop = fmt[1];
				if( chop == '\0' )
				{
					g_string_free( ret, TRUE );
					return NULL;
				}
				fmt += 2;
			}
			else if( isdigit( *fmt ) )
			{
				len = 0;
				/* Grab a number. */
				while( isdigit( *fmt ) )
					len = len * 10 + ( *(fmt++) - '0' );
			}
			else if( g_strncasecmp( fmt, "nick", 4 ) == 0 )
			{
				part = bu->nick ? : bu->handle;
				fmt += 4;
				ok |= TRUE;
				break;
			}
			else if( g_strncasecmp( fmt, "handle", 6 ) == 0 )
			{
				part = bu->handle;
				fmt += 6;
				ok |= TRUE;
				break;
			}
			else if( g_strncasecmp( fmt, "full_name", 9 ) == 0 )
			{
				part = bu->fullname;
				fmt += 9;
				ok |= part && *part;
				break;
			}
			else if( g_strncasecmp( fmt, "first_name", 10 ) == 0 )
			{
				part = bu->fullname;
				fmt += 10;
				ok |= part && *part;
				chop = ' ';
				break;
			}
			else if( g_strncasecmp( fmt, "group", 5 ) == 0 )
			{
				part = bu->group ? bu->group->name : NULL;
				fmt += 5;
				break;
			}
			else if( g_strncasecmp( fmt, "account", 7 ) == 0 )
			{
				part = bu->ic->acc->tag;
				fmt += 7;
				break;
			}
			else
			{
				g_string_free( ret, TRUE );
				return NULL;
			}
		}
		
		if( !part )
			continue;
		
		/* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT
		   should do lossy/approximate conversions, so letters with
		   accents don't just get stripped. Note that it depends on
		   LC_CTYPE being set to something other than C/POSIX. */
		if( !( irc && irc->status & IRC_UTF8_NICKS ) )
			part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT",
			                                      "UTF-8", "", NULL, NULL, NULL );
		
		if( chop && ( s = strchr( part, chop ) ) )
			len = MIN( len, s - part );
		
		if( part )
		{
			if( len < INT_MAX )
				g_string_append_len( ret, part, len );
			else
				g_string_append( ret, part );
		}
		g_free( asc );
	}
	
	rets = g_string_free( ret, FALSE );
	if( ok && rets && *rets )
	{
		nick_strip( irc, rets );
		rets[MAX_NICK_LENGTH] = '\0';
		return rets;
	}
	g_free( rets );
	return NULL;
}

void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] )
{
	irc_t *irc = (irc_t*) bu->bee->ui_data;
	int inf_protection = 256;
	irc_user_t *iu;
	
	/* Now, find out if the nick is already in use at the moment, and make
	   subtle changes to make it unique. */
	while( !nick_ok( irc, nick ) ||
	       ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) )
	{
		if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) )
		{
			nick[strlen(nick)+1] = 0;
			nick[strlen(nick)] = '_';
		}
		else
		{
			nick[0] ++;
		}
		
		if( inf_protection-- == 0 )
		{
			g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() );
			
			irc_rootmsg( irc, "Warning: Something went wrong while trying "
			                  "to generate a nickname for contact %s on %s.",
			                  bu->handle, bu->ic->acc->tag );
			irc_rootmsg( irc, "This might be a bug in BitlBee, or the result "
			                  "of a faulty nick_format setting. Will use %s "
			                  "instead.", nick );
			
			break;
		}
	}
}

/* Just check if there is a nickname set for this buddy or if we'd have to
   generate one. */
int nick_saved( bee_user_t *bu )
{
	char *store_handle, *found;
	
	store_handle = clean_handle( bu->handle );
	found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle );
	g_free( store_handle );
	
	return found != NULL;
}

void nick_del( bee_user_t *bu )
{
	g_hash_table_remove( bu->ic->acc->nicks, bu->handle );
}


void nick_strip( irc_t *irc, char *nick )
{
	int len = 0;
	
	if( irc && ( irc->status & IRC_UTF8_NICKS ) )
	{
		gunichar c;
		char *p = nick, *n, tmp[strlen(nick)+1];
		
		while( p && *p )
		{
			c = g_utf8_get_char_validated( p, -1 );
			n = g_utf8_find_next_char( p, NULL );
			
			if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
			                     strchr( nick_uc_chars, c ) ) ) ||
			    !g_unichar_isgraph( c ) )
			{
				strcpy( tmp, n );
				strcpy( p, tmp );
			}
			else
				p = n;
		}
		if( p )
			len = p - nick;
	}
	else
	{
		int i;
		
		for( i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++ )
		{
			if( strchr( nick_lc_chars, nick[i] ) || 
			    strchr( nick_uc_chars, nick[i] ) )
			{
				nick[len] = nick[i];
				len++;
			}
		}
	}
	if( isdigit( nick[0] ) )
	{
		char *orig;
		
		/* First character of a nick can't be a digit, so insert an
		   underscore if necessary. */
		orig = g_strdup( nick );
		g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig );
		g_free( orig );
		len ++;
	}
	while( len <= MAX_NICK_LENGTH )
		nick[len++] = '\0';
}

gboolean nick_ok( irc_t *irc, const char *nick )
{
	const char *s;
	
	/* Empty/long nicks are not allowed, nor numbers at [0] */
	if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH )
		return 0;
	
	if( irc && ( irc->status & IRC_UTF8_NICKS ) )
	{
		gunichar c;
		const char *p = nick, *n;
		
		while( p && *p )
		{
			c = g_utf8_get_char_validated( p, -1 );
			n = g_utf8_find_next_char( p, NULL );
			
			if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
			                     strchr( nick_uc_chars, c ) ) ) ||
			    !g_unichar_isgraph( c ) )
			{
				return FALSE;
			}
			p = n;
		}
	}
	else
	{
		for( s = nick; *s; s ++ )
			if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) )
				return FALSE;
	}
	
	return TRUE;
}

int nick_lc( irc_t *irc, char *nick )
{
	static char tab[128] = { 0 };
	int i;
	
	if( tab['A'] == 0 )
		for( i = 0; nick_lc_chars[i]; i ++ )
		{
			tab[(int)nick_uc_chars[i]] = nick_lc_chars[i];
			tab[(int)nick_lc_chars[i]] = nick_lc_chars[i];
		}
	
	if( irc && ( irc->status & IRC_UTF8_NICKS ) )
	{
		gchar *down = g_utf8_strdown( nick, -1 );
		if( strlen( down ) > strlen( nick ) )
		{
			/* Well crap. Corrupt it if we have to. */
			down[strlen(nick)] = '\0';
		}
		strcpy( nick, down );
		g_free( down );
	}
	
	for( i = 0; nick[i]; i ++ )
		if( nick[i] < 0x7f )
			nick[i] = tab[(int)nick[i]];
	
	return nick_ok( irc, nick );
}

int nick_cmp( irc_t *irc, const char *a, const char *b )
{
	char aa[1024] = "", bb[1024] = "";
	
	strncpy( aa, a, sizeof( aa ) - 1 );
	strncpy( bb, b, sizeof( bb ) - 1 );
	if( nick_lc( irc, aa ) && nick_lc( irc, bb ) )
	{
		return( strcmp( aa, bb ) );
	}
	else
	{
		return( -1 );	/* Hmm... Not a clear answer.. :-/ */
	}
}
