/*
 * fontconfig/src/fclang.c
 *
 * Copyright © 2002 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the author(s) not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The authors make no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include "fcint.h"

#if ENABLE_FREETYPE
#include "fcftint.h"
#endif

/* Objects MT-safe for readonly access. */

/*
 * Keep Han languages separated by eliminating languages
 * that the codePageRange bits says aren't supported
 */

static const struct {
    char          bit;
    const FcChar8 lang[6];
} FcCodePageRange[] = {
    { 17, "ja"    },
    { 18, "zh-cn" },
    { 19, "ko"    },
    { 20, "zh-tw" },
};

#define NUM_CODE_PAGE_RANGE (int)(sizeof FcCodePageRange / sizeof FcCodePageRange[0])

static int
FcLangSetIndex (const FcChar8 *lang);

static void
FcLangSetBitSet (FcLangSet   *ls,
                 unsigned int id)
{
    unsigned int bucket;

    id = fcLangCharSetIndices[id];
    bucket = id >> 5;
    if (bucket >= ls->map_size)
	return; /* shouldn't happen really */

    ls->map[bucket] |= ((FcChar32)1U << (id & 0x1f));
}

static FcBool
FcLangSetBitGet (const FcLangSet *ls,
                 unsigned int     id)
{
    unsigned int bucket;

    id = fcLangCharSetIndices[id];
    bucket = id >> 5;
    if (bucket >= ls->map_size)
	return FcFalse;

    return ((ls->map[bucket] >> (id & 0x1f)) & 1) ? FcTrue : FcFalse;
}

static void
FcLangSetBitReset (FcLangSet   *ls,
                   unsigned int id)
{
    unsigned int bucket;

    id = fcLangCharSetIndices[id];
    bucket = id >> 5;
    if (bucket >= ls->map_size)
	return; /* shouldn't happen really */

    ls->map[bucket] &= ~((FcChar32)1U << (id & 0x1f));
}

FcLangSet *
FcLangSetFromCharSet (const FcCharSet *charset,
                      const FcChar8   *exclusiveLang)
{
    int              i, j;
    FcChar32         missing;
    const FcCharSet *exclusiveCharset = 0;
    FcLangSet       *ls;

    if (exclusiveLang)
	exclusiveCharset = FcLangGetCharSet (exclusiveLang);
    ls = FcLangSetCreate();
    if (!ls)
	return 0;
    if (FcDebug() & FC_DBG_LANGSET) {
	printf ("font charset");
	FcCharSetPrint (charset);
	printf ("\n");
    }
    for (i = 0; i < NUM_LANG_CHAR_SET; i++) {
	if (FcDebug() & FC_DBG_LANGSET) {
	    printf ("%s charset", fcLangCharSets[i].lang);
	    FcCharSetPrint (&fcLangCharSets[i].charset);
	    printf ("\n");
	}

	/*
	 * Check for Han charsets to make fonts
	 * which advertise support for a single language
	 * not support other Han languages
	 */
	if (exclusiveCharset &&
	    FcLangIsExclusive (fcLangCharSets[i].lang)) {
	    if (fcLangCharSets[i].charset.num != exclusiveCharset->num)
		continue;

	    for (j = 0; j < fcLangCharSets[i].charset.num; j++)
		if (FcCharSetLeaf (&fcLangCharSets[i].charset, j) !=
		    FcCharSetLeaf (exclusiveCharset, j))
		    continue;
	}
	missing = FcCharSetSubtractCount (&fcLangCharSets[i].charset, charset);
	if (FcDebug() & FC_DBG_SCANV) {
	    if (missing && missing < 10) {
		FcCharSet *missed = FcCharSetSubtract (&fcLangCharSets[i].charset,
		                                       charset);
		FcChar32   ucs4;
		FcChar32   map[FC_CHARSET_MAP_SIZE];
		FcChar32   next;

		printf ("\n%s(%u) ", fcLangCharSets[i].lang, missing);
		printf ("{");
		for (ucs4 = FcCharSetFirstPage (missed, map, &next);
		     ucs4 != FC_CHARSET_DONE;
		     ucs4 = FcCharSetNextPage (missed, map, &next)) {
		    int i, j;
		    for (i = 0; i < FC_CHARSET_MAP_SIZE; i++)
			if (map[i]) {
			    for (j = 0; j < 32; j++)
				if (map[i] & (1U << j))
				    printf (" %04x", ucs4 + i * 32 + j);
			}
		}
		printf (" }\n\t");
		FcCharSetDestroy (missed);
	    } else
		printf ("%s(%u) ", fcLangCharSets[i].lang, missing);
	}
	if (!missing)
	    FcLangSetBitSet (ls, i);
    }

    if (FcDebug() & FC_DBG_SCANV)
	printf ("\n");

    return ls;
}

