#include <ansi_fp.h>
#include <ctype.h>
#include <locale.h>
#include <math.h>
#include <mbstring.h>
#include <mem.h>
#include <secure_error.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>

#include <wstring.h>

#define MAX_SIG_DIG 32

#define LDBL_MANT_DIG 24
#define LDBL_MAX_EXP 128
#define TARGET_FLOAT_BITS 32
#define TARGET_FLOAT_BYTES (TARGET_FLOAT_BITS / 8)
#define TARGET_FLOAT_MAX_EXP LDBL_MAX_EXP
#define TARGET_FLOAT_MANT_DIG LDBL_MANT_DIG
#define TARGET_FLOAT_IMPLICIT_J_BIT 1
#define TARGET_FLOAT_MANT_BITS (TARGET_FLOAT_MANT_DIG - TARGET_FLOAT_IMPLICIT_J_BIT)
#define TARGET_FLOAT_EXP_BITS (TARGET_FLOAT_BITS - TARGET_FLOAT_MANT_BITS - 1)

typedef long long intmax_t;

#define PTRDIFF __typeof__((char *) 0 - (char *) 0)
typedef PTRDIFF ptrdiff_t;

int vswprintf(wchar_t *s, size_t n, const wchar_t *format, va_list arg);

enum justification_options {
    left_justification,
    right_justification,
    zero_fill
};

enum sign_options {
    only_minus,
    sign_always,
    space_holder
};

enum argument_options {
    // normal_argument,
    // char_argument,
    // short_argument,
    // long_argument,
    // long_long_argument,
    // wchar_argument,
    // intmax_argument,
    // size_t_argument,
    // ptrdiff_argument,
    // long_double_argument
    normal_argument,
    char_argument,
    short_argument,
    long_argument,
    long_long_argument,
    wchar_argument,
    intmax_argument,
    size_t_argument,
    ptrdiff_argument,
    long_double_argument
};

typedef struct {
    unsigned char justification_options;
    unsigned char sign_options;
    unsigned char precision_specified;
    unsigned char alternate_form;
    unsigned char argument_options;
    wchar_t conversion_char;
    int field_width;
    int precision;
} print_format;

typedef struct {
    char *CharStr;
    size_t MaxCharCount;
    size_t CharsWritten;
} __OutStrCtrl;

#define conversion_buff_size 512
#define conversion_max 509
#define bad_conversion 0xFFFF

