#include <stdio.h>
#include <locale.h>
#include <ctype.h>
#include <stdlib.h>
#include <strings.h>
#include <libintl.h>

#include <errno.h>

/*
 * See »locale -a«, setlocale(3), environ(5)
 *
 * <URL:http://www.in-ulm.de/~mascheck/locale/>
 *
 * "gcc -o checklocale checklocale.c ; ./checklocale"
 * (you might have to link it with libintl.so "gcc -lintl ...")
 */

/*
 * - Parameters: no arguments expected
 * - Purpose: debug all locale categories separately, particularly if
 *            LC_ALL or LANG are set
 */


/*
 * Main action is setting the locale.
 *
 * setlocale(<locale>, "")
 *   sets that <locale> to the value from the environment variables
 *   LC_ALL, <locale> or LANG (with this priority).
 *
 * setlocale(<locale>, <value>)
 *   e.g. sets that <locale> to that <value>
 *
 * setlocale(LC_ALL, "")
 *   is special, it tries to set the locale categories which are given in
 *   the environment with the same priorities like above - it's a shortcut.
 *   LC_ALL in the /environment/ has also a special meaning,
 *   it's value overrides all other variables (caution).
 *
 * LANG (environment) is also special, it's not a locale but
 *   only a possible value. If neither LC_ALL nor the specific LC_*
 *   are set, the value of LANG is used for the various categories.
 *
 * LC_MESSAGES: If this  succeeds, it means that libc supports that value.
 *   Success doesn't guarantee non-english messages, though, because
 *   each programs has to support this with its own message catalog.
 *   If you want to try a message catalog at a location different from
 *   the default, the set CHECKLOCALE_MSGCATALOG_DIR below.
 * 
 * Use a syscall tracer ([k]truss, [k|s|]trace, par) if you're
 * in doubt about your installation.
 */

/* #define CHECKLOCALE_MSGCATALOG_DIR NULL                       /* the system default */
#define CHECKLOCALE_MSGCATALOG_DIR "/home/mascheck/lib/locale" /* your choice */

/* Sun BSD */
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif


char *locale_strings[6] = {
    "LC_CTYPE",
    "LC_NUMERIC",
    "LC_TIME",
    "LC_COLLATE",
    "LC_MONETARY",
    "LC_MESSAGES",
};

int locale_categories[6] = {
    LC_CTYPE,
    LC_NUMERIC,
    LC_TIME,
    LC_COLLATE,
    LC_MONETARY,
    LC_MESSAGES,
};


/*
 * print out all eight bit characters
 * substitute the unprintables with '#'
 */
void do_isprint_once()
{
    int i, j, k;
    static int once = 1;

    if (once) {
	once = 0;

	printf("\n  Testing LC_CTYPE with isprint():\n  ");

	k = 0;
	for (i = 0; i < 16; i++) {
	    for (j = 0; j < 16; j++) {

		if (isprint(k)) {
		    printf("%c ", k);
		}
		else {
		    printf("# ");
		}
		k++;

	    }
	    printf("\n  ");
	}
	printf("\n");
    }
}

/*
 * Print a *possibly* localized libc error message.
 * Application specific messages are not tested here.
 */
void do_error_once()
{
    static int once = 1;

    if (once) {
	once = 0;
	printf("- Testing LC_MESSAGES with perror() for EAGAIN, that is, a libc message.\n");
	printf ("  Message catalogs are in %s, according to bindtextdomain().\n",
	    bindtextdomain ("libc", CHECKLOCALE_MSGCATALOG_DIR));
	errno = EAGAIN;
	perror("  perror()   tells");
	printf("  strerror() tells: %s\n", strerror(EAGAIN));
	printf("\n");
    }
}

/*
 * For handling locale environment variables
 * the values (char*)NULL and "" make no difference.
 */
char *my_getenv (char *env)
{
    char *value = getenv(env);
    if (value) {
	/* if (!strcmp(value, "(null)"))
		printf("alarm for %s!\n", env); */
	if (!strcmp(value, "")) {
	    value= (char*)NULL;
	}
    }
    return(value);
}