FcChar8 *
FcLangNormalize (const FcChar8 *lang)
{
    FcChar8 *result = NULL, *s, *orig;
    char    *territory, *encoding, *modifier;
    size_t   llen, tlen = 0, mlen = 0;

    if (!lang || !*lang)
	return NULL;

    /* might be called without initialization */
    FcInitDebug();

    if (FcStrCmpIgnoreCase (lang, (const FcChar8 *)"C") == 0 ||
        FcStrCmpIgnoreCase (lang, (const FcChar8 *)"C.UTF-8") == 0 ||
        FcStrCmpIgnoreCase (lang, (const FcChar8 *)"C.utf8") == 0 ||
        FcStrCmpIgnoreCase (lang, (const FcChar8 *)"POSIX") == 0) {
	result = FcStrCopy ((const FcChar8 *)"en");
	goto bail;
    }

    s = FcStrCopy (lang);
    if (!s)
	goto bail;

    /* from the comments in glibc:
     *
     * LOCALE can consist of up to four recognized parts for the XPG syntax:
     *
     *            language[_territory[.codeset]][@modifier]
     *
     * Beside the first all of them are allowed to be missing.  If the
     * full specified locale is not found, the less specific one are
     * looked for.  The various part will be stripped off according to
     * the following order:
     *            (1) codeset
     *            (2) normalized codeset
     *            (3) territory
     *            (4) modifier
     *
     * So since we don't take care of the codeset part here, what patterns
     * we need to deal with is:
     *
     *   1. language_territory@modifier
     *   2. language@modifier
     *   3. language
     *
     * then. and maybe no need to try language_territory here.
     */
    modifier = strchr ((char *)s, '@');
    if (modifier) {
	*modifier = 0;
	modifier++;
	mlen = strlen (modifier);
    }
    encoding = strchr ((char *)s, '.');
    if (encoding) {
	*encoding = 0;
	encoding++;
	if (modifier) {
	    memmove (encoding, modifier, mlen + 1);
	    modifier = encoding;
	}
    }
    territory = strchr ((char *)s, '_');
    if (!territory)
	territory = strchr ((char *)s, '-');
    if (territory) {
	*territory = 0;
	territory++;
	tlen = strlen (territory);
    }
    llen = strlen ((const char *)s);
    if (llen < 2 || llen > 3) {
	fprintf (stderr, "Fontconfig warning: ignoring %s: not a valid language tag\n",
	         lang);
	goto bail0;
    }
    if (territory && (tlen < 2 || tlen > 3) &&
        !(territory[0] == 'z' && tlen < 5)) {
	fprintf (stderr, "Fontconfig warning: ignoring %s: not a valid region tag\n",
	         lang);
	goto bail0;
    }
    if (territory)
	territory[-1] = '-';
    if (modifier)
	modifier[-1] = '@';
    orig = FcStrDowncase (s);
    if (!orig)
	goto bail0;
    if (territory) {
	if (FcDebug() & FC_DBG_LANGSET)
	    printf ("Checking the existence of %s.orth\n", s);
	if (FcLangSetIndex (s) < 0) {
	    memmove (territory - 1, territory + tlen, (mlen > 0 ? mlen + 1 : 0) + 1);
	    if (modifier)
		modifier = territory;
	} else {
	    result = s;
	    /* we'll miss the opportunity to reduce the correct size
	     * of the allocated memory for the string after that.
	     */
	    s = NULL;
	    goto bail1;
	}
    }
    if (modifier) {
	if (FcDebug() & FC_DBG_LANGSET)
	    printf ("Checking the existence of %s.orth\n", s);
	if (FcLangSetIndex (s) < 0)
	    modifier[-1] = 0;
	else {
	    result = s;
	    /* we'll miss the opportunity to reduce the correct size
	     * of the allocated memory for the string after that.
	     */
	    s = NULL;
	    goto bail1;
	}
    }
    if (FcDebug() & FC_DBG_LANGSET)
	printf ("Checking the existence of %s.orth\n", s);
    if (FcLangSetIndex (s) < 0) {
	/* there seems no languages matched in orth.
	 * add the language as is for fallback.
	 */
	result = orig;
	orig = NULL;
    } else {
	result = s;
	/* we'll miss the opportunity to reduce the correct size
	 * of the allocated memory for the string after that.
	 */
	s = NULL;
    }
bail1:
    if (orig)
	FcStrFree (orig);
bail0:
    if (s)
	free (s);
bail:
    if (FcDebug() & FC_DBG_LANGSET) {
	if (result)
	    printf ("normalized: %s -> %s\n", lang, result);
	else
	    printf ("Unable to normalize %s\n", lang);
    }

    return result;
}