static const wchar_t *parse_format(const wchar_t *format_string, va_list *arg, print_format *format) {
    print_format f;
    const wchar_t *s = format_string;
    int c;
    int flag_found;

    f.justification_options = right_justification;
    f.sign_options          = only_minus;
    f.precision_specified   = 0;
    f.alternate_form        = 0;
    f.argument_options      = normal_argument;
    f.field_width           = 0;
    f.precision             = 0;

    if ((c = *++s) == L'%') {
        f.conversion_char = c;
        *format           = f;
        return ((const wchar_t *) s + 1);
    }

    while (true) {
        flag_found = 1;

        switch (c) {
            case L'-':
                f.justification_options = left_justification;
                break;

            case L'+':
                f.sign_options = sign_always;
                break;

            case L' ':
                if (f.sign_options != sign_always) {
                    f.sign_options = space_holder;
                }
                break;

            case L'#':
                f.alternate_form = 1;
                break;

            case L'0':
                if (f.justification_options != left_justification) {
                    f.justification_options = zero_fill;
                }
                break;

            default:
                flag_found = 0;
                break;
        }

        if (flag_found) {
            c = *++s;
        } else {
            break;
        }
    }

    if (c == L'*') {
        if ((f.field_width = va_arg(*arg, int)) < 0) {
            f.justification_options = left_justification;
            f.field_width           = -f.field_width;
        }

        c = *++s;
    } else {
        while (isdigit(c)) {
            f.field_width = (f.field_width * 10) + (c - L'0');
            c             = *++s;
        }
    }

    if (f.field_width > conversion_max) {
        f.conversion_char = bad_conversion;
        *format           = f;
        return ((const wchar_t *) s + 1);
    }

    if (c == L'.') {
        f.precision_specified = 1;

        if ((c = *++s) == L'*') {
            if ((f.precision = va_arg(*arg, int)) < 0) {
                f.precision_specified = 0;
            }

            c = *++s;
        } else {
            while (isdigit(c)) {
                f.precision = (f.precision * 10) + (c - L'0');
                c           = *++s;
            }
        }
    }

    flag_found = 1;

    switch (c) {
        case L'h':
            f.argument_options = short_argument;
            if (s[1] == L'h') {
                f.argument_options = char_argument;
                c                  = *++s;
            }
            break;
        case L'l':
            f.argument_options = long_argument;
            if (s[1] == L'l') {
                f.argument_options = long_long_argument;
                c                  = *++s;
            }
            break;
        case L'L':
            f.argument_options = long_double_argument;
            break;
        case L'j':
            f.argument_options = intmax_argument;
            break;
        case L't':
            f.argument_options = ptrdiff_argument;
            break;
        case L'z':
            f.argument_options = size_t_argument;
            break;
        default:
            flag_found = 0;
            break;
    }

    if (flag_found) {
        c = *++s;
    }

    f.conversion_char = c;

    switch (c) {
        case L'd':
        case L'i':
        case L'u':
        case L'o':
        case L'x':
        case L'X':
            if (f.argument_options == long_double_argument) {
                f.argument_options = long_long_argument;
            }

            if (!f.precision_specified) {
                f.precision = 1;
            } else if (f.justification_options == zero_fill) {
                f.justification_options = right_justification;
            }
            break;

        case L'f':
        case L'F':
            if (f.argument_options == short_argument || f.argument_options == intmax_argument ||
                f.argument_options == size_t_argument || f.argument_options == ptrdiff_argument ||
                f.argument_options == long_long_argument) {
                f.conversion_char = bad_conversion;
                break;
            }

            if (!f.precision_specified) {
                f.precision = 6;
            }
            break;

        case L'a':
        case L'A':
            if (!f.precision_specified) {
                f.precision = 0x0D;
            }
            if (f.argument_options == short_argument || f.argument_options == intmax_argument ||
                f.argument_options == size_t_argument || f.argument_options == ptrdiff_argument ||
                f.argument_options == long_long_argument || f.argument_options == char_argument) {
                f.conversion_char = bad_conversion;
            }
            break;

        case L'g':
        case L'G':
            if (!f.precision) {
                f.precision = 1;
            }

        case L'e':
        case L'E':
            if (f.argument_options == short_argument || f.argument_options == char_argument ||
                f.argument_options == intmax_argument || f.argument_options == size_t_argument ||
                f.argument_options == ptrdiff_argument || f.argument_options == long_long_argument) {
                f.conversion_char = bad_conversion;
                break;
            }

            if (!f.precision_specified) {
                f.precision = 6;
            }
            break;

        case L'p':
            f.argument_options = long_argument;
            f.alternate_form   = 1;
            f.conversion_char  = L'x';
            f.precision        = 8;
            break;

        case L'c':
            if (f.argument_options == long_argument) {
                f.argument_options = wchar_argument;
            } else if (f.precision_specified || f.argument_options != normal_argument) {
                f.conversion_char = bad_conversion;
            }
            break;

        case L's':
            if (f.argument_options == long_argument) {
                f.argument_options = wchar_argument;
            } else if (f.argument_options != normal_argument) {
                f.conversion_char = bad_conversion;
            }
            break;

        case L'n':
            if (f.argument_options == long_double_argument) {
                f.argument_options = long_long_argument;
            }
            break;

        default:
            f.conversion_char = bad_conversion;
            break;
    }

    *format = f;

    return ((const wchar_t *) s + 1);
}

