/*
 * MOC - music on console
 * Copyright (C) 2005,2006 Damian Pietras <daper@daper.net>
 *
 * 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.
 *
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdarg.h>

#ifdef HAVE_ICONV
# include <iconv.h>
#endif
#ifdef HAVE_NL_TYPES_H
# include <nl_types.h>
#endif
#ifdef HAVE_LANGINFO_H
# include <langinfo.h>
#endif

#if defined HAVE_NCURSESW_CURSES_H
# include <ncursesw/curses.h>
#elif defined HAVE_NCURSESW_H
# include <ncursesw.h>
#elif defined HAVE_NCURSES_CURSES_H
# include <ncurses/curses.h>
#elif defined HAVE_NCURSES_H
# include <ncurses.h>
#elif defined HAVE_CURSES_H
# include <curses.h>
#endif

#include <assert.h>
#include <string.h>
#include <errno.h>
#include <wchar.h>
#include <locale.h>

#include "common.h"
#include "log.h"
#include "options.h"
#include "utf8.h"
#include "rcc.h"
#include <unicode/ucnv.h>
#include <unicode/ucsdet.h>

static char *locale_charset = NULL;
static int using_utf8 = 0;

static iconv_t iconv_desc = (iconv_t)(-1);
static iconv_t files_iconv_desc = (iconv_t)(-1);
static iconv_t files_iconv_r_desc = (iconv_t)(-1);
static iconv_t xterm_iconv_desc = (iconv_t)(-1);


/* Return a malloc()ed string converted using iconv().
 * If for_file_name is not 0, use the conversion defined for file names.
 * For NULL returns NULL. */
char *iconv_str (const iconv_t desc, const char *str)
{
	char buf[512];
#ifdef FREEBSD
	const char *inbuf;
#else
	char *inbuf;
#endif
	char *outbuf;
	char *str_copy;
	size_t inbytesleft, outbytesleft;
	char *converted;

	if (!str)
		return NULL;
	if (desc == (iconv_t)(-1))
		return xstrdup (str);

	inbuf = str_copy = xstrdup (str);
	outbuf = buf;
	inbytesleft = strlen(inbuf);
	outbytesleft = sizeof(buf) - 1;

	iconv (desc, NULL, NULL, NULL, NULL);

	while (inbytesleft) {
		if (iconv(desc, &inbuf, &inbytesleft, &outbuf,
					&outbytesleft)
				== (size_t)(-1)) {
			if (errno == EILSEQ) {
				inbuf++;
				inbytesleft--;
				if (!--outbytesleft) {
					*outbuf = 0;
					break;
				}
				*(outbuf++) = '#';
			}
			else if (errno == EINVAL) {
				*(outbuf++) = '#';
				*outbuf = 0;
				break;
			}
			else if (errno == E2BIG) {
				outbuf[sizeof(buf)-1] = 0;
				break;
			}
		}
	}

	*outbuf = 0;
	converted = xstrdup (buf);
	free (str_copy);

	return converted;
}

#ifdef __CYGWIN__
/* Emulate codeset conversion for filesystem in cygwin */
static size_t cyg_wcstombs (char *dst, const wchar_t *src, size_t len)
{
	char buf[10];
	char *ptr = dst;
	wchar_t *pwcs = (wchar_t *) src;
	size_t n = 0;
	char *orig_locale = xstrdup (setlocale (LC_ALL, NULL));

	while (n < len && *pwcs) {
		int bytes = wctomb (buf, *pwcs);
		if (bytes == -1) {
			/* Convert chars invalid in the current codepage to a sequence
			   ASCII CAN; UTF-8 representation of invalid char. */
			buf[0] = 0x18; /* ASCII CAN */
			setlocale (LC_ALL, "C.UTF-8");
			bytes = wctomb (buf + 1, *pwcs);
			setlocale (LC_ALL, orig_locale);
			if (bytes == -1) {
				++pwcs;
				continue;
			}
			++bytes; /* Add the ASCII CAN to the byte count. */
		}
		if (n + bytes < len) {
			memcpy (ptr, buf, bytes);
			ptr += bytes;
			n += bytes;
		}
		pwcs++;
	}
	free (orig_locale);
	dst[n] = '\0';
	return n;
}
#endif