#define FcLangEnd(c) ((c) == '-' || (c) == '\0')

FcLangResult
FcLangCompare (const FcChar8 *s1, const FcChar8 *s2)
{
    FcChar8        c1, c2;
    FcLangResult   result = FcLangDifferentLang;
    const FcChar8 *s1_orig = s1;
    FcBool         is_und;

    is_und = FcToLower (s1[0]) == 'u' &&
             FcToLower (s1[1]) == 'n' &&
             FcToLower (s1[2]) == 'd' &&
             FcLangEnd (s1[3]);

    for (;;) {
	c1 = *s1++;
	c2 = *s2++;

	c1 = FcToLower (c1);
	c2 = FcToLower (c2);
	if (c1 != c2) {
	    if (!is_und && FcLangEnd (c1) && FcLangEnd (c2))
		result = FcLangDifferentTerritory;
	    return result;
	} else if (!c1) {
	    return is_und ? result : FcLangEqual;
	} else if (c1 == '-') {
	    if (!is_und)
		result = FcLangDifferentTerritory;
	}

	/* If we parsed past "und-", then do not consider it undefined anymore,
	 * as there's *something* specified. */
	if (is_und && s1 - s1_orig == 4)
	    is_und = FcFalse;
    }
}

/*
 * Return FcTrue when super contains sub.
 *
 * super contains sub if super and sub have the same
 * language and either the same country or one
 * is missing the country
 */

static FcBool
FcLangContains (const FcChar8 *super, const FcChar8 *sub)
{
    FcChar8 c1, c2;

    for (;;) {
	c1 = *super++;
	c2 = *sub++;

	c1 = FcToLower (c1);
	c2 = FcToLower (c2);
	if (c1 != c2) {
	    /* see if super has a country while sub is missing one */
	    if (c1 == '-' && c2 == '\0')
		return FcTrue;
	    /* see if sub has a country while super is missing one */
	    if (c1 == '\0' && c2 == '-')
		return FcTrue;
	    return FcFalse;
	} else if (!c1)
	    return FcTrue;
    }
}

const FcCharSet *
FcLangGetCharSet (const FcChar8 *lang)
{
    int i;
    int country = -1;

    for (i = 0; i < NUM_LANG_CHAR_SET; i++) {
	switch (FcLangCompare (lang, fcLangCharSets[i].lang)) {
	case FcLangEqual:
	    return &fcLangCharSets[i].charset;
	case FcLangDifferentTerritory:
	    if (country == -1)
		country = i;
	case FcLangDifferentLang:
	default:
	    break;
	}
    }
    if (country == -1)
	return 0;
    return &fcLangCharSets[country].charset;
}