wchar_t *long2str(long num, wchar_t *buff, print_format format) {
    unsigned long unsigned_num, base;
    wchar_t *p;
    int n, digits;
    int minus    = 0;
    unsigned_num = num;
    minus        = 0;

    p      = buff;
    *--p   = 0;
    digits = 0;

    if (!num && !format.precision && !(format.alternate_form && format.conversion_char == L'o')) {
        return p;
    }

    switch (format.conversion_char) {
        case L'd':
        case L'i':
            base = 10;

            if (num < 0) {
                if (num != 0x80000000) {
                    unsigned_num = -num;
                }

                minus = 1;
            }
            break;

        case L'o':
            base                = 8;
            format.sign_options = only_minus;
            break;

        case L'u':
            base                = 10;
            format.sign_options = only_minus;
            break;

        case L'x':
        case L'X':
            base                = 16;
            format.sign_options = only_minus;
            break;
    }

    do {
        n = unsigned_num % base;
        unsigned_num /= base;

        if (n < 10) {
            n += L'0';
        } else {
            n -= 10;

            if (format.conversion_char == L'x') {
                n += L'a';
            } else {
                n += L'A';
            }
        }

        *--p = n;
        ++digits;
    } while (unsigned_num != 0);

    if (base == 8 && format.alternate_form && *p != L'0') {
        *--p = L'0';
        ++digits;
    }

    if (format.justification_options == zero_fill) {
        format.precision = format.field_width;

        if (minus || format.sign_options != only_minus) {
            --format.precision;
        }

        if (base == 16 && format.alternate_form) {
            format.precision -= 2;
        }
    }

    if (buff - p + format.precision > conversion_max) {
        return 0;
    }

    while (digits < format.precision) {
        *--p = L'0';
        ++digits;
    }

    if (base == 16 && format.alternate_form) {
        *--p = format.conversion_char;
        *--p = L'0';
    }

    if (minus) {
        *--p = L'-';
    } else if (format.sign_options == sign_always) {
        *--p = L'+';
    } else if (format.sign_options == space_holder) {
        *--p = L' ';
    }

    return p;
}

wchar_t *longlong2str(long long num, wchar_t *pBuf, print_format fmt) {
    unsigned long long unsigned_num, base;
    wchar_t *p;
    int n, digits;
    int minus    = 0;
    unsigned_num = num;
    minus        = 0;
    p            = pBuf;
    *--p         = 0;
    digits       = 0;

    if (!num && !fmt.precision && !(fmt.alternate_form && fmt.conversion_char == L'o')) {
        return p;
    }

    switch (fmt.conversion_char) {
        case L'd':
        case L'i':
            base = 10;

            if (num < 0) {
                if (num != 0x8000000000000000) {
                    unsigned_num = -num;
                }

                minus = 1;
            }
            break;
        case L'o':
            base             = 8;
            fmt.sign_options = only_minus;
            break;
        case L'u':
            base             = 10;
            fmt.sign_options = only_minus;
            break;
        case L'x':
        case L'X':
            base             = 16;
            fmt.sign_options = only_minus;
            break;
    }

    do {
        n = unsigned_num % base;
        unsigned_num /= base;

        if (n < 10) {
            n += L'0';
        } else {
            n -= 10;
            if (fmt.conversion_char == L'x') {
                n += L'a';
            } else {
                n += L'A';
            }
        }

        *--p = n;
        ++digits;
    } while (unsigned_num != 0);

    if (base == 8 && fmt.alternate_form && *p != L'0') {
        *--p = L'0';
        ++digits;
    }

    if (fmt.justification_options == zero_fill) {
        fmt.precision = fmt.field_width;

        if (minus || fmt.sign_options != only_minus) {
            --fmt.precision;
        }

        if (base == 16 && fmt.alternate_form) {
            fmt.precision -= 2;
        }
    }

    if (pBuf - p + fmt.precision > conversion_max) {
        return 0;
    }

    while (digits < fmt.precision) {
        *--p = L'0';
        ++digits;
    }

    if (base == 16 && fmt.alternate_form) {
        *--p = fmt.conversion_char;
        *--p = L'0';
    }

    if (minus) {
        *--p = L'-';
    } else if (fmt.sign_options == sign_always) {
        *--p = L'+';
    } else if (fmt.sign_options == space_holder) {
        *--p = L' ';
    }

    return p;
}

