/*
 * Unit tests for API functions of libjpiconv
 *
 * SPDX-FileType: SOURCE
 * SPDX-FileCopyrightText: Michael Bäuerle
 * SPDX-License-Identifier: BSD-2-Clause
 */

/* Check for XSI extension on POSIX systems older than POSIX.1-2001 */
#if (defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200112L)
#include <unistd.h>
#if _XOPEN_VERSION >= 500
#define _XOPEN_SOURCE  _XOPEN_VERSION
#endif  /* _XOPEN_VERSION >= 500 */
#endif  /* (defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200112L) */

/* System headers */
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>

/* libjpiconv */
#include "libjpiconv-0/iconv.h"
#include "libjpiconv-0/iconv_table_ni.h"

/* CHEAT unit test framework */
#ifndef __BASE_FILE__
#define __BASE_FILE__ JPIC0_UTFILE
#endif  /* __BASE_FILE__ */
#include <cheat.h>


CHEAT_DECLARE(
    static size_t rv CHEAT_COMMA inlen CHEAT_COMMA outlen;

    /* Input buffer */
    static char inbuf[256];

    /* Output buffer (four times larger, sufficient for UTF-8) */
    static char outbuf[1024];

    /* Fill input buffer with octet values representing the index */
    void PREPARE_INBUF(void)
    {
        size_t i;

        for (i = 0; 256 > i; ++i)
            inbuf[i] = i;
    }
)


/* Check return value and print debug output after error */
CHEAT_DECLARE(
    void CHECK(size_t rv, size_t expected)
    {
        if (expected != rv)
        {
            if ((size_t)-1 == rv)
                fprintf(stderr, "rv: -1, ", (unsigned long int)rv);
            else
                fprintf(stderr, "rv: %lu, ", (unsigned long int)rv);

            fprintf(stderr, "inlen: %lu, outlen: %lu\n",
                    (unsigned long int)inlen, (unsigned long int)outlen);

            /* Print human readable string for errno */
            perror("");
        }

        cheat_assert(expected == rv);
    }
)


/* Load conversion input data from file */
CHEAT_DECLARE(
    void LOAD_INPUT_DATA(char *buf, size_t len, size_t *inlen, const char* pn)
    {
        FILE *fp = fopen(pn, "rb");

        cheat_assert(NULL != fp);
        if (NULL != fp)
        {
            int    rv_load = EOF;
            size_t i       = 0;

            *inlen = 0;
            for ( ; len > i; ++i)
            {
                rv_load = fgetc(fp);
                if (EOF == rv_load)
                    break;
                else
                {
                    buf[i] = (char)rv_load;
                    ++(*inlen);
                }
            }

            /* Check for EOF in reference data file */
            if (EOF != rv_load)
            {
                rv_load = fgetc(fp);
                cheat_assert(EOF == rv_load);
            }

            rv_load = fclose(fp);
            cheat_assert(0 == rv_load);
        }
    }
)


/* Compare conversion output with reference file */
CHEAT_DECLARE(
    void COMPARE_WITH_REFERENCE(const char *buf, size_t len, const char* pn)
    {
        FILE *fp = fopen(pn, "rb");

        cheat_assert(NULL != fp);
        if (NULL != fp)
        {
            int    rv_compare = EOF;
            size_t i          = 0;

            /* Compare with reference data */
            for ( ; len > i; ++i)
            {
                unsigned int chk = (unsigned char)buf[i];

                rv_compare = fgetc(fp);
                cheat_assert(EOF != rv_compare);
                {
                    unsigned int ref = (unsigned char)rv_compare;

                    if (chk != (unsigned char)rv_compare)
                    {

                        fprintf( stderr, "Mismatch at index %u (0x%02X): "
                                 "Checked 0x%02X against Reference 0x%02X\n",
                                 (unsigned int) i, (unsigned int) i, chk, ref );
                    }
                    cheat_assert(chk == ref);
                    cheat_yield();
                }
            }

            /* Check for EOF in reference data file */
            rv_compare = fgetc(fp);
            cheat_assert(EOF == rv_compare);

            rv_compare = fclose(fp);
            cheat_assert(0 == rv_compare);
        }
    }
)


/* Print CHEAT version */
CHEAT_TEST(version,
    (void)cheat_print(stdout, "%s", 1, "Unit test framework: ");
    cheat_print_version();
    /* Yellow foreground color is unreadable with white background */
    (void)cheat_print(stdout,
                      "%s", 1, "Yellow foreground color patched out.\n\n");
)


CHEAT_TEST(empty_name,
    size_t rv;

    (void)fputs("Testing jpic0_iconvstr() with empty name ... \n", stdout);

    inbuf[0] = 0x20;
    inlen    = 1;
    outlen   = 1024;
    rv = jpic0_iconvstr("", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EBADF == errno);
)


CHEAT_TEST(invalid_name,
    (void)fputs("Testing jpic0_iconvstr() with invalid name ... \n", stdout);

    inbuf[0] = 0x20;
    inlen    = 1;
    outlen   = 1024;
    rv = jpic0_iconvstr("INVAL", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EBADF == errno);
)


