/* ex: set ro ft=c: -*- buffer-read-only: t -*-
 *
 * !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!!
 * This file is auto-generated by Unicode::Towctrans 0.09
 * gen_wctrans
 * for Unicode 17.0.0
 * Any changes here will be lost!
 */
/*
Copyright (c) 2017,2027 Reini Urban
--------------------------------------------------------------
This code is licensed under the following standard MIT license
--------------------------------------------------------------

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------------------------------
*/

#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <wctype.h>
/* for Unicode 17.0.0 */
#define TOWFC_UNICODE_VERSION 17

#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if defined(__GNUC__) || __has_builtin(__builtin_expect)
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif

/* wchar_t is 2 bytes on Windows, 4 bytes everywhere else */
#ifndef SIZEOF_WCHAR_T
#if defined(_WIN32) || defined(__CYGWIN__)
#define SIZEOF_WCHAR_T 2
#elif defined(__SIZEOF_WCHAR_T__)
#define SIZEOF_WCHAR_T __SIZEOF_WCHAR_T__
#else
#define SIZEOF_WCHAR_T 4
#endif
#endif

/* Unicode 17.0.0 has 212 code points for which the fold case mapping is not to
 * itself, or to tolower(), 108 of it return length 1, the rest 104 map to 2 or
 * 3 and are handled below.
 */