static wchar_t *double2hex(long double num, wchar_t *buff, print_format format) {
    wchar_t *p;
    unsigned char *q;
    unsigned char working_byte;
    long double ld;
    int expbits, expmask;
    unsigned snum;
    long exp;
    print_format exp_format;
    int hex_precision;
    int mantissa_bit;
    decform form;
    decimal dec;
    int radix_marker;

    radix_marker = '.';
    p            = buff;
    ld           = num;

    if (format.precision > 509) {
        return 0;
    }

    form.style  = (char) 0;
    form.digits = 0x20;
    __num2dec(&form, num, &dec);

    switch (*dec.sig.text) {
        case '0':
            dec.exp = 0;
            if (dec.sgn) {
                p = buff - 5;
                if (format.conversion_char == L'A') {
                    wcscpy(p, L"-0X0");
                } else {
                    wcscpy(p, L"-0x0");
                }
            } else {
                p = buff - 4;
                if (format.conversion_char == L'A') {
                    wcscpy(p, L"0X0");
                } else {
                    wcscpy(p, L"0x0");
                }
            }

            return p;
        case 'I':
            if (dec.sgn) {
                p = buff - 5;
                if (format.conversion_char == L'A') {
                    wcscpy(p, L"-INF");
                } else {
                    wcscpy(p, L"-inf");
                }
            } else {
                p = buff - 4;
                if (format.conversion_char == L'A') {
                    wcscpy(p, L"INF");
                } else {
                    wcscpy(p, L"inf");
                }
            }

            return p;
        case 'N':
            if (dec.sgn) {
                p = buff - 5;
                if (format.conversion_char == L'A') {
                    wcscpy(p, L"-NAN");
                } else {
                    wcscpy(p, L"-nan");
                }
            } else {
                p = buff - 4;
                if (format.conversion_char == L'A') {
                    wcscpy(p, L"NAN");
                } else {
                    wcscpy(p, L"nan");
                }
            }

            return p;
    }

    exp_format.justification_options = right_justification;
    exp_format.sign_options          = sign_always;
    exp_format.precision_specified   = 0;
    exp_format.alternate_form        = 0;
    exp_format.argument_options      = normal_argument;
    exp_format.field_width           = 0;
    exp_format.precision             = 1;
    exp_format.conversion_char       = L'd';

    int i;
    unsigned char *nump = (unsigned char *) &num;
    for (i = 0; i < TARGET_FLOAT_BYTES; i++) {
        unsigned char x                  = nump[i];
        nump[i]                          = nump[TARGET_FLOAT_BYTES - i + 3];
        nump[TARGET_FLOAT_BYTES - i + 3] = x;
    }

    expbits = TARGET_FLOAT_EXP_BITS;
    expmask = 0x7FF;

    snum = ((unsigned char *) &num)[0] << 25;
    if (TARGET_FLOAT_EXP_BITS > 7) {
        snum |= ((unsigned char *) &num)[1] << 17;
    }
    if (TARGET_FLOAT_EXP_BITS > 15) {
        snum |= ((unsigned char *) &num)[2] << 9;
    }
    if (TARGET_FLOAT_EXP_BITS > 23) {
        snum |= ((unsigned char *) &num)[3] << 1;
    }

    snum = (snum >> (29 - expbits)) & expmask;

    if (snum != 0) {
        exp = snum - 0x3FF;
    } else {
        exp = 0;
    }

    p = long2str(exp, buff, exp_format);
    if (format.conversion_char == L'a') {
        *--p = L'p';
    } else {
        *--p = L'P';
    }
    q = (unsigned char *) &num;

    if (TARGET_FLOAT_IMPLICIT_J_BIT) {
        mantissa_bit = (4 + expbits + format.precision * 4) - 1;
    } else {
        mantissa_bit = (1 + expbits + format.precision * 4) - 4;
    }

    for (hex_precision = format.precision; hex_precision >= 1; hex_precision--) {
        if (mantissa_bit < 64) {
            int mantissa_byte;

            mantissa_byte = mantissa_bit >> 3;
            working_byte  = (*(q + mantissa_byte)) >> (7 - (mantissa_bit & 7));

            if ((mantissa_bit & ~7) != ((mantissa_bit - 4) & ~7)) {
                working_byte |= ((*(q + (mantissa_byte - 1))) << 8) >> (7 - ((mantissa_bit) & 7));
            }

            if (!TARGET_FLOAT_IMPLICIT_J_BIT) {
                if (mantissa_bit == 1 + expbits) {
                    *--p = radix_marker;
                    working_byte &= 0x1;
                }
            }

            if ((working_byte &= 0xF) < 10) {
                working_byte += L'0';
            } else if (format.conversion_char == L'a') {
                working_byte += L'a' - 10;
            } else {
                working_byte += L'A' - 10;
            }
        } else {
            working_byte = L'0';
        }

        *--p = working_byte;
        mantissa_bit -= 4;
    }

    if (TARGET_FLOAT_IMPLICIT_J_BIT) {
        if (format.precision || format.alternate_form) {
            *--p = radix_marker;
        }

        if (fabsl(ld) != 0.0) {
            *--p = '1';
        } else {
            *--p = '0';
        }
    }

    if (format.conversion_char == L'a') {
        *--p = L'x';
    } else {
        *--p = L'X';
    }

    *--p = L'0';

    if (dec.sgn) {
        *--p = L'-';
    } else if (format.sign_options == sign_always) {
        *--p = L'+';
    } else if (format.sign_options == space_holder) {
        *--p = L' ';
    }

    return p;
}

