/********************************************************************\
  * 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., 51 Franklin St.,
  Fifth Floor, Boston, MA  02110-1301  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++] = g_ascii_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, "nick_lowercase")) {
			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 (g_ascii_isdigit(*fmt)) {
				len = 0;
				/* Grab a number. */
				while (g_ascii_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)) {
			asc = g_convert_with_fallback(part, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, NULL, NULL);

			if (!asc) {
				/* If above failed, try again without //TRANSLIT.
				   //TRANSLIT is a GNU iconv special and is not POSIX.
				   Other platforms may not support it. */
				asc = g_convert_with_fallback(part, -1, "ASCII", "UTF-8", "", NULL, NULL, NULL);
			}

			part = asc;
		}

		if (part && 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);

		if (set_getbool(&bu->bee->set, "nick_lowercase")) {
			nick_lc(irc, rets);
		}

		truncate_utf8(rets, MAX_NICK_LENGTH);
		return rets;
	}
	g_free(rets);
	return NULL;
}

/* Used for nicks and channel names too! */
void underscore_dedupe(char nick[MAX_NICK_LENGTH + 1])
{
	if (strlen(nick) < (MAX_NICK_LENGTH - 1)) {
		nick[strlen(nick) + 1] = 0;
		nick[strlen(nick)] = '_';
	} else {
		/* We've got no more space for underscores,
		   so truncate it and replace the last three
		   chars with a random "_XX" suffix */
		int len = truncate_utf8(nick, MAX_NICK_LENGTH - 3);
		nick[len] = '_';
		g_snprintf(nick + len + 1, 3, "%2x", rand());
	}
}

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)) {

		underscore_dedupe(nick);

		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;
	gboolean nick_underscores = irc ? set_getbool(&irc->b->set, "nick_underscores") : FALSE;

	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 (nick_underscores && c == ' ') {
				*p = '_';
				p = n;
			} else 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 (nick_underscores && nick[i] == ' ') {
				nick[len] = '_';
				len++;
			} else if (strchr(nick_lc_chars, nick[i]) ||
			    strchr(nick_uc_chars, nick[i])) {
				nick[len] = nick[i];
				len++;
			}
		}
	}
	if (g_ascii_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 || g_ascii_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) {
		/* initialize table so nonchars are mapped to themselves */
		for (i = 0; i < sizeof(tab); i++) {
			tab[i] = i;
		}
		/* replace uppercase chars with lowercase chars */
		for (i = 0; nick_lc_chars[i]; i++) {
			tab[(int) nick_uc_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)) {
			truncate_utf8(down, strlen(nick));
		}
		strcpy(nick, down);
		g_free(down);
	}

	for (i = 0; nick[i]; i++) {
		if (((guchar) nick[i]) < 0x7f) {
			nick[i] = tab[(guchar) 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.. :-/ */
	}
}