char *files_iconv_str (const char *str)
{
    return iconv_str (files_iconv_desc, str);
}

char *files_iconv_r_str (const char *str)
{
#ifdef __CYGWIN__
	wchar_t wbuf[PATH_MAX];
	char buf[PATH_MAX];
	char *orig_locale = xstrdup (setlocale (LC_ALL, NULL));
	setlocale (LC_ALL, "C.UTF-8");
	mbstowcs (wbuf, str, PATH_MAX);
	setlocale (LC_ALL, orig_locale);
	cyg_wcstombs (buf, wbuf, PATH_MAX);
	free (orig_locale);
	return xstrdup (buf);
#else
	return iconv_str (files_iconv_r_desc, str);
#endif
}

char *xterm_iconv_str (const char *str)
{
    return iconv_str (xterm_iconv_desc, str);
}

/* Codesets for languages with a high percentage of 0x80-0xff */
static char *codesets_full_8bit[] = {
	"UTF-8",
	"CP932",
	"CP936","GB18030",
	"CP949",
	"CP950",
	"BIG5-HKSCS",
	"CP866","KOI8-R",
	"Windows-874",
	"Windows-1251",
	"Windows-1253",
	"Windows-1255",
	"TIS-620",
	"ISO-8859-5",
	"ISO-8859-6",
	"ISO-8859-7",
	"ISO-8859-8",
	"ISO-8859-11",
	NULL};