void round_decimal(decimal *dec, int new_length) {
    unsigned char c;
    unsigned char *p;
    int carry;

    if (new_length < 0) {
    return_zero:
        dec->exp        = 0;
        dec->sig.length = 1;
        *dec->sig.text  = L'0';
        return;
    }

    if (new_length >= dec->sig.length) {
        return;
    }

    p = (unsigned char *) dec->sig.text + new_length + 1;
    c = *--p - L'0';

    if (c == 5) {
        unsigned char *q = &((unsigned char *) dec->sig.text)[dec->sig.length];

        while (--q > p && *q == L'0')
            ;
        carry = (q == p) ? p[-1] & 1 : 1;
    } else {
        carry = (c > 5);
    }

    while (new_length != 0) {
        c = *--p - L'0' + carry;

        if ((carry = (c > 9)) != 0 || c == 0) {
            --new_length;
        } else {
            *p = c + L'0';
            break;
        }
    }

    if (carry != 0) {
        dec->exp += 1;
        dec->sig.length = 1;
        *dec->sig.text  = L'1';
        return;
    } else if (new_length == 0) {
        goto return_zero;
    }

    dec->sig.length = new_length;
}

wchar_t *float2str(long double num, wchar_t *wbuff, print_format format) {
    decimal dec;
    decform form;
    wchar_t *pw;
    unsigned char *p;
    unsigned char buff[512];
    unsigned char *q;
    int n, digits, sign;
    int int_digits, frac_digits;
    int radix_marker;

    radix_marker = '.';

    if (format.precision > conversion_max) {
        return 0;
    }

    form.style  = 0;
    form.digits = 0x20;
    __num2dec(&form, num, &dec);
    p = (unsigned char *) dec.sig.text + dec.sig.length;

    while (dec.sig.length > 1 && *--p == L'0') {
        --dec.sig.length;
        ++dec.exp;
    }

    switch (*dec.sig.text) {
        case L'0':
            dec.exp = 0;
            break;
        case L'I':
            if (num < 0) {
                pw = wbuff - 5;

                if (isupper(format.conversion_char)) {
                    wcscpy(pw, L"-INF");
                } else {
                    wcscpy(pw, L"-inf");
                }
            } else {
                pw = wbuff - 4;
                if (isupper(format.conversion_char)) {
                    wcscpy(pw, L"INF");
                } else {
                    wcscpy(pw, L"inf");
                }
            }

            return pw;

        case L'N':
            if (dec.sgn) {
                pw = wbuff - 5;

                if (isupper(format.conversion_char)) {
                    wcscpy(pw, L"-NAN");
                } else {
                    wcscpy(pw, L"-nan");
                }
            } else {
                pw = wbuff - 4;
                if (isupper(format.conversion_char)) {
                    wcscpy(pw, L"NAN");
                } else {
                    wcscpy(pw, L"nan");
                }
            }

            return pw;
    }

    dec.exp += dec.sig.length - 1;
    p    = buff + 512;
    *--p = 0;

    switch (format.conversion_char) {
        case L'g':
        case L'G':

            if (dec.sig.length > format.precision) {
                round_decimal(&dec, format.precision);
            }

            if (dec.exp < -4 || dec.exp >= format.precision) {
                if (format.alternate_form) {
                    --format.precision;
                } else {
                    format.precision = dec.sig.length - 1;
                }

                if (format.conversion_char == L'g') {
                    format.conversion_char = L'e';
                } else {
                    format.conversion_char = L'E';
                }

                goto e_format;
            }

            if (format.alternate_form) {
                format.precision -= dec.exp + 1;
            } else {
                if ((format.precision = dec.sig.length - (dec.exp + 1)) < 0) {
                    format.precision = 0;
                }
            }

            goto f_format;

        case L'e':
        case L'E':
        e_format:

            if (dec.sig.length > format.precision + 1) {
                round_decimal(&dec, format.precision + 1);
            }

            n    = dec.exp;
            sign = L'+';

            if (n < 0) {
                n    = -n;
                sign = L'-';
            }

            for (digits = 0; n || digits < 2; ++digits) {
                *--p = n % 10 + '0';
                n /= 10;
            }

            *--p = sign;
            *--p = format.conversion_char;

            if (buff - p + format.precision > conversion_max) {
                return 0;
            }

            if (dec.sig.length < format.precision + 1) {
                for (n = format.precision + 1 - dec.sig.length + 1; --n;) {
                    *--p = L'0';
                }
            }

            for (n = dec.sig.length, q = (unsigned char *) dec.sig.text + dec.sig.length; --n;) {
                *--p = *--q;
            }

            if (format.precision || format.alternate_form) {
                *--p = radix_marker;
            }

            *--p = *dec.sig.text;

            if (dec.sgn) {
                *--p = L'-';
            } else if (format.sign_options == sign_always) {
                *--p = L'+';
            } else if (format.sign_options == space_holder) {
                *--p = L' ';
            }

            break;

        case L'f':
        case L'F':
        f_format:

            if ((frac_digits = -dec.exp + dec.sig.length - 1) < 0) {
                frac_digits = 0;
            }

            if (frac_digits > format.precision) {
                round_decimal(&dec, dec.sig.length - (frac_digits - format.precision));

                if ((frac_digits = -dec.exp + dec.sig.length - 1) < 0) {
                    frac_digits = 0;
                }
            }

            if ((int_digits = dec.exp + 1) < 0) {
                int_digits = 0;
            }

            if (int_digits + frac_digits > conversion_max) {
                return 0;
            }

            q = (unsigned char *) dec.sig.text + dec.sig.length;

            for (digits = 0; digits < (format.precision - frac_digits); ++digits) {
                *--p = L'0';
            }

            for (digits = 0; digits < frac_digits && digits < dec.sig.length; ++digits) {
                *--p = *--q;
            }

            for (; digits < frac_digits; ++digits) {
                *--p = L'0';
            }

            if (format.precision || format.alternate_form) {
                *--p = radix_marker;
            }

            if (int_digits) {
                for (digits = 0; digits < int_digits - dec.sig.length; ++digits) {
                    *--p = L'0';
                }

                for (; digits < int_digits; ++digits) {
                    *--p = *--q;
                }
            } else {
                *--p = L'0';
            }

            if (dec.sgn) {
                *--p = L'-';
            } else if (format.sign_options == sign_always) {
                *--p = L'+';
            } else if (format.sign_options == space_holder) {
                *--p = L' ';
            }

            break;
    }

    pw = wbuff - strlen(p) - 1;
    mbstowcs(pw, p, strlen(p));
    return pw;
}

