/*------------------------------------------------------------------
 * test_wcsnorm_s
 * File 'wcsnorm_s.c'
 * Lines executed:78.25% of 377
 *
 *------------------------------------------------------------------
 */

#include "test_private.h"
#include "safe_str_lib.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <locale.h>

#define LEN (128)

#if SIZEOF_WCHAR_T > 2
#define _dec_w16(src) *(src)
#else
EXTERN uint32_t _dec_w16(wchar_t *src);
#endif
int test_wcsnorm_s(void);

#define PERL_TEST
/* Must have the same Unicode version 10, since 5.27.3
   perl -MUnicode::UCD -e'print Unicode::UCD::UnicodeVersion()'
*/
#define TESTPL "test-norm.pl"

int test_wcsnorm_s(void) {
    errno_t rc;
    wchar_t str[LEN];
    wchar_t str1[LEN];
    rsize_t ind;
    size_t len;
    int errs = 0;
#ifdef PERL_TEST
    FILE *pl;
    struct stat st;

    pl = fopen(TESTPL, "w");
#endif

    /*--------------------------------------------------*/

#ifndef HAVE_CT_BOS_OVR
    EXPECT_BOS("empty dest")
    rc = wcsnorm_s(NULL, LEN, L"test", WCSNORM_NFD, NULL);
    ERR(ESNULLP);

    wcscpy(str, L"A"
                L"\x1fb3");
    EXPECT_BOS("empty src")
    rc = wcsnorm_s(str, LEN, NULL, WCSNORM_NFD, NULL);
    ERR(ESNULLP);
    WEXPSTR(str, L"\0");

    EXPECT_BOS("empty dest or dmax")
    rc = wcsnorm_s(str, 0, L"test", WCSNORM_NFD, &ind);
    ERR(ESZEROL)
    INDZERO();
    WEXPSTR(str, L"\0");

    ind = 4;
    EXPECT_BOS("dest overflow")
    rc = wcsnorm_s(str, RSIZE_MAX_WSTR + 1, L"test", WCSNORM_NFD, &ind);
    ERR(ESLEMAX);
    WEXPSTR(str, L"\0");
    INDCMP(!= 0)

    if (_BOS_KNOWN(str)) {
        ind = 4;
        EXPECT_BOS("dest overflow")
        rc = wcsnorm_s(str, LEN + 1, L"test", WCSNORM_NFD, &ind);
        ERR(EOVERFLOW);
        WEXPSTR(str, L"\0");
        INDCMP(!= 0)
    }

    if (_BOS_KNOWN(str)) {
        ind = 4;
        EXPECT_BOS("dest overflow")
        rc = wcsnorm_decompose_s(str, LEN + 1, L"test", &ind, false);
        ERR(EOVERFLOW);
        WEXPSTR(str, L"\0");
        INDCMP(!= 0)

        ind = 4;
        EXPECT_BOS("dest overflow")
        rc = wcsnorm_reorder_s(str, LEN + 1, L"test", ind);
        ERR(EOVERFLOW);
        WEXPSTR(str, L"\0");

        EXPECT_BOS("dest overflow")
        rc = wcsnorm_compose_s(str, LEN + 1, L"test", &ind, false);
        ERR(EOVERFLOW);
        WEXPSTR(str, L"\0");
        INDCMP(!= 0)
    }
#endif

#if SIZEOF_WCHAR_T >= 4
#define OVMAX_WC L"\x11ffff"
#define MAX_WC1 L"\x10fff0"
#else
    /* => capped at 0x10ffff */
#define OVMAX_WC                                                               \
    L"\xdbff"                                                                  \
    L"\xdfff"
#define MAX_WC1                                                                \
    L"\xdb3f"                                                                  \
    L"\xdff0"
#endif

    rc = wcsnorm_s(str, LEN, OVMAX_WC, WCSNORM_NFD, NULL);
#if SIZEOF_WCHAR_T >= 4
    ERR(ESLEMAX);
    WEXPSTR(str, L"\0");
#else
    ERR(EOK); /* cp collapses to 0x10ffff, decompose keeps it */
    wcscpy(str1, L"\xd876"
                 L"\xdfff");
    WEXPSTR(&str[1], &str1[1]);
    WCHECK_SLACK(&str[2], LEN - 2);
#endif

#if SIZEOF_WCHAR_T >= 4
    rc = wcsnorm_decompose_s(str, LEN, OVMAX_WC, NULL, false);
    ERR(ESLEMAX);
    WEXPSTR(str, L"\0");

    rc = wcsnorm_decompose_s(str, LEN, OVMAX_WC, NULL, true);
#ifdef HAVE_NORM_COMPAT
    ERR(ESLEMAX);
    WEXPSTR(str, L"\0");
#else
    if (rc == EOF) {
        ERR(EOF);
    } else {
        debug_printf("%u TODO !norm-compat rc=%d\n", __LINE__, rc);
    }
#endif
#endif

    rc = wcsnorm_s(str, 4, MAX_WC1, WCSNORM_NFD, NULL);
    ERR(ESLEMIN);
    WEXPSTR(str, L"\0");

    /*--------------------------------------------------*/

    rc = wcsnorm_s(str, LEN, L"Caf\xe9", WCSNORM_NFD, &ind);
    ERR(EOK);
    WEXPSTR(str, L"Cafe\x301");
    INDCMP(!= 5);
    WCHECK_SLACK(&str[5], LEN - 5);

    /*--------------------------------------------------*/

#ifndef __PGI
    rc = wcsnorm_s(str, LEN, L"Café", WCSNORM_NFD, &ind);
    ERR(EOK)
    WEXPSTR(str, L"Cafe\x301");
    INDCMP(!= 5);
    WCHECK_SLACK(&str[5], LEN - 5);
#endif

    rc = wcsnorm_s(str, LEN, L"Cafe\x301", WCSNORM_NFC, &ind);
    ERR(EOK)
    WEXPSTR(str, L"Caf\xe9");
    INDCMP(!= 4);
    WCHECK_SLACK(&str[4], LEN - 4);

    /*--------------------------------------------------*/

    wcscpy(str, L"A"
                L"\x1fb3");
    rc = wcsnorm_s(str1, LEN, str, WCSNORM_NFC, NULL);
    ERR(EOK);

    wcscpy(str, L"Abc"
                L"\x1fb7");
    rc = wcsnorm_s(str1, 6, str, WCSNORM_NFD, NULL);
    ERR(ESNOSPC);
    WEXPSTR(str1, L"\0");

#ifdef HAVE_NORM_COMPAT
    wcscpy(str, L"A"
                L"\x321d");
    rc = wcsnorm_s(str1, 18, str, WCSNORM_NFKC, NULL);
    ERR(ESLEMIN);
    WEXPSTR(str1, L"\0");
#endif

    /* echo "Aᾳ" | unorm -n nfd | iconv -t UTF-32LE | od -h */
    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x1fb3",
                   WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x3b1"
                 L"\x345");
    WEXPSTR(str, str1);
    INDCMP(!= 3);
    WCHECK_SLACK(&str[3], LEN - 3);

    /* Aᾳ => A≈ᾳ */
    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x3b1"
                   L"\x345",
                   WCSNORM_NFC, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x1fb3");
    WEXPSTR(str, str1);
    INDCMP(!= 2);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x1fb7",
                   WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x3b1"
                 L"\x342"
                 L"\x345");
    WEXPSTR(str, str1);
    INDCMP(!= 4);
    WCHECK_SLACK(&str[4], LEN - 4);

    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x3b1"
                   L"\x342"
                   L"\x345",
                   WCSNORM_NFC, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x1fb7");
    WEXPSTR(str, str1);
    INDCMP(!= 2);
    WCHECK_SLACK(&str[2], LEN - 2);

    /* TBL(6) on windows, no _exc tbl with canon */