CHEAT_TEST(buffer_size,
    (void)fputs("Testing jpic0_iconvstr() "
                "with insufficient output buffer ... \n", stdout);

    inbuf[0] = 0x41;
    inbuf[1] = 0x42;
    inbuf[2] = 0x43;
    inlen    = 3;
    outlen   = 2;
    rv = jpic0_iconvstr("UtF-8", "IsO-2022-jP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(E2BIG == errno);
    cheat_assert(1U == inlen);
)


CHEAT_TEST(control_nul,
    (void)fputs("Testing jpic0_iconvstr() with NUL control character ... \n",
                  stdout);

    inbuf[0] = 0x20;
    inbuf[1] = 0x00;
    inbuf[2] = 0x20;
    inlen    = 3;
    outlen   = 1024;
    rv = jpic0_iconvstr("UTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, 0);
    cheat_assert(2U == inlen);
)


CHEAT_TEST(non_ident,
    (void)fputs("Testing jpic0_iconvstr() with non-identical conversions ... \n",
                stdout);

    inbuf[0] = 0x80;
    inbuf[1] = 0x20;
    inbuf[2] = 0x80;
    inlen    = 3;
    outlen   = 1024;
    rv = jpic0_iconvstr("UTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_yield();

    inlen    = 3;
    outlen   = 1024;
    rv = jpic0_iconvstr("UTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 2);
)


CHEAT_TEST(us_ascii,
    (void)fputs("Testing jpic0_iconvstr() with undeclared ISO-646-US ... \n",
                stdout);

    PREPARE_INBUF();
    inbuf[0x1B] = 0x00;  /* Map ESC to NUL */
    inlen       = 256;
    outlen      = 1024;
    rv = jpic0_iconvstr("UtF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_IGNORE_NULL | JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 128);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/US-ASCII.utf8");
)


CHEAT_TEST(iso_646_us,
    (void)fputs("Testing jpic0_iconvstr() with ISO-646-US ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-646-US.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_IGNORE_NULL | JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/ISO-646-US.utf8");
)


CHEAT_TEST(iso_646_jp,
    (void)fputs("Testing jpic0_iconvstr() with ISO-646-JP ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-646-JP.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_IGNORE_NULL | JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/ISO-646-JP.utf8");
)


CHEAT_TEST(jis_x_208,
    (void)fputs("Testing jpic0_iconvstr() with JIS X 208 ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, 0);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox.utf8");
)


CHEAT_TEST(jis_x_208_nsto,
    (void)fputs("Testing jpic0_iconvstr() "
                "with no switch to ISO-646-US at the end ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_ns.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EINVAL == errno);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox.utf8");
)


CHEAT_TEST(jis_x_208_nul,
    (void)fputs("Testing jpic0_iconvstr() "
                "with NUL control character at the end ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_nul.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_IGNORE_NULL);
    CHECK(rv, 0);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox_nul.utf8");
)


CHEAT_TEST(jis_x_208_broken,
    (void)fputs("Testing jpic0_iconvstr() "
                "with JIS X 208 and broken kuten ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen,
                    "reference/ISO-2022-JP_fox_broken.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_assert(7 == inlen);

    errno = 0;
    LOAD_INPUT_DATA(inbuf, 256U, &inlen,
                    "reference/ISO-2022-JP_fox_broken.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox_broken.utf8");
)


CHEAT_TEST(jis_x_208_crlf,
    (void)fputs("Testing jpic0_iconvstr() "
                "with JIS X 208 and CRLF in multibyte mode ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_crlf.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_assert(7 == inlen);

    errno = 0;
    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_crlf.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 4);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox_crlf.utf8");
)


CHEAT_TEST(jis_x_208_garbage,
    (void)fputs("Testing jpic0_iconvstr() with JIS X 208 "
                "and garbage before ESC control character ... \n", stdout);

   LOAD_INPUT_DATA(inbuf, 256U, &inlen,
                    "reference/ISO-2022-JP_fox_garbage.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("UTF-8", "IsO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_assert(4 == inlen);

    errno = 0;
    LOAD_INPUT_DATA(inbuf, 256U, &inlen,
                    "reference/ISO-2022-JP_fox_garbage.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("UTF-8", "IsO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox_garbage.utf8");
)


CHEAT_TEST(jis_x_208_truncated,
    (void)fputs("Testing jpic0_iconvstr() "
                "with JIS X 208 and truncated kuten at the end ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_trunc.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EINVAL == errno);
    cheat_assert(1 == inlen);

    errno = 0;
    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_trunc.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox_trunc.utf8");
)


CHEAT_TEST(jis_x_208_unassigned,
    (void)fputs("Testing jpic0_iconvstr() "
                "with JIS X 208 and unassigned kuten ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_ua.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_assert(7 == inlen);

    errno = 0;
    LOAD_INPUT_DATA(inbuf, 256U, &inlen, "reference/ISO-2022-JP_fox_ua.raw");
    outlen = 1024;
    rv = jpic0_iconvstr("uTF-8", "ISO-2022-JP", inbuf, &inlen, outbuf, &outlen,
                        JPIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_assert(0 == inlen);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 1024U - outlen, "reference/fox_ua.utf8");
)