// non-matching: https://decomp.me/scratch/bV2T9
int __wpformatter(void *(*WriteProc)(void *, const wchar_t *, size_t), void *WriteProcArg, const wchar_t *format_str,
                  va_list arg, bool is_secure) {
    int num_chars, chars_written, field_width;
    const wchar_t *format_ptr;
    const wchar_t *curr_format;
    print_format format;
    long long_num;
    long long long_long_num;
    long double long_double_num;
    wchar_t buff[512];
    wchar_t *buff_ptr;
    wchar_t *string_end;
    char *cstring_end;
    wchar_t fill_char = ' ';
    char *strptr;

    format_ptr    = format_str;
    chars_written = 0;

    while (*format_ptr) {
        if (!(curr_format = wcschr(format_ptr, L'%'))) {
            num_chars = wcslen(format_ptr);
            chars_written += num_chars;

            if (num_chars && !(*WriteProc)(WriteProcArg, format_ptr, num_chars)) {
                return -1;
            }

            break;
        }

        num_chars = curr_format - format_ptr;
        chars_written += num_chars;

        if (num_chars && !(*WriteProc)(WriteProcArg, format_ptr, num_chars)) {
            return -1;
        }

        format_ptr = curr_format;
        format_ptr = parse_format(format_ptr, &arg, &format);

        switch (format.conversion_char) {
            case L'd':
            case L'i':
                if (format.argument_options == long_argument) {
                    long_num = va_arg(arg, long);
                } else if (format.argument_options == long_long_argument) {
                    long_long_num = va_arg(arg, long long);
                } else if (format.argument_options == intmax_argument) {
                    long_long_num = va_arg(arg, intmax_t);
                } else if (format.argument_options == size_t_argument) {
                    long_num = va_arg(arg, size_t);
                } else if (format.argument_options == ptrdiff_argument) {
                    long_num = va_arg(arg, ptrdiff_t);
                } else {
                    long_num = va_arg(arg, int);
                }

                if (format.argument_options == short_argument) {
                    long_num = (short) long_num;
                }

                if ((format.argument_options == long_long_argument) || (format.argument_options == intmax_argument)) {
                    if (!(buff_ptr = longlong2str(long_long_num, buff + 512, format))) {
                        goto conversion_error;
                    }
                } else {
                    if (!(buff_ptr = long2str(long_num, buff + 512, format))) {
                        goto conversion_error;
                    }
                }

                num_chars = buff + 512 - 1 - buff_ptr;
                break;

            case L'o':
            case L'u':
            case L'x':
            case L'X':
                if (format.argument_options == long_argument) {
                    long_num = va_arg(arg, unsigned long);
                } else if (format.argument_options == long_long_argument) {
                    long_long_num = va_arg(arg, long long);
                } else if (format.argument_options == intmax_argument) {
                    long_long_num = va_arg(arg, intmax_t);
                } else if (format.argument_options == size_t_argument) {
                    long_num = va_arg(arg, size_t);
                } else if (format.argument_options == ptrdiff_argument) {
                    long_num = va_arg(arg, ptrdiff_t);
                } else {
                    long_num = va_arg(arg, unsigned int);
                }

                if (format.argument_options == short_argument) {
                    long_num = (unsigned short) long_num;
                }

                if ((format.argument_options == long_long_argument) || (format.argument_options == intmax_argument)) {
                    if (!(buff_ptr = longlong2str(long_long_num, buff + 512, format))) {
                        goto conversion_error;
                    }
                } else {
                    if (!(buff_ptr = long2str(long_num, buff + 512, format))) {
                        goto conversion_error;
                    }
                }

                num_chars = buff + 512 - 1 - buff_ptr;
                break;

            case L'f':
            case L'F':
            case L'e':
            case L'E':
            case L'g':
            case L'G':
                if (format.argument_options == long_double_argument) {
                    long_double_num = va_arg(arg, long double);
                } else {
                    long_double_num = va_arg(arg, double);
                }

                if (!(buff_ptr = float2str(long_double_num, buff + 512, format))) {
                    goto conversion_error;
                }

                num_chars = buff + 512 - 1 - buff_ptr;
                break;

            case L'a':
            case L'A':
                if (format.argument_options == long_double_argument) {
                    long_double_num = va_arg(arg, long double);
                } else {
                    long_double_num = va_arg(arg, double);
                }

                if (!(buff_ptr = double2hex(long_double_num, buff + 512, format))) {
                    goto conversion_error;
                }

                num_chars = buff + 512 - 1 - buff_ptr;
                break;

            case L's':
                if (format.argument_options == wchar_argument) {
                    buff_ptr = va_arg(arg, wchar_t *);

                    if (is_secure && buff_ptr == NULL) {
                        __msl_runtime_constraint_violation_s(NULL, NULL, -1);
                        return -1;
                    }

                    if (buff_ptr == NULL) {
                        buff_ptr = L"";
                    }

                    if (format.alternate_form) {
                        num_chars = (unsigned char) *buff_ptr++;

                        if (format.precision_specified && num_chars > format.precision) {
                            num_chars = format.precision;
                        }
                    } else if (format.precision_specified) {
                        num_chars = format.precision;

                        if ((string_end = (wchar_t *) wmemchr(buff_ptr, 0, num_chars)) != 0) {
                            num_chars = string_end - buff_ptr;
                        }
                    } else {
                        num_chars = wcslen(buff_ptr);
                    }
                } else {
                    strptr = va_arg(arg, char *);

                    if (is_secure && strptr == NULL) {
                        __msl_runtime_constraint_violation_s(0, 0, -1);
                        return -1;
                    }

                    if (strptr == NULL) {
                        strptr = "";
                    }

                    if (format.alternate_form) {
                        num_chars = (unsigned char) *buff_ptr++;

                        if (format.precision_specified && num_chars > format.precision) {
                            num_chars = format.precision;
                        }
                    } else if (format.precision_specified) {
                        num_chars = format.precision;

                        if ((cstring_end = (char *) memchr(strptr, 0, num_chars)) != 0) {
                            num_chars = cstring_end - strptr;
                        }
                    } else {
                        num_chars = strlen(strptr);
                    }
                    if ((num_chars = mbstowcs(buff, strptr, num_chars)) < 0) {
                        goto conversion_error;
                    }

                    buff_ptr = &buff[0];
                }

                break;

            case L'n':
                if (is_secure) {
                    __msl_runtime_constraint_violation_s(0, 0, -1);
                    return -1;
                }

                buff_ptr = va_arg(arg, wchar_t *);

                switch (format.argument_options) {
                    case normal_argument:
                        *(int *) buff_ptr = chars_written;
                        break;
                    case short_argument:
                        *(short *) buff_ptr = chars_written;
                        break;
                    case long_argument:
                        *(long *) buff_ptr = chars_written;
                        break;
                    case intmax_argument:
                        *(intmax_t *) buff_ptr = chars_written;
                        break;
                    case size_t_argument:
                        *(size_t *) buff_ptr = chars_written;
                        break;
                    case ptrdiff_argument:
                        *(ptrdiff_t *) buff_ptr = chars_written;
                        break;
                    case long_long_argument:
                        *(long long *) buff_ptr = chars_written;
                        break;
                }

                continue;

            case L'c':
                buff_ptr = buff;
                if (format.argument_options == wchar_argument) {
                    *buff_ptr = va_arg(arg, int);
                    num_chars = 1;
                } else {
                    char chint = va_arg(arg, int);
                    num_chars  = mbtowc(buff, &chint, 1);
                }

                break;

            case L'%':
                buff_ptr  = buff;
                *buff_ptr = L'%';
                num_chars = 1;
                break;

            case L'p':
            case 0xFFFF:
            default:
            conversion_error:
                num_chars = wcslen(curr_format);
                chars_written += num_chars;

                if (num_chars && !(*WriteProc)(WriteProcArg, curr_format, num_chars)) {
                    return -1;
                }

                return chars_written;
        }

        field_width = num_chars;

        if (format.justification_options != left_justification) {
            fill_char = (format.justification_options == zero_fill) ? '0' : ' ';

            if (((*buff_ptr == L'+') || (*buff_ptr == L'-') || (*buff_ptr == L' ')) && (fill_char == '0')) {
                if ((*WriteProc)(WriteProcArg, buff_ptr, 1) == 0) {
                    return -1;
                }

                ++buff_ptr;
                num_chars--;
            }

            if ((format.justification_options == zero_fill) &&
                ((format.conversion_char == L'a') || (format.conversion_char == L'A'))) {
                if (num_chars < 2) {
                    return -1;
                }

                if ((*WriteProc)(WriteProcArg, buff_ptr, 2) == 0) {
                    return -1;
                }

                num_chars -= 2;
                buff_ptr += 2;
            }

            while (field_width < format.field_width) {
                if ((*WriteProc)(WriteProcArg, &fill_char, 1) == 0) {
                    return -1;
                }

                ++field_width;
            }
        }

        if (num_chars && !(*WriteProc)(WriteProcArg, buff_ptr, num_chars)) {
            return -1;
        }

        if (format.justification_options == left_justification) {
            while (field_width < format.field_width) {
                wchar_t blank = L' ';

                if ((*WriteProc)(WriteProcArg, &blank, 1) == 0) {
                    return -1;
                }

                ++field_width;
            }
        }

        chars_written += field_width;
    }

    return chars_written;
}

