/*
 * Conversion functions for libuciconv
 *
 * SPDX-FileType: SOURCE
 * SPDX-FileCopyrightText: Michael Bäuerle
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <stddef.h>
#include <string.h>

#include "libuciconv-0/iconv.h"  /* Always include main header file first */
#include "iconv_cesu-8.h"
#include "iconv_name.h"
#include "iconv_utf-7.h"
#include "iconv_private.h"


/* Check macros must not depend on C execution character set and/or locale */
#define UCIC0_I_PRINTABLE_ASCII(c)  ((0x20U < c) && (0x7FU > c))
#define UCIC0_I_LOWERCASE_ASCII(c)  ((0x60U < c) && (0x7BU > c))


/* ========================================================================== */
/*
 * Search encoding definition 'def' for encoding 'enc'.
 *
 * Returns a pointer to a conversion function or NULL (encoding not supported).
 */
static ucic0_i_func ucic0_i_check_def(const char *enc,
                                      const ucic0_i_name_def *def)
{
    size_t i = 0;

    while (NULL != def[i].name)
    {
        if (!strcmp(enc, def[i].name))
            return def[i].func;
        ++i;
    }

    return NULL;
}


/* ========================================================================== */
/*
 * Select encoding definition and search for encoding 'enc'.
 *
 * Returns a pointer to a conversion function or NULL (encoding not supported).
 */
static ucic0_i_func ucic0_i_check(const char *enc)
{
    ucic0_i_func ret = NULL;

    ret = ucic0_i_check_def(enc, ucic0_i_name_src);
    if (NULL != ret)
        return ret;

#if UCIC0_I_NAME_ENABLE_ALIASES
    ret = ucic0_i_check_def(enc, ucic0_i_name_src_alias);
    if (NULL != ret)
        return ret;
#endif  /* UCIC0_I_NAME_ENABLE_ALIASES */

#if UCIC0_I_NAME_ENABLE_NONSTANDARD
    ret = ucic0_i_check_def(enc, ucic0_i_name_src_nonstd);
    if (NULL != ret)
        return ret;
#endif  /* UCIC0_I_NAME_ENABLE_NONSTANDARD */

    return ret;
}


/* ========================================================================== */
/*
 * Check string for printable US-ASCII and convert it to uppercase
 *
 * The parameter 'str' must point to a string with NUL-termination.
 *
 * Returns 1 for success and 0 for error.
 */
static int ucic0_i_to_uppercase(char *str)
{
    size_t i = 0;

    do
    {
        unsigned char c = str[i];

        if (0 == c)
            break;

        if (!UCIC0_I_PRINTABLE_ASCII(c))
            return 0;
        if (UCIC0_I_LOWERCASE_ASCII(c))
            str[i] = c - 0x20U;
    }
    while (UCIC0_I_NAME_MAX > ++i);

    return 1;
}


/* ========================================================================== */
/*
 * Check whether conversion from an encoding to Unicode is supported.
 *
 * The parameter 'enc' is the name of the encoding (treated case-insensitive).
 *
 * Returns a pointer to the mapping table or NULL (not supported).
 */
static ucic0_i_func ucic0_i_check_source_encoding(const char *enc)
{
    char buf[UCIC0_I_NAME_MAX + 2U];  /* +2 to silence warning from GCC 14 */

    /* Copy name and ensure that it is NUL-terminated (not too long) */
    (void)strncpy(buf, enc, UCIC0_I_NAME_MAX + 1U);
    if (0 != buf[UCIC0_I_NAME_MAX])
        return NULL;

    /* Check name for printable US-ASCII and convert it to uppercase */
    if (!ucic0_i_to_uppercase(buf))
        return NULL;

    /* Check whether encoding is supported */
    return ucic0_i_check(buf);
}


/* ========================================================================== */
/*
 * Check target encoding.
 *
 * The parameter 'enc' is the name of the encoding (treated case-insensitive).
 *
 * Returns 1 for "UTF-8" (supported) or 0 otherwise (not supported).
 */
static unsigned char ucic0_i_check_target_encoding(const char *enc)
{
    char buf[UCIC0_I_NAME_MAX + 2U];  /* +2 to silence warning from GCC 14 */

    /* Copy name and ensure that it is NUL-terminated (not too long) */
    (void)strncpy(buf, enc, UCIC0_I_NAME_MAX + 1U);
    if (0 != buf[UCIC0_I_NAME_MAX])
        return 0;

    /* Check name for printable US-ASCII and convert it to uppercase */
    if (!ucic0_i_to_uppercase(buf))
        return 0;

    /* Check whether encoding is supported */
    if (!strncmp("UTF-8", buf, UCIC0_I_NAME_MAX + 1U))
        return 1;

    return 0;
}


/* ========================================================================== */
size_t ucic0_iconvstr(const char *tocode, const char *fromcode,
                      char *inarray, size_t *inlen,
                      char *outarray, size_t *outlen,
                      int flag)
{
    const unsigned char tgt  = ucic0_i_check_target_encoding(tocode);
    ucic0_i_func const  conv = ucic0_i_check_source_encoding(fromcode);
    /* clang compiler complains with { NULL } */
    ucic0_i_state state      = { NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0 };

    if ((NULL == conv) || (0 == tgt))
    {
        /* Requested conversion is not supported */
        errno = UCIC0_I_EBADF;
        return (size_t)-1;
    }

    /* C90 does not allow aggregate initializer with non-constant expressions */
    state.inarray      = inarray;
    state.inlen        = inlen;
    state.outarray     = outarray;
    state.outlen       = outlen;
    state.inlen_start  = *inlen;
    state.outlen_start = *outlen;
    state.flag         = flag;

    if (conv(&state))
        return (size_t)-1;

    return state.nonident;
}