int main()
{
    int i;		/* for the categories */
    char *value;	/* for the categories */
    char *value_LC_ALL; /* special, needed several times */
    char *value_LANG;	/* special, needed several times */
    char *setlocale_ret; /* returned by setlocale() */

    /* When debugging, only use LANG if LC_ALL is unset */
    char *implicit_debug_category;

    int status = EXIT_SUCCESS;

    printf("\n[Latin1/9] If there's no real copyrightsymbol at the end of this sentence,\n"
	"then your terminal/terminalemulator/font is not ISO8859-1/15 ready: %c\n\n", '©');

    printf("- Current environment settings:\n");

    /*
     * the "not existent" categories LC_ALL and LANG:
     */
    value_LC_ALL = my_getenv("LC_ALL");
    value_LANG = my_getenv("LANG");

    if (value_LC_ALL)
	printf("  LC_ALL      = \"%s\"\n", value_LC_ALL);
    if (value_LANG)
	printf("  LANG        = \"%s\"\n", value_LANG);


    /*
     * The "real" categories:
     */
    for (i=0; i<6; i++) {
	value = my_getenv(locale_strings[i]);
	if (value)
	    printf("  %-11s = \"%s\"\n", locale_strings[i], value);
    }

    if (value_LC_ALL) {
	printf("\n");
	setlocale_ret = setlocale(LC_ALL, "");
	if (setlocale_ret != (char*)NULL) {
	    printf("- Overriding all locale categories with LC_ALL succeeded\n");
	    printf("  (LC_ALL is only useful for debugging purposes).\n");
	    if (strcmp(setlocale_ret, value_LC_ALL)) {
		printf("  Note: setlocale() returned \"%s\"\n", setlocale_ret);
		printf("  This might be a (system dependent) composite value.\n");
	    }

	    /*
	     * LC_ALL succeeded.
	     * Most interesting: LC_CTYPE and LC_MESSAGES
	     */
	    do_isprint_once();
	    do_error_once();
	} else {
	    printf("- Overriding all locale categories with LC_ALL failed.\n");
	    printf("  You really should unset it now.\n");
	    printf("  Programs will usually fall back to \"C\" now.\n\n");
	}
    }

    /*
     * LANG is only of interest if LC_ALL is unset.
     * But even more: only then it's usable for setlocale() in any way
     * (for debugging purposes here).
     */
    if (!value_LC_ALL && value_LANG) {
	printf("\n");
	setlocale_ret = setlocale(LC_ALL, "");
	if (setlocale_ret != (char*)NULL) {
	    printf("- Implicitly setting all locale categories with LANG succeeded.\n");
	    if (strcmp(setlocale_ret, value_LANG)) {
		printf("  Note: setlocale() returned \"%s\"\n", setlocale_ret);
		printf("  This might be a (system dependent) composite value.\n");
	    }

	    /*
	     * LANG succeeded.
	     * Most interesting: LC_CTYPE and LC_MESSAGES
	     */
	    do_isprint_once();
	    do_error_once();
	} else {
	    printf("- Implicitly setting all locale categories with LANG failed.\n");
	    printf("  You might want to unset/fix it now and/or set supported"
		    "  categories instead.\n");
	    status = EXIT_FAILURE;
	}
    }


    for (i=0; i<6; i++) {
	value = my_getenv(locale_strings[i]);

	if (value) {
	    setlocale_ret = setlocale(locale_categories[i], value);
	    if (setlocale_ret != (char*)NULL) {
		printf("- Setting %s to \"%s\" succeeded.\n", locale_strings[i], value);
		if (strcmp(setlocale_ret, value)) {
		    printf("  Note: setlocale() returned \"%s\".\n", setlocale_ret);
		}
		if (locale_categories[i] == LC_MESSAGES) {
		    do_error_once();
		}
	    } else {
		printf("- Setting %s to its own value failed.\n",
			locale_strings[i]);
		status = EXIT_FAILURE;
	    }
	    /*
	     * LC_CTYPE might always be of interest,
	     * even if setlocale() failed.
	     */
	    if (locale_categories[i] == LC_CTYPE) {
		do_isprint_once();
	    }
	} else {
	    /*
	     * If LC_ALL is set, test this value individually.
	     * Otherwise, if LANG is set, test this value individually.
	     * Otherwise there's no way for an implicit setting, skip.
	     */
	    if (value_LC_ALL)
		implicit_debug_category = "LC_ALL";
	    else if (value_LANG)
		implicit_debug_category = "LANG";
	    else
		implicit_debug_category = (char*)NULL;

	    if (implicit_debug_category) {
		setlocale_ret = setlocale(locale_categories[i], "");
		if (setlocale_ret != (char*)NULL) {
		printf("- Implicitly setting %12s by %s succeeded.\n",
			    locale_strings[i], implicit_debug_category);
		if (strcmp(setlocale_ret, my_getenv(implicit_debug_category))) {
		    printf("  Note: setlocale() returned \"%s\".\n", setlocale_ret);
		}
		    if (!strcmp(locale_strings[i], "LC_CTYPE")) {
			do_isprint_once();
		    }
		} else {
		    printf("- Implicitly setting %12s by %s failed.\n",
			    locale_strings[i], implicit_debug_category);
		    status = EXIT_FAILURE;
		}
	    }
	}
    }
    return (status);
}