typedef struct {
    wchar_t *wCharStr;
    size_t MaxCharCount;
    size_t CharsWritten;
} __wOutStrCtrl;

void *__wStringWrite(void *wosc, const wchar_t *buffer, size_t numchars) {
    size_t tobewritten;
    void *res;
    __wOutStrCtrl *wOscp = (__wOutStrCtrl *) wosc;
    tobewritten =
        ((wOscp->CharsWritten + numchars) <= wOscp->MaxCharCount) ? numchars : wOscp->MaxCharCount - wOscp->CharsWritten;
    res = (void *) wmemcpy(wOscp->wCharStr + wOscp->CharsWritten, buffer, tobewritten);
    wOscp->CharsWritten += tobewritten;
    return res;
}

int swprintf(wchar_t *s, size_t n, const wchar_t *format, ...) {
    va_list args;
    va_start(args, format);
    return vswprintf(s, n, format, args);
}

int vswprintf(wchar_t *s, size_t n, const wchar_t *format, va_list arg) {
    int count;
    __wOutStrCtrl wosc;
    wosc.wCharStr     = s;
    wosc.MaxCharCount = n;
    wosc.CharsWritten = 0;

    count = __wpformatter(&__wStringWrite, &wosc, format, arg, 0);

    if (count >= 0) {
        if (count < n) {
            s[count] = 0;
        } else {
            *(s + n - 1) = 0;
            count        = -1;
        }
    }

    return count;
}