FcStrSet *
FcGetLangs (void)
{
    FcStrSet *langs;
    int       i;

    langs = FcStrSetCreate();
    if (!langs)
	return 0;

    for (i = 0; i < NUM_LANG_CHAR_SET; i++)
	FcStrSetAdd (langs, fcLangCharSets[i].lang);

    return langs;
}

FcLangSet *
FcLangSetCreate (void)
{
    FcLangSet *ls;

    ls = malloc (sizeof (FcLangSet));
    if (!ls)
	return 0;
    memset (ls->map, '\0', sizeof (ls->map));
    ls->map_size = NUM_LANG_SET_MAP;
    ls->extra = 0;
    return ls;
}

void
FcLangSetDestroy (FcLangSet *ls)
{
    if (!ls)
	return;

    if (ls->extra)
	FcStrSetDestroy (ls->extra);
    free (ls);
}

FcLangSet *
FcLangSetCopy (const FcLangSet *ls)
{
    FcLangSet *newp;

    if (!ls)
	return NULL;

    newp = FcLangSetCreate();
    if (!newp)
	goto bail0;
    memset (newp->map, '\0', sizeof (newp->map));
    memcpy (newp->map, ls->map, FC_MIN (sizeof (newp->map), ls->map_size * sizeof (ls->map[0])));
    if (ls->extra) {
	FcStrList *list;
	FcChar8   *extra;

	newp->extra = FcStrSetCreate();
	if (!newp->extra)
	    goto bail1;

	list = FcStrListCreate (ls->extra);
	if (!list)
	    goto bail1;

	while ((extra = FcStrListNext (list)))
	    if (!FcStrSetAdd (newp->extra, extra)) {
		FcStrListDone (list);
		goto bail1;
	    }
	FcStrListDone (list);
    }
    return newp;
bail1:
    FcLangSetDestroy (newp);
bail0:
    return 0;
}

/* When the language isn't found, the return value r is such that:
 *  1) r < 0
 *  2) -r -1 is the index of the first language in fcLangCharSets that comes
 *     after the 'lang' argument in lexicographic order.
 *
 *  The -1 is necessary to avoid problems with language id 0 (otherwise, we
 *  wouldn't be able to distinguish between “language found, id is 0” and
 *  “language not found, sorts right before the language with id 0”).
 */
static int
FcLangSetIndex (const FcChar8 *lang)
{
    int     low, high, mid = 0;
    int     cmp = 0;
    FcChar8 firstChar = FcToLower (lang[0]);
    FcChar8 secondChar = firstChar ? FcToLower (lang[1]) : '\0';

    if (firstChar < 'a') {
	low = 0;
	high = fcLangCharSetRanges[0].begin;
    } else if (firstChar > 'z') {
	low = fcLangCharSetRanges[25].begin;
	high = NUM_LANG_CHAR_SET - 1;
    } else {
	low = fcLangCharSetRanges[firstChar - 'a'].begin;
	high = fcLangCharSetRanges[firstChar - 'a'].end;
	/* no matches */
	if (low > high)
	    return -(low + 1); /* one past next entry after where it would be */
    }

    while (low <= high) {
	mid = (high + low) >> 1;
	if (fcLangCharSets[mid].lang[0] != firstChar)
	    cmp = FcStrCmpIgnoreCase (fcLangCharSets[mid].lang, lang);
	else { /* fast path for resolving 2-letter languages (by far the most common) after
	        * finding the first char (probably already true because of the hash table) */
	    cmp = fcLangCharSets[mid].lang[1] - secondChar;
	    if (cmp == 0 &&
	        (fcLangCharSets[mid].lang[2] != '\0' ||
	         lang[2] != '\0')) {
		cmp = FcStrCmpIgnoreCase (fcLangCharSets[mid].lang + 2,
		                          lang + 2);
	    }
	}
	if (cmp == 0)
	    return mid;
	if (cmp < 0)
	    low = mid + 1;
	else
	    high = mid - 1;
    }
    if (cmp < 0)
	mid++;
    return -(mid + 1);
}