#define DO_UCNV(x, y, z) \
	if (x##_str && *x##_str) { \
		char revert[4096]; \
		int32_t len; \
		x##_err = U_ZERO_ERROR; \
		len = ucnv_convert (y, z, x, sizeof (x), x##_str, -1, &x##_err); \
		ucnv_convert (z, y, revert, sizeof (revert), x, len, &x##_err); \
		if (strcmp (x##_str, revert)) \
			x##_err = U_INVALID_CHAR_FOUND; \
	}

#define MAX_DET 8
#define MAX_CHARSET_LEN 32
#define DO_CHARDET(x) \
	if (x##_str && *x##_str) { \
		UErrorCode status = U_ZERO_ERROR; \
		UCharsetDetector *csd = ucsdet_open (&status); \
		const UCharsetMatch **match; \
		char buf[65] = ""; \
		int32_t n = 64; \
		do { \
			strncat (buf, x##_str, n); \
			n -= x##_len; \
		} while (n > 0); \
		ucsdet_setText (csd, buf, -1, &status); \
		match = ucsdet_detectAll (csd, &n, &status); \
		if (n > MAX_DET) n = MAX_DET; \
		for (int i = 0; i < n; i++) { \
			const char *name = ucsdet_getName (match[i], &status); \
			strncpy (x##_charset_det[i], name, MAX_CHARSET_LEN); \
			x##_charset_det[i][MAX_CHARSET_LEN - 1] = '\0';\
		} \
		ucsdet_close (csd); \
	} else { \
		strncpy (x##_charset_det[0], "ASCII", MAX_CHARSET_LEN); \
		x##_charset_det[0][MAX_CHARSET_LEN - 1] = '\0';\
	} \
	PATCH_CHARSET_DET (x##_charset_det);

#define PATCH_CHARSET_DET(x) \
	for (int i = 0; *x[i]; i++) { \
		if (strcasecmp (x[i], "SHIFT_JIS") == 0) \
			strcpy (x[i], "CP932"); \
		else if (strcasecmp (x[i], "GBK") == 0) \
			strcpy (x[i], "CP936"); \
		else if (strcasecmp (x[i], "EUC-KR") == 0) \
			strcpy (x[i], "CP949"); \
		else if (strcasecmp (x[i], "BIG5") == 0) \
			strcpy (x[i], "CP950"); \
	}

#define MAKE_CHARSET_CAND(x) \
do { \
	int flags = maybe_full_8bit (x##_str); \
	int add_all_charset_det = 0; \
	int add_current_charset_det = 0; \
	if (x##_maybe_utf8) \
		x##_charset[x##_cand_cnt++] = "UTF-8"; \
	if ((flags & FULL_8BIT) && (flags & USED_0X80_0X9F)) { \
		if (is_full_8bit_with_0x80_0x9f (x##_charset_det[0])) \
			add_current_charset_det = 1; \
		else \
			add_all_charset_det = 1; \
	} else if (flags & USED_0X80_0X9F) { \
		if (use_0x80_0x9f (x##_charset_det[0])) \
			add_current_charset_det = 1; \
		else \
			add_all_charset_det = 1; \
	} else if (flags & FULL_8BIT) { \
		if (is_full_8bit (x##_charset_det[0])) \
			add_current_charset_det = 1; \
		else \
			add_all_charset_det = 1; \
	} else { \
		if (x##_might_be_utf8) { \
			x##_charset[x##_cand_cnt++] = "ISO-8859-1"; \
			x##_might_be_utf8 = 0; \
		} \
		add_current_charset_det = 1; \
	} \
	if (add_current_charset_det) { \
		for (int i = 0; i < MAX_DET; i++) { \
			if (*x##_charset_det[i] == '\0') \
				break; \
			ADD_CHARSET_DET_1ST (x); \
		} \
		for (int i = 0; i < MAX_DET; i++) { \
			if (*x##_charset_det[i] == '\0') \
				break; \
			ADD_CHARSET_DET_2ND (x); \
		} \
	} \
	if (add_all_charset_det) { \
		for (int i = 0; i < MAX_DET; i++) { \
			if (*title_charset_det[i]) \
				ADD_CHARSET_DET_1ST (title); \
			if (*artist_charset_det[i]) \
				ADD_CHARSET_DET_1ST (artist); \
			if (*album_charset_det[i]) \
				ADD_CHARSET_DET_1ST (album); \
		} \
		for (int i = 0; i < MAX_DET; i++) { \
			if (*title_charset_det[i]) \
				ADD_CHARSET_DET_2ND (title); \
			if (*artist_charset_det[i]) \
				ADD_CHARSET_DET_2ND (artist); \
			if (*album_charset_det[i]) \
				ADD_CHARSET_DET_2ND (album); \
		} \
	} \
	if (x##_might_be_utf8) \
		x##_charset[x##_cand_cnt++] = "ISO-8859-1"; \
	for (int i=0; x##_charset[i]; i++) \
		logit ("Conversion caididates %s: %s", #x, x##_charset[i]); \
} while (0);

#define ADD_CHARSET_DET_1ST(x) \
do { \
	if (strcasecmp (x##_charset_det[i], "KOI8-R") == 0 \
		&& count_koi8_r_graph (x##_str) * 8 > strlen (x##_str)) \
		; /* Add it in the 2nd stage */ \
	else if ((flags & FULL_8BIT) && (flags & USED_0X80_0X9F)) { \
		if (is_full_8bit_with_0x80_0x9f (x##_charset_det[i])) { \
			if (strncasecmp (x##_charset_det[i], "Windows-", 8) == 0) \
				/* Windows-xxxx uses 0x80-0x9f, but it's rare case. */ \
				; /* Add it in the 2nd stage */ \
			else \
				x##_charset[x##_cand_cnt++] = x##_charset_det[i]; \
		} \
	} else if (flags & USED_0X80_0X9F) { \
		if (use_0x80_0x9f (x##_charset_det[i])) \
			x##_charset[x##_cand_cnt++] = x##_charset_det[i]; \
	} else if (flags & FULL_8BIT) { \
		if (is_full_8bit (x##_charset_det[i])) \
			x##_charset[x##_cand_cnt++] = x##_charset_det[i]; \
	} \
} while (0);

#define ADD_CHARSET_DET_2ND(x) \
do { \
	if (strcasecmp (x##_charset_det[i], "KOI8-R") == 0 \
			&& count_koi8_r_graph (x##_str) * 8 > strlen (x##_str)) \
		x##_charset[x##_cand_cnt++] = x##_charset_det[i]; \
	else if ((flags & FULL_8BIT) && (flags & USED_0X80_0X9F)) { \
		if (strncasecmp (x##_charset_det[i], "Windows-", 8) == 0) \
			x##_charset[x##_cand_cnt++] = x##_charset_det[i]; \
		else if (!is_full_8bit (x##_charset_det[i]) \
				&& use_0x80_0x9f (x##_charset_det[i])) \
			x##_charset[x##_cand_cnt++] = x##_charset_det[i]; \
	} else if (flags & FULL_8BIT) { \
		if (!is_full_8bit (x##_charset_det[i])) \
			x##_charset[x##_cand_cnt++] = x##_charset_det[i]; \
	} \
} while (0);

#define TRY_UCNV_CAND(x) \
	for (int i = 0; i < x##_cand_cnt; i++) { \
		logit ("Try codeset conversion %s: %s", #x, x##_charset[i]); \
		if (strcasecmp (x##_charset[i], "UTF-8") == 0) { \
			/* Check if convertable from UTF-8 */ \
			DO_UCNV (x, "UTF-16", "UTF-8"); \
			if (U_FAILURE (x##_err)) \
				continue; /* Not UTF-8 */ \
			break; \
		} \
		DO_UCNV (x, "UTF-8", x##_charset[i]); \
		if (U_FAILURE (x##_err)) \
			continue; \
		if (tags->x) { \
			char *old = tags->x; \
			tags->x = xstrdup (x); \
			free (old); \
		} \
		break; \
	} \
	free (x##_str); \
	x##_str = NULL; \
	logit ("Final codeset conversion result (%s): %s", #x, tags->x);

static inline int is_full_8bit (char *charset)
{
	for (int i = 0; codesets_full_8bit[i]; i++)
		if (strcasecmp (codesets_full_8bit[i], charset) == 0)
			return 1;
	return 0;
}

static inline int use_0x80_0x9f (char *charset)
{
	if (strncasecmp (charset, "ISO", 3) == 0)
		return 0;
	if (strncasecmp (charset, "TIS", 3) == 0)
		return 0;
	if (strncasecmp (charset, "EUC", 3) == 0)
		return 0;
	if (strncasecmp (charset, "Windows", 7) == 0)
		return 1;
	if (strcasecmp (charset, "UTF-8") == 0)
		return 1;
	if (!is_full_8bit (charset))
		return 0;
	return 1;
}

#define FULL_8BIT 1
#define USED_0X80_0X9F 2
static inline int maybe_full_8bit (char *str)
{
	int cnt1 = 0, cnt2 = 0;
	int ret = 0;
	unsigned char *p;
	if (str)
		for (p = (unsigned char *) str; *p; p++, cnt1++) {
			cnt2 += ((*p & 0x80) != 0);
			ret |= (*p >= 0x80 && *p <= 0x9f) ? USED_0X80_0X9F : 0;
		}
	return ret | ((cnt2*4 > cnt1) ? FULL_8BIT : 0); /* 0x80-0xff > 25% */
}

static inline size_t count_char_range (char *str, char a, char b)
{
	size_t cnt = 0;
	unsigned char *p;
	if (str)
		for (p = (unsigned char *) str; *p; p++)
			cnt += (*p >= (unsigned char) a && *p <= (unsigned char) b);
	return cnt;
}

static inline size_t count_koi8_r_graph (char *str)
{
	return count_char_range (str, 0x80, 0xbe);
}

static inline int is_full_8bit_with_0x80_0x9f (char *charset)
{
	return is_full_8bit (charset) && use_0x80_0x9f (charset);
}

/* Fix bad-mannered ID3v2 tags generated by Windows Media Player, etc. */
void fixtags_iconv (struct file_tags *tags)
{
	char title[4096], artist[4096], album[4096];
	char *title_str = NULL, *artist_str = NULL, *album_str = NULL;
	size_t title_len, artist_len, album_len;
	UErrorCode title_err, artist_err, album_err;
	char title_charset_det[MAX_DET][MAX_CHARSET_LEN] = {'\0', };
	char artist_charset_det[MAX_DET][MAX_CHARSET_LEN] = {'\0', };
	char album_charset_det[MAX_DET][MAX_CHARSET_LEN] = {'\0', };
	char *title_charset[MAX_DET*3 + 2] = {NULL, };
	char *artist_charset[MAX_DET*3 + 2] = {NULL, };
	char *album_charset[MAX_DET*3 + 2] = {NULL, };
	int title_cand_cnt = 0;
	int artist_cand_cnt = 0;
	int album_cand_cnt = 0;
	int title_maybe_utf8 = 0;
	int artist_maybe_utf8 = 0;
	int album_maybe_utf8 = 0;
	int title_might_be_utf8 = 0;
	int artist_might_be_utf8 = 0;
	int album_might_be_utf8 = 0;

	if (tags->title == NULL && tags->artist == NULL && tags->album == NULL)
		return;

	if (tags->title) {
		title_str = xstrdup (tags->title);
		title_len = strlen (title_str);
	}
	if (tags->artist) {
		artist_str = xstrdup (tags->artist);
		artist_len = strlen (artist_str);
	}
	if (tags->album) {
		album_str = xstrdup (tags->album);
		album_len = strlen (album_str);
	}

	DO_UCNV (title, "UTF-16", "UTF-8");
	if (U_SUCCESS (title_err))
		title_maybe_utf8 = 1;

	DO_UCNV (artist, "UTF-16", "UTF-8");
	if (U_SUCCESS (artist_err))
		artist_maybe_utf8 = 1;

	DO_UCNV (album, "UTF-16", "UTF-8");
	if (U_SUCCESS (album_err))
		album_maybe_utf8 = 1;

	/* Revert ISO-8859-1 -> UTF-8 conversion */
	DO_UCNV (title, "ISO-8859-1", "UTF-8");
	if (U_SUCCESS (title_err)) { /* Successfully reverted */
		free (title_str);
		title_str = xstrdup (title);
		title_len = strlen (title_str);
		if (title_maybe_utf8) {
			title_maybe_utf8 = 0;
			title_might_be_utf8 = 1;
		}
	}

	DO_UCNV (artist, "ISO-8859-1", "UTF-8");
	if (U_SUCCESS (artist_err)) { /* Successfully reverted */
		free (artist_str);
		artist_str = xstrdup (artist);
		artist_len = strlen (artist_str);
		if (artist_maybe_utf8) {
			artist_maybe_utf8 = 0;
			artist_might_be_utf8 = 1;
		}
	}

	DO_UCNV (album, "ISO-8859-1", "UTF-8");
	if (U_SUCCESS (album_err)) { /* Successfully reverted */
		free (album_str);
		album_str = xstrdup (album);
		album_len = strlen (album_str);
		if (album_maybe_utf8) {
			album_maybe_utf8 = 0;
			album_might_be_utf8 = 1;
		}
	}

	DO_CHARDET (title);
	DO_CHARDET (artist);
	DO_CHARDET (album);

	MAKE_CHARSET_CAND (title);
	MAKE_CHARSET_CAND (artist);
	MAKE_CHARSET_CAND (album);

	TRY_UCNV_CAND (title);
	TRY_UCNV_CAND (artist);
	TRY_UCNV_CAND (album);

	return;
}

int xwaddstr (WINDOW *win, const char *str)
{
	int res;

	if (using_utf8)
		res = waddstr (win, str);
	else {
		char *lstr = iconv_str (iconv_desc, str);

		res = waddstr (win, lstr);
		free (lstr);
	}

	return res;
}

/* Convert multi-byte sequence to wide characters.  Change invalid UTF-8
 * sequences to '?'.  'dest' can be NULL as in mbstowcs().
 * If 'invalid_char' is not NULL it will be set to 1 if an invalid character
 * appears in the string, otherwise 0. */
static size_t xmbstowcs (wchar_t *dest, const char *src, size_t len,
		int *invalid_char)
{
	mbstate_t ps;
	size_t count = 0;

	assert (src != NULL);
	assert (!dest || len > 0);

	memset (&ps, 0, sizeof(ps));

	if (dest)
		memset (dest, 0, len * sizeof(wchar_t));

	if (invalid_char)
		*invalid_char = 0;

	while (src && (len || !dest)) {
		size_t res;

		res = mbsrtowcs (dest, &src, len, &ps);
		if (res != (size_t)-1) {
			count += res;
			src = NULL;
		}
		else {
			size_t converted;

			src++;
			if (dest) {
				converted = wcslen (dest);
				dest += converted;
				count += converted;
				len -= converted;

				if (len > 1) {
					*dest = L'?';
					dest++;
					*dest = L'\0';
					len--;
				}
				else
					*(dest - 1) = L'\0';
			}
			else
				count++;
			memset (&ps, 0, sizeof(ps));

			if (invalid_char)
				*invalid_char = 1;
		}
	}

	return count;
}

int xwaddnstr (WINDOW *win, const char *str, const int n)
{
	int res, width, inv_char;
	wchar_t *ucs;
	char *mstr, *lstr;
	size_t size, num_chars;

	assert (n > 0);
	assert (str != NULL);

	mstr = iconv_str (iconv_desc, str);

	size = xmbstowcs (NULL, mstr, -1, NULL) + 1;
	ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);
	xmbstowcs (ucs, mstr, size, &inv_char);
	width = wcswidth (ucs, WIDTH_MAX);

	if (width == -1) {
		size_t clidx;
		for (clidx = 0; clidx < size - 1; clidx++) {
			if (wcwidth (ucs[clidx]) == -1)
				ucs[clidx] = L'?';
		}
		width = wcswidth (ucs, WIDTH_MAX);
		inv_char = 1;
	}

	if (width > n) {
		while (width > n)
			width -= wcwidth (ucs[--size]);
		ucs[size] = L'\0';
	}

	num_chars = wcstombs (NULL, ucs, 0);
	lstr = (char *)xmalloc (num_chars + 1);

	if (inv_char)
		wcstombs (lstr, ucs, num_chars + 1);
	else
		snprintf (lstr, num_chars + 1, "%s", mstr);

	res = waddstr (win, lstr);

	free (ucs);
	free (lstr);
	free (mstr);
	return res;
}

int xmvwaddstr (WINDOW *win, const int y, const int x, const char *str)
{
	int res;

	if (using_utf8)
		res = mvwaddstr (win, y, x, str);
	else {
		char *lstr = iconv_str (iconv_desc, str);

		res = mvwaddstr (win, y, x, lstr);
		free (lstr);
	}

	return res;
}

int xmvwaddnstr (WINDOW *win, const int y, const int x, const char *str,
		const int n)
{
	int res;

	if (using_utf8)
		res = mvwaddnstr (win, y, x, str, n);
	else {
		char *lstr = iconv_str (iconv_desc, str);

		res = mvwaddnstr (win, y, x, lstr, n);
		free (lstr);
	}

	return res;
}

int xwprintw (WINDOW *win, const char *fmt, ...)
{
	va_list va;
	int res;
	char *buf;

	va_start (va, fmt);
	buf = format_msg_va (fmt, va);
	va_end (va);

	res = waddstr (win, buf);

	free (buf);

	return res;
}

int xprintf (const char *fmt, ...)
{
	va_list va;
	int res;
	char *buf;

	va_start (va, fmt);
	buf = format_msg_va (fmt, va);
	va_end (va);

	if (using_utf8)
		res = printf ("%s", buf);
	else {
		char *lstr = iconv_str (iconv_desc, buf);

		res = printf ("%s", lstr);
		free (lstr);
	}

	free (buf);

	return res;
}

static void iconv_cleanup ()
{
	if (iconv_desc != (iconv_t)(-1)
			&& iconv_close(iconv_desc) == -1)
		log_errno ("iconv_close() failed", errno);
}

void utf8_init ()
{
#ifdef HAVE_NL_LANGINFO_CODESET
#ifdef HAVE_NL_LANGINFO
	locale_charset = xstrdup (nl_langinfo(CODESET));
	assert (locale_charset != NULL);

	if (!strcmp(locale_charset, "UTF-8")) {
#ifdef HAVE_NCURSESW
		logit ("Using UTF8 output");
		using_utf8 = 1;
#else /* HAVE_NCURSESW */
		locale_charset = xstrdup ("US-ASCII");
		logit ("Using US-ASCII conversion - compiled without libncursesw");
#endif /* HAVE_NCURSESW */
	}
	else
		logit ("Terminal character set: %s", locale_charset);
#else /* HAVE_NL_LANGINFO */
	locale_charset = xstrdup ("US-ASCII");
	logit ("Assuming US-ASCII terminal character set");
#endif /* HAVE_NL_LANGINFO */
#endif /* HAVE_NL_LANGINFO_CODESET */

	if (!using_utf8 && locale_charset) {
		iconv_desc = iconv_open (locale_charset, "UTF-8");
		if (iconv_desc == (iconv_t)(-1))
			log_errno ("iconv_open() failed", errno);
	}

	if (options_get_bool ("FileNamesIconv")) {
		files_iconv_desc = iconv_open ("UTF-8", locale_charset);
		files_iconv_r_desc = iconv_open (locale_charset, "UTF-8");
	}

	if (options_get_bool ("NonUTFXterm"))
		xterm_iconv_desc = iconv_open (locale_charset, "UTF-8");
}

void utf8_cleanup ()
{
	if (locale_charset)
		free (locale_charset);
	iconv_cleanup ();
}

/* Return the number of columns the string occupies when displayed. */
size_t strwidth (const char *s)
{
	wchar_t *ucs;
	size_t size;
	size_t width;
	char *orig_locale = xstrdup (setlocale (LC_ALL, NULL));

	assert (s != NULL);

	setlocale (LC_ALL, "C.UTF-8");
	size = xmbstowcs (NULL, s, -1, NULL) + 1;
	ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);
	xmbstowcs (ucs, s, size, NULL);
	setlocale (LC_ALL, orig_locale);
	free (orig_locale);
	width = wcswidth (ucs, WIDTH_MAX);
	free (ucs);

	return width;
}

/* Return a malloc()ed string containing the tail of 'str' up to a
 * maximum of 'len' characters (in columns occupied on the screen). */
char *xstrtail (const char *str, const int len)
{
	wchar_t *ucs;
	wchar_t *ucs_tail;
	size_t size;
	int width;
	char *tail;
	char *orig_locale = xstrdup (setlocale (LC_ALL, NULL));

	assert (str != NULL);
	assert (len > 0);

	setlocale (LC_ALL, "C.UTF-8");
	size = xmbstowcs(NULL, str, -1, NULL) + 1;
	ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);
	xmbstowcs (ucs, str, size, NULL);
	setlocale(LC_ALL, orig_locale);
	ucs_tail = ucs;

	width = wcswidth (ucs, WIDTH_MAX);
	assert (width >= 0);

	while (width > len)
		width -= wcwidth (*ucs_tail++);

	setlocale (LC_ALL, "C.UTF-8");
	size = wcstombs (NULL, ucs_tail, 0) + 1;
	tail = (char *)xmalloc (size);
	wcstombs (tail, ucs_tail, size);
	setlocale(LC_ALL, orig_locale);

	free (ucs);
	free (orig_locale);

	return tail;
}
