/*
 * _scanf_core — ядро scanf-семейства (см. _scanf.h).
 *
 * Поддержано: %d (знак ±), %u, %x, %o, %c (ширина = число символов,
 * по умолчанию 1, пробелы НЕ скипаются), %s (скип пробелов, до
 * пробела/ширины, NUL добавляется), модификатор l (long), ширина,
 * '*' — подавление присваивания, %% — литерал.  Пробельный символ в
 * формате съедает любую цепочку пробелов ввода; прочие символы
 * формата должны совпасть с вводом (иначе стоп).
 *
 * Возврат: число присвоенных полей; EOF, если ввод кончился до
 * первого присваивания/совпадения (ANSI-семантика).
 */

#include <ctype.h>
#include "_scanf.h"

/* Цифра c в базе base или -1. */
static signed char digit_val(int c, unsigned char base)
{
    unsigned char v;
    if (c >= '0' && c <= '9')      v = (unsigned char)(c - '0');
    else if (c >= 'a' && c <= 'f') v = (unsigned char)(c - 'a' + 10);
    else if (c >= 'A' && c <= 'F') v = (unsigned char)(c - 'A' + 10);
    else return -1;
    return v < base ? (signed char)v : -1;
}

/* Пропустить пробелы ввода; вернуть первый непробельный символ
 * (или EOF), НЕ возвращая его в источник. */
static int skip_ws(_sc_get get, void *ctx)
{
    int c;
    do { c = get(ctx); } while (c != -1 && isspace(c));
    return c;
}

int _scanf_core(_sc_get get, _sc_unget unget, void *ctx,
                const char *fmt, va_list ap)
{
    int assigned = 0;
    int touched  = 0;              /* было ли хоть одно чтение ввода */
    int c;

    for (; *fmt; fmt++) {
        if (isspace((unsigned char)*fmt)) {
            c = skip_ws(get, ctx);
            if (c != -1) unget(ctx, c);
            continue;
        }
        if (*fmt != '%') {
            /* Литерал формата должен совпасть с вводом. */
            c = get(ctx);
            touched = 1;
            if (c != (unsigned char)*fmt) {
                if (c != -1) { unget(ctx, c); goto out; }
                goto out_eof;
            }
            continue;
        }

        /* ---- %-конверсия ---- */
        fmt++;
        if (*fmt == '%') {
            c = get(ctx);
            touched = 1;
            if (c != '%') {
                if (c != -1) { unget(ctx, c); goto out; }
                goto out_eof;
            }
            continue;
        }

        unsigned char suppress = 0;
        if (*fmt == '*') { suppress = 1; fmt++; }

        unsigned width = 0;
        while (*fmt >= '0' && *fmt <= '9') {
            width = width * 10 + (unsigned)(*fmt - '0');
            fmt++;
        }

        unsigned char lng = 0;
        if (*fmt == 'l') { lng = 1; fmt++; }

        switch (*fmt) {

        case 'c': {
            unsigned w = width ? width : 1;
            char *dst = suppress ? 0 : va_arg(ap, char *);
            while (w--) {
                c = get(ctx);
                touched = 1;
                if (c == -1) {
                    if (!assigned) goto out_eof;
                    goto out;
                }
                if (dst) *dst++ = (char)c;
            }
            if (!suppress) assigned++;
            break;
        }

        case 's': {
            char *dst = suppress ? 0 : va_arg(ap, char *);
            unsigned w = width ? width : 0xFFFF;
            c = skip_ws(get, ctx);
            touched = 1;
            if (c == -1) {
                if (!assigned) goto out_eof;
                goto out;
            }
            unsigned n = 0;
            while (c != -1 && !isspace(c) && n < w) {
                if (dst) dst[n] = (char)c;
                n++;
                c = get(ctx);
            }
            if (c != -1) unget(ctx, c);
            if (n == 0) goto out;
            if (dst) { dst[n] = '\0'; assigned++; }
            break;
        }

        case 'd': case 'u': case 'x': case 'o': {
            unsigned char base = 10;
            if (*fmt == 'x') base = 16;
            else if (*fmt == 'o') base = 8;

            unsigned w = width ? width : 0xFFFF;
            unsigned char neg = 0;
            unsigned long val = 0;
            unsigned ndig = 0;

            c = skip_ws(get, ctx);
            touched = 1;
            if (c == -1) {
                if (!assigned) goto out_eof;
                goto out;
            }
            if (*fmt == 'd' && (c == '-' || c == '+') && w) {
                neg = (c == '-');
                w--;
                c = get(ctx);
            }
            while (c != -1 && w) {
                signed char d = digit_val(c, base);
                if (d < 0) break;
                val = val * base + (unsigned char)d;
                ndig++;
                w--;
                c = get(ctx);
            }
            if (c != -1) unget(ctx, c);
            if (ndig == 0) goto out;      /* цифр не было — стоп */
            if (neg) val = (unsigned long)(-(long)val);
            if (!suppress) {
                if (lng) *va_arg(ap, long *) = (long)val;
                else     *va_arg(ap, int  *) = (int)val;
                assigned++;
            }
            break;
        }

        default:
            /* Незнакомая конверсия — прекратить разбор. */
            goto out;
        }
    }

out:
    return assigned;
out_eof:
    /* Ввод кончился до первого присвоенного поля. */
    return (assigned == 0 && touched) ? -1 : assigned;
}