static const struct {
    unsigned short upper;
    unsigned short lower1;
    unsigned short lower2;
} tbl2[] = {
    // clang-format off
    {0x00DF, 0x0073, 0x0073}, /* LATIN SMALL LETTER SHARP S */
    {0x0130, 0x0069, 0x0307}, /* LATIN CAPITAL LETTER I WITH DOT ABOVE */
    {0x0149, 0x02BC, 0x006E}, /* LATIN SMALL LETTER N PRECEDED BY APOSTROPHE */
    {0x01F0, 0x006A, 0x030C}, /* LATIN SMALL LETTER J WITH CARON */
    {0x0587, 0x0565, 0x0582}, /* ARMENIAN SMALL LIGATURE ECH YIWN */
    {0x1E96, 0x0068, 0x0331}, /* LATIN SMALL LETTER H WITH LINE BELOW */
    {0x1E97, 0x0074, 0x0308}, /* LATIN SMALL LETTER T WITH DIAERESIS */
    {0x1E98, 0x0077, 0x030A}, /* LATIN SMALL LETTER W WITH RING ABOVE */
    {0x1E99, 0x0079, 0x030A}, /* LATIN SMALL LETTER Y WITH RING ABOVE */
    {0x1E9A, 0x0061, 0x02BE}, /* LATIN SMALL LETTER A WITH RIGHT HALF RING */
    {0x1E9E, 0x0073, 0x0073}, /* LATIN CAPITAL LETTER SHARP S */
    {0x1F50, 0x03C5, 0x0313}, /* GREEK SMALL LETTER UPSILON WITH PSILI */
    {0x1F80, 0x1F00, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI */
    {0x1F81, 0x1F01, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI */
    {0x1F82, 0x1F02, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI */
    {0x1F83, 0x1F03, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI */
    {0x1F84, 0x1F04, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI */
    {0x1F85, 0x1F05, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI */
    {0x1F86, 0x1F06, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI */
    {0x1F87, 0x1F07, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI */
    {0x1F88, 0x1F00, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI */
    {0x1F89, 0x1F01, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI */
    {0x1F8A, 0x1F02, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
    {0x1F8B, 0x1F03, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
    {0x1F8C, 0x1F04, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
    {0x1F8D, 0x1F05, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
    {0x1F8E, 0x1F06, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
    {0x1F8F, 0x1F07, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
    {0x1F90, 0x1F20, 0x03B9}, /* GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI */
    {0x1F91, 0x1F21, 0x03B9}, /* GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI */
    {0x1F92, 0x1F22, 0x03B9}, /* GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI */
    {0x1F93, 0x1F23, 0x03B9}, /* GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI */
    {0x1F94, 0x1F24, 0x03B9}, /* GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI */
    {0x1F95, 0x1F25, 0x03B9}, /* GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI */
    {0x1F96, 0x1F26, 0x03B9}, /* GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI */
    {0x1F97, 0x1F27, 0x03B9}, /* GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI */
    {0x1F98, 0x1F20, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI */
    {0x1F99, 0x1F21, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI */
    {0x1F9A, 0x1F22, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
    {0x1F9B, 0x1F23, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
    {0x1F9C, 0x1F24, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
    {0x1F9D, 0x1F25, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
    {0x1F9E, 0x1F26, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
    {0x1F9F, 0x1F27, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
    {0x1FA0, 0x1F60, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI */
    {0x1FA1, 0x1F61, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI */
    {0x1FA2, 0x1F62, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI */
    {0x1FA3, 0x1F63, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI */
    {0x1FA4, 0x1F64, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI */
    {0x1FA5, 0x1F65, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI */
    {0x1FA6, 0x1F66, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI */
    {0x1FA7, 0x1F67, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI */
    {0x1FA8, 0x1F60, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI */
    {0x1FA9, 0x1F61, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI */
    {0x1FAA, 0x1F62, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
    {0x1FAB, 0x1F63, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
    {0x1FAC, 0x1F64, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
    {0x1FAD, 0x1F65, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
    {0x1FAE, 0x1F66, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
    {0x1FAF, 0x1F67, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
    {0x1FB2, 0x1F70, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI */
    {0x1FB3, 0x03B1, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI */
    {0x1FB4, 0x03AC, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI */
    {0x1FB6, 0x03B1, 0x0342}, /* GREEK SMALL LETTER ALPHA WITH PERISPOMENI */
    {0x1FBC, 0x03B1, 0x03B9}, /* GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI */
    {0x1FC2, 0x1F74, 0x03B9}, /* GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI */
    {0x1FC3, 0x03B7, 0x03B9}, /* GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI */
    {0x1FC4, 0x03AE, 0x03B9}, /* GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI */
    {0x1FC6, 0x03B7, 0x0342}, /* GREEK SMALL LETTER ETA WITH PERISPOMENI */
    {0x1FCC, 0x03B7, 0x03B9}, /* GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI */
    {0x1FD6, 0x03B9, 0x0342}, /* GREEK SMALL LETTER IOTA WITH PERISPOMENI */
    {0x1FE4, 0x03C1, 0x0313}, /* GREEK SMALL LETTER RHO WITH PSILI */
    {0x1FE6, 0x03C5, 0x0342}, /* GREEK SMALL LETTER UPSILON WITH PERISPOMENI */
    {0x1FF2, 0x1F7C, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI */
    {0x1FF3, 0x03C9, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI */
    {0x1FF4, 0x03CE, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI */
    {0x1FF6, 0x03C9, 0x0342}, /* GREEK SMALL LETTER OMEGA WITH PERISPOMENI */
    {0x1FFC, 0x03C9, 0x03B9}, /* GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI */
    {0xFB00, 0x0066, 0x0066}, /* LATIN SMALL LIGATURE FF */
    {0xFB01, 0x0066, 0x0069}, /* LATIN SMALL LIGATURE FI */
    {0xFB02, 0x0066, 0x006C}, /* LATIN SMALL LIGATURE FL */
    {0xFB05, 0x0073, 0x0074}, /* LATIN SMALL LIGATURE LONG S T */
    {0xFB06, 0x0073, 0x0074}, /* LATIN SMALL LIGATURE ST */
    {0xFB13, 0x0574, 0x0576}, /* ARMENIAN SMALL LIGATURE MEN NOW */
    {0xFB14, 0x0574, 0x0565}, /* ARMENIAN SMALL LIGATURE MEN ECH */
    {0xFB15, 0x0574, 0x056B}, /* ARMENIAN SMALL LIGATURE MEN INI */
    {0xFB16, 0x057E, 0x0576}, /* ARMENIAN SMALL LIGATURE VEW NOW */
    {0xFB17, 0x0574, 0x056D}, /* ARMENIAN SMALL LIGATURE MEN XEH */
    {0, 0, 0}
    // clang-format on
};

static const struct {
    unsigned short upper;
    unsigned short lower1;
    unsigned short lower2;
    unsigned short lower3;
} tbl3[] = {
    // clang-format off
    {0x0390, 0x03B9, 0x0308, 0x0301}, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
    {0x03B0, 0x03C5, 0x0308, 0x0301}, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
    {0x1F52, 0x03C5, 0x0313, 0x0300}, /* GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA */
    {0x1F54, 0x03C5, 0x0313, 0x0301}, /* GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA */
    {0x1F56, 0x03C5, 0x0313, 0x0342}, /* GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI */
    {0x1FB7, 0x03B1, 0x0342, 0x03B9}, /* GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI */
    {0x1FC7, 0x03B7, 0x0342, 0x03B9}, /* GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI */
    {0x1FD2, 0x03B9, 0x0308, 0x0300}, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA */
    {0x1FD3, 0x03B9, 0x0308, 0x0301}, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA */
    {0x1FD7, 0x03B9, 0x0308, 0x0342}, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI */
    {0x1FE2, 0x03C5, 0x0308, 0x0300}, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA */
    {0x1FE3, 0x03C5, 0x0308, 0x0301}, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA */
    {0x1FE7, 0x03C5, 0x0308, 0x0342}, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI */
    {0x1FF7, 0x03C9, 0x0342, 0x03B9}, /* GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI */
    {0xFB03, 0x0066, 0x0066, 0x0069}, /* LATIN SMALL LIGATURE FFI */
    {0xFB04, 0x0066, 0x0066, 0x006C}, /* LATIN SMALL LIGATURE FFL */
    {0, 0, 0, 0}
    // clang-format on
};

/* Return the number of wide lower-case characters needed to full fold-case
   the given uppercase character. Returns 0, 1, 2 or 3.
   0 if the character stays the same, 1 if one character changes,
   2 or 3 if the character will be replaced with 2 or 3.

   Note that accents expand to more characters than 1 via NFD decomposition.
   iswfc only matches towfc_s, but not wcsfc_s which does NFD decomposition
   also.
*/
int iswfc(const uint32_t wc) {
    int i;
    for (i = 0; tbl2[i].upper; i++) {
        if (tbl2[i].upper == wc)
            return 2;
        if (tbl2[i].upper > wc)
            break;
    }
    for (i = 0; tbl3[i].upper; i++) {
        if (tbl3[i].upper == wc)
            return 3;
        if (tbl3[i].upper > wc)
            break;
    }
#if SIZEOF_WCHAR_T > 2
    return iswupper(wc) ? 1 : 0;
#else
    if (wc > 0xffff) {
        uint32_t lc = _towcase(wc, 1);
        return (lc != wc) ? 1 : 0;
    }
    return iswupper((wint_t)wc) ? 1 : 0;
#endif
}

/* The 108 single fc chars where fc is different to lc.
   Must only be called when we know for sure that the length is 1!
   Returns 1 if fc is different to lc, else -1.
   May return 2 on sizeof(wchar_t)==2 if >0xffff, i.e. converted to surrogate
   pair
 */
int _towfc_single(wchar_t *restrict dest, const uint32_t src) {
    /* fc exceptions: not towlower */
    dest[1] = L'\0';
    if (src < 0xb5)
        goto single;
    if (src >= 0xb5 && src <= 0x3f5) {
        switch (src) {
        case 0xb5:
            dest[0] = 0x3bc;
            return 1; /* lc=b5 */
        case 0x17f:
            dest[0] = 0x73;
            return 1; /* lc=17f */
        case 0x345:
            dest[0] = 0x3b9;
            return 1; /* lc=345 */
        case 0x3c2:
            dest[0] = 0x3c3;
            return 1; /* lc=3c2 */
        case 0x3d0:
            dest[0] = 0x3b2;
            return 1; /* lc=3d0 */
        case 0x3d1:
            dest[0] = 0x3b8;
            return 1; /* lc=3d1 */
        case 0x3d5:
            dest[0] = 0x3c6;
            return 1; /* lc=3d5 */
        case 0x3d6:
            dest[0] = 0x3c0;
            return 1; /* lc=3d6 */
        case 0x3f0:
            dest[0] = 0x3ba;
            return 1; /* lc=3f0 */
        case 0x3f1:
            dest[0] = 0x3c1;
            return 1; /* lc=3f1 */
        case 0x3f5:
            dest[0] = 0x3b5;
            return 1; /* lc=3f5 */
        default:
            break;
        }
        goto single;
    } else if (unlikely(src >= 0x13f8 && src <= 0x13fd)) {
        dest[0] = src - 8;
        return 1;
    } else if (unlikely(src >= 0x1c80 && src <= 0x1c88)) {
        switch (src) {
        case 0x1c80:
            dest[0] = 0x432;
            return 1; /* lc=1c80 */
        case 0x1c81:
            dest[0] = 0x434;
            return 1; /* lc=1c81 */
        case 0x1c82:
            dest[0] = 0x43e;
            return 1; /* lc=1c82 */
        case 0x1c83:
            dest[0] = 0x441;
            return 1; /* lc=1c83 */
        case 0x1c84:
            dest[0] = 0x442;
            return 1; /* lc=1c84 */
        case 0x1c85:
            dest[0] = 0x442;
            return 1; /* lc=1c85 */
        case 0x1c86:
            dest[0] = 0x44a;
            return 1; /* lc=1c86 */
        case 0x1c87:
            dest[0] = 0x463;
            return 1; /* lc=1c87 */
        case 0x1c88:
            dest[0] = 0xa64b;
            return 1; /* lc=1c88 */
        default:
            break;
        }
        goto single;
    } else if (unlikely(src >= 0x1e9b && src <= 0x1fbe)) {
        switch (src) {
        case 0x1e9b:
            dest[0] = 0x1e61;
            return 1; /* lc=1e9b */
        case 0x1fbe:
            dest[0] = 0x3b9;
            return 1; /* lc=1fbe */
        default:
            break;
        }
        goto single;
    } else if (unlikely(src >= 0xab70 && src <= 0xabbf)) {
        dest[0] = src - (0xab70 - 0x13a0);
        return 1;
    }

single:
#if SIZEOF_WCHAR_T > 2
    dest[0] = src < 128 ? (wchar_t)tolower(src) : (wchar_t)_towcase(src, 1);
    return (uint32_t)dest[0] == src ? -1 : 1;
#else
{
    uint32_t cp = src < 128 ? (uint32_t)tolower(src) : _towcase(src, 1);
    if (unlikely(cp > 0xffff)) {
        dest[0] = 0xd800 + (((cp - 0x10000) >> 10) & 0x3ff);
        dest[1] = 0xdc00 + (cp & 0x3ff);
        dest[2] = 0;
        return cp == src ? -1 : 2;
    } else {
        dest[0] = cp;
        return cp == src ? -1 : 1;
    }
}
#endif
}

/* Writes the fold-cased wide string to dest from the given uppercase wide
   character. dmax should be 4 (3 wchar's + \0).
   Returns the number of replaced wide characters, or -1 if not
   replaced.
*/
int towfc(wchar_t *restrict dest, size_t dmax, const uint32_t src) {
    int i;
    const size_t destsz = dmax * sizeof(wchar_t);

    if (unlikely(dest == NULL)) {
        return -2;
    }
    if (unlikely(dmax < 4)) {
        return -3;
    }
    dest[0] = L'\0';
    if (unlikely(dmax > 0x7fff)) {
        return -4;
    }

    if (src < 128) {
        dest[1] = L'\0';
        dest[0] = tolower(src);
        return (uint32_t)dest[0] == src ? -1 : 1;
    }

    for (i = 0; tbl2[i].upper; i++) {
        if (tbl2[i].upper == src) {
            dest[0] = tbl2[i].lower1;
            dest[1] = tbl2[i].lower2;
            dest[2] = L'\0';
            return 2;
        }
        if (tbl2[i].upper > src)
            break;
    }
    for (i = 0; tbl3[i].upper; i++) {
        if (tbl3[i].upper == src) {
            dest[0] = tbl3[i].lower1;
            dest[1] = tbl3[i].lower2;
            dest[2] = tbl3[i].lower3;
            dest[3] = L'\0';
            return 3;
        }
        if (tbl3[i].upper > src)
            break;
    }

    /* fc exceptions: not towlower, for c == 1 */
    return _towfc_single(dest, src);
}
