/*
   PURPOSE: ( Functions to convert narrow (char) strings to wide (wchar_t) strings and vice-versa, as well as functions
   to determine the number of characters that would result, without actually performing a conversion.)

   PROGRAMMERS: (((John M. Penn) (L-3 Communications) (08/2006))) */

#if !( __Lynx__ || __sgi || __INTERIX)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wctype.h>
#include <limits.h>
#include "trick/wcs_ext.h"


size_t wcs_to_ncs_len(const wchar_t * wcs)
{
    return wcs_to_ncs(wcs, (char *) NULL, (size_t) 0);
}

size_t ncs_to_wcs_len(const char *ncs)
{
    return ncs_to_wcs(ncs, (wchar_t *) NULL, (size_t) 0);
}

size_t wcs_to_ncs(const wchar_t * wcs, char *ncs, size_t ncs_max_len)
{
    unsigned int i, wlen, nlen;

    if (wcs == (wchar_t *) NULL) {
        fprintf(stderr, "%s:wcs_to_ncs: Wide input character string is NULL. No conversion performed.\n", __FILE__);
        return 0;
    }

    wlen = wcslen(wcs);
    nlen = 0;
    if (ncs != NULL) {
        ncs[0] = '\0';
    }

    for (i = 0; i < wlen; i++) {
        wint_t wcint = wcs[i];
        char work_s[11];

        if (iswprint(wcint)) {
            int len;
            /* The wide character is printable in the current locale, so create a multibyte, external representation of
               it. Note that the external representation will also be printable in the current locale. */

            if (wcint == '\'') {
                snprintf(work_s, sizeof(work_s), "\\\'");
            } else if (wcint == '\"') {
                snprintf(work_s, sizeof(work_s), "\\\"");
            } else if (wcint == '\?') {
                snprintf(work_s, sizeof(work_s), "\\\?");
            } else if (wcint == '\\') {
                snprintf(work_s, sizeof(work_s), "\\\\");
            } else {
                len = wcrtomb(work_s, (wchar_t) wcint, NULL);
                work_s[len] = '\0';
            }

        } else {
            /* The wide character is not printable in the current locale so, create a printable, external
               representation of this non-printable character. */
            if (wcint <= 0xFF) {
                if (wcint == '\a') {
                    snprintf(work_s, sizeof(work_s), "\\a");
                } else if (wcint == '\b') {
                    snprintf(work_s, sizeof(work_s), "\\b");
                } else if (wcint == '\f') {
                    snprintf(work_s, sizeof(work_s), "\\f");
                } else if (wcint == '\n') {
                    snprintf(work_s, sizeof(work_s), "\\n");
                } else if (wcint == '\r') {
                    snprintf(work_s, sizeof(work_s), "\\n");
                } else if (wcint == '\t') {
                    snprintf(work_s, sizeof(work_s), "\\t");
                } else if (wcint == '\v') {
                    snprintf(work_s, sizeof(work_s), "\\v");
                } else {
                    snprintf(work_s, sizeof(work_s), "\\x%02x", wcint);
                }
            } else if (wcint <= 0xFFFF) {
                snprintf(work_s, sizeof(work_s), "\\u%04X", wcint);
            } else {
                snprintf(work_s, sizeof(work_s), "\\U%08X", wcint);
            }
        }

        nlen += strlen(work_s);

        if (ncs != NULL) {
            if (nlen < ncs_max_len) {
                strcat(ncs, work_s);
            } else {
                fprintf(stderr, "%s:wcs_to_ncs: Insufficient storage for the (narrow) output character string.\n",
                        __FILE__);
                return 0;
            }
        }

    }                                  /* end-for each wide character */

    return (nlen);
}