#if SIZEOF_WCHAR_T == 2
    {
        wchar_t *sp = &str1[0];
        uint32_t cp = 0x1d1c0UL;    /* need surrogate pair for this */
        wcscpy(str1, L"\x0\x0\x0"); /* 0xd874 0xddc0 */
        _ENC_W16(sp, len, cp);
        rc = wcsnorm_s(str, LEN, str1, WCSNORM_NFD, &ind);
    }
    ERR(EOK);
    wcscpy(str1, L"\xd834\xddba\xd834\xdd65\xd834\xdd6f");
    WEXPSTR(str, str1);
    INDCMP(!= 6);
    WCHECK_SLACK(&str[6], LEN - 6);
#else
    /* MUSICAL SYMBOL FUSA BLACK */
    rc = wcsnorm_s(str, LEN, L"\x1d1c0", WCSNORM_NFD, &ind);
    ERR(EOK);
    /* MUSICAL SYMBOL MINIMA BLACK (U+1D1BC) MUSICAL SYMBOL COMBINING FLAG-2
     * (U+1D16F) */
    /* => MUSICAL SYMBOL SEMIBREVIS BLACK (U+1D1BA) MUSICAL SYMBOL COMBINING
     * STEM (U+1D165) + U+1D16F */
    wcscpy(str1, L"\x1d1ba\x1d165\x1d16f");
    WEXPSTR(str, str1);
    INDCMP(!= 3);
    WCHECK_SLACK(&str[3], LEN - 3);