FcBool
FcLangSetAdd (FcLangSet *ls, const FcChar8 *lang)
{
    int id;

    id = FcLangSetIndex (lang);
    if (id >= 0) {
	FcLangSetBitSet (ls, id);
	return FcTrue;
    }
    if (!ls->extra) {
	ls->extra = FcStrSetCreate();
	if (!ls->extra)
	    return FcFalse;
    }
    return FcStrSetAdd (ls->extra, lang);
}

FcBool
FcLangSetDel (FcLangSet *ls, const FcChar8 *lang)
{
    int id;

    id = FcLangSetIndex (lang);
    if (id >= 0) {
	FcLangSetBitReset (ls, id);
    } else if (ls->extra) {
	FcStrSetDel (ls->extra, lang);
    }
    return FcTrue;
}

FcLangResult
FcLangSetHasLang (const FcLangSet *ls, const FcChar8 *lang)
{
    int          id;
    FcLangResult best, r;
    int          i;

    id = FcLangSetIndex (lang);
    if (id < 0)
	id = -id - 1;
    else if (FcLangSetBitGet (ls, id))
	return FcLangEqual;
    best = FcLangDifferentLang;
    for (i = id - 1; i >= 0; i--) {
	r = FcLangCompare (lang, fcLangCharSets[i].lang);
	if (r == FcLangDifferentLang)
	    break;
	if (FcLangSetBitGet (ls, i) && r < best)
	    best = r;
    }
    for (i = id; i < NUM_LANG_CHAR_SET; i++) {
	r = FcLangCompare (lang, fcLangCharSets[i].lang);
	if (r == FcLangDifferentLang)
	    break;
	if (FcLangSetBitGet (ls, i) && r < best)
	    best = r;
    }
    if (ls->extra) {
	FcStrList *list = FcStrListCreate (ls->extra);
	FcChar8   *extra;

	if (list) {
	    while (best > FcLangEqual && (extra = FcStrListNext (list))) {
		r = FcLangCompare (lang, extra);
		if (r < best)
		    best = r;
	    }
	    FcStrListDone (list);
	}
    }
    return best;
}

static FcLangResult
FcLangSetCompareStrSet (const FcLangSet *ls, FcStrSet *set)
{
    FcStrList   *list = FcStrListCreate (set);
    FcLangResult r, best = FcLangDifferentLang;
    FcChar8     *extra;

    if (list) {
	while (best > FcLangEqual && (extra = FcStrListNext (list))) {
	    r = FcLangSetHasLang (ls, extra);
	    if (r < best)
		best = r;
	}
	FcStrListDone (list);
    }
    return best;
}

FcLangResult
FcLangSetCompare (const FcLangSet *lsa, const FcLangSet *lsb)
{
    int          i, j, count;
    FcLangResult best, r;
    FcChar32     aInCountrySet, bInCountrySet;

    count = FC_MIN (lsa->map_size, lsb->map_size);
    count = FC_MIN (NUM_LANG_SET_MAP, count);
    for (i = 0; i < count; i++)
	if (lsa->map[i] & lsb->map[i])
	    return FcLangEqual;
    best = FcLangDifferentLang;
    for (j = 0; j < NUM_COUNTRY_SET; j++) {
	aInCountrySet = 0;
	bInCountrySet = 0;

	for (i = 0; i < count; i++) {
	    aInCountrySet |= lsa->map[i] & fcLangCountrySets[j][i];
	    bInCountrySet |= lsb->map[i] & fcLangCountrySets[j][i];

	    if (aInCountrySet && bInCountrySet) {
		best = FcLangDifferentTerritory;
		break;
	    }
	}
    }
    if (lsa->extra) {
	r = FcLangSetCompareStrSet (lsb, lsa->extra);
	if (r < best)
	    best = r;
    }
    if (best > FcLangEqual && lsb->extra) {
	r = FcLangSetCompareStrSet (lsa, lsb->extra);
	if (r < best)
	    best = r;
    }
    return best;
}

