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

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

#include "libssiconv-0/iconv.h"  /* Always include main header file first */
#include "libssiconv-0/iconv_bool.h"


/* For every input octet a maximum of four UTF-8 output octets are created */
#define SSIC0_I_CLI_INBUF_SIZE   8192U
#define SSIC0_I_CLI_OUTBUF_SIZE  32768U


static char ssic0_i_cli_inbuf[SSIC0_I_CLI_INBUF_SIZE];
static char ssic0_i_cli_outbuf[SSIC0_I_CLI_OUTBUF_SIZE];

static ssic0_i_bool  ssic0_i_cli_omit_invalid    = 0;     /* For "-c" option */
static ssic0_i_bool  ssic0_i_cli_suppress_stderr = 0;     /* For "-s" option */
static const char   *ssic0_i_cli_from_encoding   = NULL;  /* For "-f" option */
static const char   *ssic0_i_cli_to_encoding     = NULL;  /* For "-t" option */
static const char   *ssic0_i_cli_pathname        = NULL;  /* For "file" */


/* ========================================================================== */
/*
 * Parse command line options.
 * Returns zero on success.
 */
static ssic0_i_bool ssic0_i_cli_parse_options(int argc, char **argv)
{
    if (5 > argc)
        goto error;

    {
        size_t       opt_num  = argc;
        ssic0_i_bool opt_from = 0;
        ssic0_i_bool opt_to   = 0;
        size_t       i        = 1;

        for ( ; opt_num > i; ++i)
        {
            if (opt_from)
            {
                opt_from                  = 0;
                ssic0_i_cli_from_encoding = argv[i];
                continue;
            }
            if (opt_to)
            {
                opt_to                  = 0;
                ssic0_i_cli_to_encoding = argv[i];
                continue;
            }

            if (!strcmp("-c", argv[i]))
                ssic0_i_cli_omit_invalid = 1;
            else if (!strcmp("-s", argv[i]))
                ssic0_i_cli_suppress_stderr = 1;
            else if (!strcmp("-f", argv[i]))
                opt_from = 1;
            else if (!strcmp("-t", argv[i]))
                opt_to = 1;
            else if (!strcmp("--", argv[i]))
            {
                /* POSIX conformant explicit option list termination */
                ++i;
                break;
            }
            else
                break;
        }

        /* First non-option is accepted as "file" operand */
        if (opt_num > i)
        {
            /* "-" must be treated as standard input */
            if (strcmp("-", argv[i]))
                ssic0_i_cli_pathname = argv[i];
        }
    }

    if ( (NULL == ssic0_i_cli_from_encoding) ||
         (NULL == ssic0_i_cli_to_encoding) )
        goto error;

    return 0;

error:
    fprintf(stderr, "Invalid options\n");
    return 1;
}


/* ========================================================================== */
/* Copy 'len' octets output buffer to standard output */
static void ssic0_i_cli_flush(size_t len)
{
    size_t i = 0;

    for ( ; len > i; ++i)
        fputc((unsigned char)ssic0_i_cli_outbuf[i], stdout);
}


/* ========================================================================== */
/*
 * Convert intput data.
 * Returns exit status for main().
 */
static int ssic0_i_cli_convert(FILE *instream)
{
    int flag = SSIC0_ICONV_IGNORE_NULL;

    while (1)
    {
        size_t outlen = SSIC0_I_CLI_OUTBUF_SIZE;
        size_t inlen  = fread(ssic0_i_cli_inbuf, 1, SSIC0_I_CLI_INBUF_SIZE,
                              instream);

        if (0 == inlen)
            break;

cont:
        errno = 0;
        {
            size_t in = inlen;
            size_t rv = ssic0_iconvstr(ssic0_i_cli_to_encoding,
                                       ssic0_i_cli_from_encoding,
                                       ssic0_i_cli_inbuf, &inlen,
                                       ssic0_i_cli_outbuf, &outlen,
                                       flag);

            if (SSIC0_I_CLI_OUTBUF_SIZE < outlen)
            {
                fprintf(stderr, "Invalid output data size (bug)\n");
                return 1;
            }
            if ((size_t)-1 == rv)
            {
                if (EBADF == errno)
                    fprintf(stderr, "Requested conversion not supported\n");
                else if (EILSEQ == errno)
                {
                    if (!ssic0_i_cli_suppress_stderr)
                        fprintf(stderr, "Invalid input octet\n");
                    if (0 == inlen)
                        fprintf(stderr, "No input data left (bug)\n");
                    else if (ssic0_i_cli_omit_invalid)
                    {
                        size_t converted = in - (--inlen);

                        ssic0_i_cli_flush(SSIC0_I_CLI_OUTBUF_SIZE - outlen);
                        outlen = SSIC0_I_CLI_OUTBUF_SIZE;
                        memmove(ssic0_i_cli_inbuf,
                                &ssic0_i_cli_inbuf[converted], inlen);
                        goto cont;
                    }
                }
                else
                    fprintf(stderr, "Unknown error (bug)\n");
                return 1;
            }
            if (0 != rv)
            {
                fprintf(stderr, "Conversion failed (bug)\n");
                return 1;
            }
        }
        ssic0_i_cli_flush(SSIC0_I_CLI_OUTBUF_SIZE - outlen);
    }

    return 0;
}


/* ========================================================================== */
int main(int argc, char **argv)
{
    FILE *instream = stdin;

    /* List available codeset descriptions */
    if (2 == argc && !strcmp("-l", argv[1]))
    {
        ssic0_print_codesets();
        return 0;
    }

    /* Setup conversion state */
    if (ssic0_i_cli_parse_options(argc, argv))
        return 1;

    /* Prepare input file stream */
    if (NULL != ssic0_i_cli_pathname)
    {
        instream = fopen(ssic0_i_cli_pathname, "rb");
        if (NULL == instream)
        {
            fprintf(stderr, "Failed to open input file\n");
            return 1;
        }
    }

    /* Execute conversion */
    return ssic0_i_cli_convert(instream);
}
