/*
 * fgetc — прочитать один байт из потока (буферизованно, вариант B+).
 *
 * Горячий путь («в readahead-буфере есть байт») написан на asm —
 * SDCC на эту цепочку проверок генерит ~90 инструкций с IX-фреймом,
 * asm укладывается в ~30 без фрейма (замер fbench: ~176 мкс/байт
 * у C-версии).  Всё остальное — медленный C-путь fgetc_slow:
 * hold-байт от ungetc, консольный getchar(), сброс накопленной
 * записи, дозаполнение буфера read(fd, buf, BUFSIZ), fallback без
 * буфера.  Возвращает байт, EOF на конце файла (_F_EOF) или при
 * ошибке (_F_ERROR).
 *
 * Раскладка FILE (см. stdio.h, байтовые смещения):
 *   +0 fd, +2 flags, +3 buf, +5 curp, +7 level, +9 hold
 */

#include <unistd.h>
#include <errno.h>
#include "_file.h"

/* Медленный путь; зовётся из asm-обёртки (метка _fgetc_slow видна в
 * пределах этого модуля). */
static int fgetc_slow(FILE *fp)
{
#ifndef LIBC_NOCHECK
    if (!fp) { errno = EBADF; return EOF; }
#endif
    if (fp->hold != EOF) {
        int c = fp->hold;
        fp->hold = EOF;
        return c;
    }
    if (fp->flags & _F_CONIN) {
        return getchar();
    }
    if (!(fp->flags & _F_READ)) { errno = EBADF; return EOF; }

    if (fp->flags & _F_DIROUT) {
        if (_file_sync(fp)) return EOF;      /* сброс накопленной записи */
    }
    if (fp->level == 0) {
        uint8_t *b = _file_buf(fp);
        if (!b) {
            /* Небуферизованный fallback. */
            uint8_t ch;
            int r = read(fp->fd, &ch, 1);
            if (r == 0) { fp->flags |= _F_EOF;   return EOF; }
            if (r < 0)  { fp->flags |= _F_ERROR; return EOF; }
            return (int)ch;
        }
        int r = read(fp->fd, b, BUFSIZ);
        if (r == 0) { fp->flags |= _F_EOF;   return EOF; }
        if (r < 0)  { fp->flags |= _F_ERROR; return EOF; }
        fp->level = (uint16_t)r;
        fp->curp  = b;
    }
    fp->level--;
    return (int)*fp->curp++;
}

int fgetc(FILE *fp) __naked
{
    (void)fp;
    (void)fgetc_slow;        /* используется из asm */
    __asm
        ;; HL = fp.
#ifndef LIBC_NOCHECK
        ld    a, h
        or    a, l
        jr    Z, _fg_slow           ; NULL — C-путь выставит errno
#endif
        push  hl
        pop   iy                    ; IY = fp (IY — scratch в __sdcccall(1))

        ld    a, 7 (iy)             ; level != 0 ?
        or    a, 8 (iy)
        jr    Z, _fg_slow           ; буфер пуст/не наш режим
        ld    a, 2 (iy)             ; flags
        and   a, #0x08              ; _F_DIROUT — буфер копит запись?
        jr    NZ, _fg_slow
        ld    a, 9 (iy)             ; hold == EOF (0xFFFF)?
        and   a, 10 (iy)
        inc   a
        jr    NZ, _fg_slow          ; есть отложенный ungetc-байт

        ;; --- горячий путь: level--, вернуть *curp++ ---
        ld    a, 7 (iy)
        sub   a, #1
        ld    7 (iy), a
        jr    NC, _fg_nb
        dec   8 (iy)
    _fg_nb:
        ld    l, 5 (iy)             ; HL = curp
        ld    h, 6 (iy)
        ld    e, (hl)               ; E = байт
        ld    d, #0                 ; zero-extend в int
        inc   hl
        ld    5 (iy), l             ; curp++
        ld    6 (iy), h
        ret

    _fg_slow:
        jp    _fgetc_slow           ; HL = fp уже на месте
    __endasm;
}