/*
 * Used in computing values -- mustn't allocate any storage
 */
FcLangSet *
FcLangSetPromote (const FcChar8 *lang, FcValuePromotionBuffer *vbuf)
{
    int id;
    typedef struct {
	FcLangSet ls;
	FcStrSet  strs;
	FcChar8  *str;
    } FcLangSetPromotionBuffer;
    FcLangSetPromotionBuffer *buf = (FcLangSetPromotionBuffer *)vbuf;

    FC_ASSERT_STATIC (sizeof (FcLangSetPromotionBuffer) <= sizeof (FcValuePromotionBuffer));

    memset (buf->ls.map, '\0', sizeof (buf->ls.map));
    buf->ls.map_size = NUM_LANG_SET_MAP;
    buf->ls.extra = 0;
    if (lang) {
	id = FcLangSetIndex (lang);
	if (id >= 0) {
	    FcLangSetBitSet (&buf->ls, id);
	} else {
	    buf->ls.extra = &buf->strs;
	    buf->strs.num = 1;
	    buf->strs.size = 1;
	    buf->strs.strs = &buf->str;
	    FcRefInit (&buf->strs.ref, 1);
	    buf->str = (FcChar8 *)lang;
	}
    }
    return &buf->ls;
}

FcChar32
FcLangSetHash (const FcLangSet *ls)
{
    FcChar32 h = 0;
    int      i, count;

    count = FC_MIN (ls->map_size, NUM_LANG_SET_MAP);
    for (i = 0; i < count; i++)
	h ^= ls->map[i];
    if (ls->extra)
	h ^= ls->extra->num;
    return h;
}

FcLangSet *
FcNameParseLangSet (const FcChar8 *string)
{
    FcChar8    lang[32], c = 0;
    int        i;
    FcLangSet *ls;

    ls = FcLangSetCreate();
    if (!ls)
	goto bail0;

    for (;;) {
	for (i = 0; i < 31; i++) {
	    c = *string++;
	    if (c == '\0' || c == '|')
		break; /* end of this code */
	    lang[i] = c;
	}
	lang[i] = '\0';
	if (!FcLangSetAdd (ls, lang))
	    goto bail1;
	if (c == '\0')
	    break;
    }
    return ls;
bail1:
    FcLangSetDestroy (ls);
bail0:
    return 0;
}

FcBool
FcNameUnparseLangSet (FcStrBuf *buf, const FcLangSet *ls)
{
    int      i, bit, count;
    FcChar32 bits;
    FcBool   first = FcTrue;

    count = FC_MIN (ls->map_size, NUM_LANG_SET_MAP);
    for (i = 0; i < count; i++) {
	if ((bits = ls->map[i])) {
	    for (bit = 0; bit <= 31; bit++)
		if (bits & (1U << bit)) {
		    int id = (i << 5) | bit;
		    if (!first)
			if (!FcStrBufChar (buf, '|'))
			    return FcFalse;
		    if (!FcStrBufString (buf, fcLangCharSets[fcLangCharSetIndicesInv[id]].lang))
			return FcFalse;
		    first = FcFalse;
		}
	}
    }
    if (ls->extra) {
	FcStrList *list = FcStrListCreate (ls->extra);
	FcChar8   *extra;

	if (!list)
	    return FcFalse;
	while ((extra = FcStrListNext (list))) {
	    if (!first)
		if (!FcStrBufChar (buf, '|')) {
		    FcStrListDone (list);
		    return FcFalse;
		}
	    if (!FcStrBufString (buf, extra)) {
		FcStrListDone (list);
		return FcFalse;
	    }
	    first = FcFalse;
	}
	FcStrListDone (list);
    }
    return FcTrue;
}