size_t ncs_to_wcs(const char *ncs, wchar_t * wcs, size_t w_max_size)
{

    mbstate_t state;
    wchar_t wc_in, wc_out = 0;
    int code = 0;
    int pstate = 0;
    size_t wlen = 0;
    size_t nbytes;
    size_t len;                 /* number of bytes remaining in the input string. */

    if (ncs == (char *) NULL) {
        fprintf(stderr, "%s:ncs_to_wcs: Narrow input character string is NULL. No conversion performed.\n", __FILE__);
        return 0;
    }
    len = strlen(ncs);

    memset(&state, '\0', sizeof(state));

    /* While we have another wide-character and there is sufficient space to store it ... The extra (len > 0 ) check is
       for the Macs because mbrtowc still returns values > 0 if len == 0 */
    while (((nbytes = mbrtowc(&wc_in, ncs, len, &state)) > 0) && (len > 0)) {

        wint_t wcint;
        int digits_remaining = 0;
        int digit = 0;

        len -= nbytes;
        ncs += nbytes;
        wcint = (wint_t) wc_in;

        if (pstate && (wcint > 0x7F)) {
            // We should only find ASCII characters in the escaped state.
            fprintf(stderr, "%s:ncs_to_wcs: Unknown escape sequence. No conversion performed.\n", __FILE__);
            return 0;
        }

        switch (pstate) {
            case 0:{
                    if (wcint == '\\') {
                        pstate = 1;
                    } else {
                        // Store the wide-character.
                        wc_out = wc_in;
                    }
                }
                break;

            case 1:{
                    switch (wcint) {
                        case '\'':
                        case '\"':
                        case '\?':
                        case '\\':{
                                wc_out = wc_in;
                                pstate = 0;
                            }
                            break;

                        case 'a':{
                                wc_out = btowc((int) '\a');
                                pstate = 0;
                            } break;
                        case 'b':{
                                wc_out = btowc((int) '\b');
                                pstate = 0;
                            } break;
                        case 'f':{
                                wc_out = btowc((int) '\f');
                                pstate = 0;
                            } break;
                        case 'n':{
                                wc_out = btowc((int) '\n');
                                pstate = 0;
                            } break;
                        case 'r':{
                                wc_out = btowc((int) '\r');
                                pstate = 0;
                            } break;
                        case 't':{
                                wc_out = btowc((int) '\t');
                                pstate = 0;
                            } break;
                        case 'v':{
                                wc_out = btowc((int) '\v');
                                pstate = 0;
                            } break;
                        case 'x':{
                                pstate = 2;
                                digits_remaining = 2;
                                code = 0;
                            }
                            break;
                        case 'u':{
                                pstate = 2;
                                digits_remaining = 4;
                                code = 0;
                            }
                            break;
                        case 'U':{
                                pstate = 2;
                                digits_remaining = 8;
                                code = 0;
                            }
                            break;
                        default:{
                                fprintf(stderr, "%s:ncs_to_wcs: Unknown escape sequence. No conversion performed.\n",
                                        __FILE__);
                                return 0;
                            }
                    }                  // switch wcint

                }
                break;                 // case 1

            case 2:{
                    if (digits_remaining > 0) {
                        if (wcint >= '0' && wcint <= '9') {
                            digit = (int) wcint - (int) '0';
                        } else if (wcint >= 'A' && wcint <= 'F') {
                            digit = (int) wcint - (int) 'A' + 10;;
                        } else if (wcint >= 'a' && wcint <= 'f') {
                            digit = (int) wcint - (int) 'a' + 10;;
                        } else {
                            // error
                        }
                        code = code * 16 + digit;
                        digits_remaining--;

                        if (digits_remaining == 0) {
                            wc_out = code;
                            pstate = 0;
                        }
                    }
                }
                break;
        }                              /* switch */

        if (pstate == 0) {
            if (wcs != NULL) {
                if (wlen < w_max_size) {
                    wcs[wlen] = wc_out;
                } else {
                    fprintf(stderr, "%s:ncs_to_wcs: Insufficient storage for the (wide) output character string.\n",
                            __FILE__);
                }
            }
            wlen++;
        }


    }                                  /* while */

    if (wcs == NULL) {
        return wlen;
    }

    if (wlen < w_max_size) {
        wcs[wlen] = 0 ;
        return wlen;
    } else {
        fprintf(stderr, "%s:ncs_to_wcs: Insufficient storage for the (wide) output character string.\n", __FILE__);
        return 0;
    }
}
#else
int wcs_to_ncs_len(void)
{
    return (0);
}

int ncs_to_wcs_len(void)
{
    return (0);
}

int wcs_to_ncs(void)
{
    return (0);
}

int ncs_to_wcs(void)
{
    return (0);
}
#endif