#endif

    /*--------------------------------------------------*/

    /* reordering */
    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x3b1"
                   L"\x345"
                   L"\x342",
                   WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x3b1"
                 L"\x342"
                 L"\x345"); /* do reorder */
    WEXPSTR(str, str1);
    INDCMP(!= 4);
    WCHECK_SLACK(&str[4], LEN - 4);

    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x3b1"
                   L"\x345"
                   L"\x342",
                   WCSNORM_FCD, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x3b1"
                 L"\x345"
                 L"\x342"); /* no reorder */
    WEXPSTR(str, str1);
    INDCMP(!= 4);
    WCHECK_SLACK(&str[4], LEN - 4);

    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x3b1"
                   L"\x345"
                   L"\x342",
                   WCSNORM_NFC, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x1fb7"); /* nfc */
    WEXPSTR(str, str1);
    INDCMP(!= 2);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN,
                   L"A"
                   L"\x3b1"
                   L"\x345"
                   L"\x342",
                   WCSNORM_FCC, &ind);
    ERR(EOK);
    wcscpy(str1, L"A"
                 L"\x1fb7"); /* the same in this case */
    WEXPSTR(str, str1);
    INDCMP(!= 2);
    WCHECK_SLACK(&str[2], LEN - 2);

    /*--------------------------------------------------*/

    rc = wcsnorm_s(str, LEN, L"\x101", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x61"
                 L"\x304");
    WEXPSTR(str, str1);
    INDCMP(!= 2);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN,
                   L"\x61"
                   L"\x304",
                   WCSNORM_NFC, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x101");
    WEXPSTR(str, str1);
    INDCMP(!= 1);
    WCHECK_SLACK(&str[1], LEN - 1);

    rc = wcsnorm_s(str, LEN, L"\x115", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x65"
                 L"\x306");
    WEXPSTR(str, str1);
    INDCMP(!= 2);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN, L"\xdf", WCSNORM_NFD, &ind); /* !nfd */
    ERR(EOK);
    wcscpy(str1, L"\xdf");
    WEXPSTR(str, str1);
    INDCMP(!= 1);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN, L"\x37e", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x37e"); /* NFKC => 3b */
    len = wcslen(str1);
    INDCMP(!= (int)len);
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[len], LEN - len);

    rc = wcsnorm_s(str, LEN, L"\x385", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\xa8"
                 L"\x301");
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN, L"\x3ac", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x3b1"
                 L"\x301");
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN, L"\x3ad", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x3b5"
                 L"\x301");
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN, L"\x1f71", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x3b1"
                 L"\x301");
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN, L"\x1f02", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x3b1"
                 L"\x313"
                 L"\x300");
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[3], LEN - 3);

    rc = wcsnorm_s(str, LEN, L"\x1feb", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x3a5"
                 L"\x301");
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[2], LEN - 2);

    rc = wcsnorm_s(str, LEN, L"\x1fee", WCSNORM_NFD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\xa8"
                 L"\x301");
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[2], LEN - 2);

    /*--------------------------------------------------*/

    /* compat NFKD */
    /* echo "㈝" | unorm -n nfkd | iconv -t UTF-32LE | od -h */
    rc = wcsnorm_s(str, LEN, L"\x321d", WCSNORM_NFKD, &ind); /* TODO wchar2 */
#ifdef HAVE_NORM_COMPAT
    ERR(EOK);
    wcscpy(str1, L"(\x110b\x1169\x110c\x1165\x11ab)");
    len = wcslen(str1);
    INDCMP_(!= len);
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[len], LEN - len);
#else
    if (rc == -1) {
        ERR(-1);
    } else {
        debug_printf("%u TODO !norm-compat rc=%d\n", __LINE__, rc);
    }
#endif