FcBool
FcLangSetEqual (const FcLangSet *lsa, const FcLangSet *lsb)
{
    int i, count;

    count = FC_MIN (lsa->map_size, lsb->map_size);
    count = FC_MIN (NUM_LANG_SET_MAP, count);
    for (i = 0; i < count; i++) {
	if (lsa->map[i] != lsb->map[i])
	    return FcFalse;
    }
    if (!lsa->extra && !lsb->extra)
	return FcTrue;
    if (lsa->extra && lsb->extra)
	return FcStrSetEqual (lsa->extra, lsb->extra);
    return FcFalse;
}

static FcBool
FcLangSetContainsLang (const FcLangSet *ls, const FcChar8 *lang)
{
    int id;
    int i;

    id = FcLangSetIndex (lang);
    if (id < 0)
	id = -id - 1;
    else if (FcLangSetBitGet (ls, id))
	return FcTrue;
    /*
     * search up and down among equal languages for a match
     */
    for (i = id - 1; i >= 0; i--) {
	if (FcLangCompare (fcLangCharSets[i].lang, lang) == FcLangDifferentLang)
	    break;
	if (FcLangSetBitGet (ls, i) &&
	    FcLangContains (fcLangCharSets[i].lang, lang))
	    return FcTrue;
    }
    for (i = id; i < NUM_LANG_CHAR_SET; i++) {
	if (FcLangCompare (fcLangCharSets[i].lang, lang) == FcLangDifferentLang)
	    break;
	if (FcLangSetBitGet (ls, i) &&
	    FcLangContains (fcLangCharSets[i].lang, lang))
	    return FcTrue;
    }
    if (ls->extra) {
	FcStrList *list = FcStrListCreate (ls->extra);
	FcChar8   *extra;

	if (list) {
	    while ((extra = FcStrListNext (list))) {
		if (FcLangContains (extra, lang))
		    break;
	    }
	    FcStrListDone (list);
	    if (extra)
		return FcTrue;
	}
    }
    return FcFalse;
}

/*
 * return FcTrue if lsa contains every language in lsb
 */
FcBool
FcLangSetContains (const FcLangSet *lsa, const FcLangSet *lsb)
{
    int      i, j, count;
    FcChar32 missing;

    if (FcDebug() & FC_DBG_MATCHV) {
	printf ("FcLangSet ");
	FcLangSetPrint (lsa);
	printf (" contains ");
	FcLangSetPrint (lsb);
	printf ("\n");
    }
    /*
     * check bitmaps for missing language support
     */
    count = FC_MIN (lsa->map_size, lsb->map_size);
    count = FC_MIN (NUM_LANG_SET_MAP, count);
    for (i = 0; i < count; i++) {
	missing = lsb->map[i] & ~lsa->map[i];
	if (missing) {
	    for (j = 0; j < 32; j++)
		if (missing & (1U << j)) {
		    if (!FcLangSetContainsLang (lsa,
		                                fcLangCharSets[fcLangCharSetIndicesInv[i * 32 + j]].lang)) {
			if (FcDebug() & FC_DBG_MATCHV)
			    printf ("\tMissing bitmap %s\n", fcLangCharSets[fcLangCharSetIndicesInv[i * 32 + j]].lang);
			return FcFalse;
		    }
		}
	}
    }
    if (lsb->extra) {
	FcStrList *list = FcStrListCreate (lsb->extra);
	FcChar8   *extra;

	if (list) {
	    while ((extra = FcStrListNext (list))) {
		if (!FcLangSetContainsLang (lsa, extra)) {
		    if (FcDebug() & FC_DBG_MATCHV)
			printf ("\tMissing string %s\n", extra);
		    break;
		}
	    }
	    FcStrListDone (list);
	    if (extra)
		return FcFalse;
	}
    }
    return FcTrue;
}

FcBool
FcLangSetSerializeAlloc (FcSerialize *serialize, const FcLangSet *l)
{
    if (!FcSerializeAlloc (serialize, l, sizeof (FcLangSet)))
	return FcFalse;
    return FcTrue;
}

FcLangSet *
FcLangSetSerialize (FcSerialize *serialize, const FcLangSet *l)
{
    FcLangSet *l_serialize = FcSerializePtr (serialize, l);

    if (!l_serialize)
	return NULL;
    memset (l_serialize->map, '\0', sizeof (l_serialize->map));
    memcpy (l_serialize->map, l->map, FC_MIN (sizeof (l_serialize->map), l->map_size * sizeof (l->map[0])));
    l_serialize->map_size = NUM_LANG_SET_MAP;
    l_serialize->extra = NULL; /* We don't serialize ls->extra */
    return l_serialize;
}

FcStrSet *
FcLangSetGetLangs (const FcLangSet *ls)
{
    FcStrSet *langs;
    int       i;

    langs = FcStrSetCreate();
    if (!langs)
	return 0;

    for (i = 0; i < NUM_LANG_CHAR_SET; i++)
	if (FcLangSetBitGet (ls, i))
	    FcStrSetAdd (langs, fcLangCharSets[i].lang);

    if (ls->extra) {
	FcStrList *list = FcStrListCreate (ls->extra);
	FcChar8   *extra;

	if (list) {
	    while ((extra = FcStrListNext (list)))
		FcStrSetAdd (langs, extra);

	    FcStrListDone (list);
	}
    }

    return langs;
}

static FcLangSet *
FcLangSetOperate (const FcLangSet *a,
                  const FcLangSet *b,
                  FcBool (*func) (FcLangSet     *ls,
                                  const FcChar8 *s))
{
    FcLangSet *langset = FcLangSetCopy (a);
    FcStrSet  *set = FcLangSetGetLangs (b);
    FcStrList *sl = FcStrListCreate (set);
    FcChar8   *str;

    FcStrSetDestroy (set);
    while ((str = FcStrListNext (sl))) {
	func (langset, str);
    }
    FcStrListDone (sl);

    return langset;
}

FcLangSet *
FcLangSetUnion (const FcLangSet *a, const FcLangSet *b)
{
    return FcLangSetOperate (a, b, FcLangSetAdd);
}

FcLangSet *
FcLangSetSubtract (const FcLangSet *a, const FcLangSet *b)
{
    return FcLangSetOperate (a, b, FcLangSetDel);
}

FcBool
FcLangIsExclusive (const FcChar8 *lang)
{
    int i;

    for (i = 0; i < NUM_CODE_PAGE_RANGE; i++) {
	if (FcLangCompare (lang, FcCodePageRange[i].lang) == FcLangEqual)
	    return FcTrue;
    }
    return FcFalse;
}

const FcChar8 *
FcLangIsExclusiveFromOs2 (unsigned long os2ulUnicodeRange1, unsigned long os2ulUnicodeRange2)
{
    unsigned int   i;
    const FcChar8 *exclusiveLang = 0;

    for (i = 0; i < NUM_CODE_PAGE_RANGE; i++) {
	unsigned long bits;
	int           bit;
	if (FcCodePageRange[i].bit < 32) {
	    bits = os2ulUnicodeRange1;
	    bit = FcCodePageRange[i].bit;
	} else {
	    bits = os2ulUnicodeRange2;
	    bit = FcCodePageRange[i].bit - 32;
	}
	if (bits & (1U << bit)) {
	    /*
	     * If the font advertises support for multiple
	     * "exclusive" languages, then include support
	     * for any language found to have coverage
	     */
	    if (exclusiveLang) {
		exclusiveLang = 0;
		break;
	    }
	    exclusiveLang = FcCodePageRange[i].lang;
	}
    }
    return exclusiveLang;
}

#define __fclang__
#include "fcaliastail.h"
#if ENABLE_FREETYPE
#include "fcftaliastail.h"
#endif
#undef __fclang__