#ifdef HAVE_NORM_COMPAT
    rc = wcsnorm_s(str, LEN, L"\x37e", WCSNORM_NFKC, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x3b");
    len = wcslen(str1);
    INDCMP_(!= len);
    WEXPSTR(str, str1);
    WCHECK_SLACK(&str[len], LEN - len);

    /* (오전) */
    rc = wcsnorm_s(str, LEN, L"\x321d", WCSNORM_NFKC, &ind);
    ERR(EOK);
    wcscpy(str1, L"(\xc624\xc804)");
    WEXPSTR(str, str1);
    len = wcslen(str1);
    INDCMP_(!= len);
    WCHECK_SLACK(&str[ind], LEN - ind);

    rc = wcsnorm_s(str, LEN, L"\xfdfb", WCSNORM_NFKD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x062c\x0644\x0020\x062c\x0644\x0627\x0644\x0647");
    WEXPSTR(str, str1);
    len = wcslen(str1);
    INDCMP_(!= len);
    WCHECK_SLACK(&str[ind], LEN - ind);

    rc = wcsnorm_s(str, LEN, L"\x2103", WCSNORM_NFKD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\xb0"
                 L"\x43");
    WEXPSTR(str, str1);
    len = wcslen(str1);
    INDCMP_(!= len);
    WCHECK_SLACK(&str[ind], LEN - ind);

    rc = wcsnorm_s(str, LEN, L"\x2150", WCSNORM_NFKD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x31"
                 L"\x2044"
                 L"\x37");
    WEXPSTR(str, str1);
    len = wcslen(str1);
    INDCMP_(!= len);
    WCHECK_SLACK(&str[ind], LEN - ind);

    rc = wcsnorm_s(str, LEN, L"\x3382", WCSNORM_NFKD, &ind);
    ERR(EOK);
    wcscpy(str1, L"\x03bc"
                 L"\x41");
    WEXPSTR(str, str1);
    len = wcslen(str1);
    INDCMP_(!= len);
    WCHECK_SLACK(&str[ind], LEN - ind);

#endif

    /*--------------------------------------------------*/

    /* see if we can lower-case and decompose all */
#ifdef PERL_TEST
    fprintf_s(pl,
              "use v5.27.2;\nno warnings;\nuse Unicode::Normalize;\nmy $err;\n"
              "sub wstr ($) {\n"
              "  join('',map{sprintf'\\x{%%X}',$_} unpack 'W*',shift);\n"
              "}\n"
              "sub chknfd {\n"
              "  my ($ch, $got) = @_;\n"
              "  my $nfd = NFD($ch);\n"
              "  if ($nfd ne $got) {\n"
              "    printf \"Error NFD \\\\x{%%X} = %%s; got: %%s\\n\",\n"
              "         unpack('W*',$ch), wstr $nfd, wstr $got;\n"
              "    1\n"
              "  }\n"
              "}\n");
#ifdef HAVE_NORM_COMPAT
    fprintf_s(pl, "sub chknfkd {\n"
                  "  my ($ch, $got) = @_;\n"
                  "  my $nfd = NFKD($ch);\n"
                  "  if ($nfd ne $got) {\n"
                  "    printf \"Error NFKD \\\\x{%%X} = %%s; got: %%s\\n\",\n"
                  "         unpack('W*',$ch), wstr $nfd, wstr $got;\n"
                  "    1\n"
                  "  }\n"
                  "}\n");
#endif
#endif
    for (ind = 0xc0; ind < 0x02fa20; ind++) {
        static wchar_t src[5];
        wchar_t *dest = &src[0];
        rsize_t dmax = 5;
        if (ind == 0xd800) {
            ind = 0xdfff;
            continue;
        }
        _ENC_W16(dest, dmax, ind);
        *dest = 0;
        rc = wcsnorm_s(str, 10, src, WCSNORM_NFD, &len);
        if (rc || len < 1) {
            debug_printf("%s %u  Error %d U+%04X len=%ld ", __FUNCTION__,
                         __LINE__, (int)rc, (int)ind, (long)len);
            WPRINTLS(src);
            debug_printf(" => ");
            WPRINTLSN(str);
            errs++;
        }
#ifdef PERL_TEST
        {
            size_t i;
            /* cross-check with perl */
            fprintf_s(pl, "$err += chknfd (\"\\N{U+%04X}\",\"\\N{U+%04X}", (unsigned)ind,
                      (unsigned)str[0]);
            for (i = 1; i < len; i++) {
                fprintf_s(pl, "\\N{U+%04X}", (unsigned)str[i]);
            }
            fprintf_s(pl, "\");\n");

#ifdef HAVE_NORM_COMPAT
            rc = wcsnorm_s(str, LEN, src, WCSNORM_NFKD, &len);
            fprintf_s(pl, "$err += chknfkd(\"\\N{U+%04X}\",\"\\N{U+%04X}", (unsigned)ind,
                      (unsigned)str[0]);
            for (i = 1; i < len; i++) {
                fprintf_s(pl, "\\N{U+%04X}", (unsigned)str[i]);
            }
            fprintf_s(pl, "\");\n");
#endif
        }
#endif
    }

    /*--------------------------------------------------*/

#ifdef PERL_TEST
    fprintf_s(pl, "exit $err;\n");
#ifdef BSD_ALL_LIKE
    fstat(pl->_file, &st);
    fclose(pl);
#elif defined __GLIBC__
    fstat(pl->_fileno, &st);
    fclose(pl);
#else
    fclose(pl);
    stat(TESTPL, &st);
#endif
    if (st.st_size) {
        printf("Cross check with " PERL ":\n");
        fflush(stdout);
        if (system(PERL " " TESTPL) < 0) {
            printf("Redo with perl (probably wrong Unicode version):\n");
            fflush(stdout);
            system("perl " TESTPL) || printf("perl " TESTPL " failed\n");
        }
    }
#ifndef DEBUG
    unlink(TESTPL);
#endif

#endif /* PERL_TEST */

    /*--------------------------------------------------*/

    return (errs);
}

int main(void) { return test_wcsnorm_s(); }
