;
; Ullrich von Bassewitz, 2002-11-22, 2004-05-14
;
; size_t __fastcall__ fread (void* buf, size_t size, size_t count, FILE* file);
; /* Read from a file */
;

        .export         _fread

        .import         _read
        .import         pusha0, pushax
        .import         incsp4, incsp6
        .import         ldaxysp, ldax0sp
        .import         pushwysp
        .import         tosumulax, tosudivax

        .importzp       ptr1, sp

        .include        "errno.inc"
        .include        "_file.inc"

        .macpack        generic

; ------------------------------------------------------------------------
; Code

.segment "LIBCODE"
.proc   _fread

; We will place a pointer to the file descriptor into the register bank

        .import regbank: zp
        file    = regbank

; Save two bytes from the register bank

        ldy     file+0
        sty     save
        ldy     file+1
        sty     save+1

; Save the file pointer into the register bank

    	sta  	file
    	stx  	file+1

; Check if the file is open

    	ldy  	#_FILE::f_flags
    	lda  	(file),y
    	and  	#_FOPEN		      	; Is the file open?
       	beq    	@L1  			; Branch if no

; Check if the stream is in an error state

        lda  	(file),y		; get file->f_flags again
	and  	#_FERROR
	beq     @L2

; File not open or in error state

@L1:    lda    	#EINVAL
        jsr     __seterrno              ; Set __errno, return zero in A
        tax                             ; a/x = 0
        jmp     @L99                    ; Bail out

; Remember if we have a pushed back character and reset the flag.

@L2:    tax                             ; X = 0
        lda     (file),y
        and     #_FPUSHBACK
        beq     @L3
        lda     (file),y
        and     #<~_FPUSHBACK
        sta     (file),y                ; file->f_flags &= ~_FPUSHBACK;
        inx                             ; X = 1
@L3:    stx     pb

; Build the stackframe for read()

        ldy     #_FILE::f_fd
        lda     (file),y
        jsr     pusha0                  ; file->f_fd

        ldy     #9
        jsr     pushwysp                ; buf

; Stack is now: buf/size/count/file->fd/buf
; Calculate the number of bytes to read: count * size

        ldy     #7
        jsr     pushwysp                ; count
        ldy     #9
        jsr     ldaxysp                 ; Get size
        jsr     tosumulax               ; count * size -> a/x

; Check if count is zero.

        cmp     #0
        bne     @L4
        cpx     #0
        bne     @L4

; Count is zero, drop the stack frame just built and return count

        jsr     incsp4                  ; Drop file->fd/buf
        jsr     ldax0sp                 ; Get count
        jmp     @L99                    ; Bail out

; Check if we have a buffered char from ungetc

@L4:    ldy     pb
        beq     @L6

; We have a buffered char from ungetc. Save the low byte from count

        pha

; Copy the buffer pointer into ptr1, and increment the pointer value passed 
; to read() by one, so read() starts to store data at buf+1.

        ldy     #0
        lda     (sp),y
        sta     ptr1
        add     #1
        sta     (sp),y
        iny
        lda     (sp),y
        sta     ptr1+1
        adc     #0
        sta     (sp),y                  ; ptr1 = buf++;

; Get the buffered character and place it as first character into the read
; buffer.

        ldy     #_FILE::f_pushback
        lda     (file),y
        ldy     #0
        sta     (ptr1),y                ; *buf = file->f_pushback;

; Restore the low byte of count and decrement count by one. This may result
; in count being zero, so check for that.

        pla
        sub     #1
        bcs     @L5
        dex
@L5:    cmp     #0
        bne     @L6
        cpx     #0
        beq     @L8

; Call read(). This will leave the original 3 params on the stack

@L6:    jsr     _read

; Check for errors in read

        cpx     #$FF
        bne     @L8
        cmp     #$FF
        bne     @L8

; Error in read. Set the stream error flag and bail out. _oserror and/or
; errno are already set by read(). On entry to label @L7, X must be zero.

        inx                             ; X = 0
        lda     #_FERROR
@L7:    ldy     #_FILE::f_flags         ; X must be zero here!
        ora     (file),y
        sta     (file),y
        txa                             ; a/x = 0
        beq     @L99                    ; Return zero

; Read was ok, account for the pushed back character (if any).

@L8:    add     pb
        bcc     @L9
        inx

; Check for end of file.

@L9:    cmp     #0                      ; Zero bytes read?
        bne     @L10
        cpx     #0
        bne     @L10

; Zero bytes read. Set the EOF flag

        lda     #_FEOF
        bne     @L7                     ; Set flag and return zero

; Return the number of items successfully read. Since we've checked for
; bytes == 0 above, size cannot be zero here, so the division is safe.

@L10:   jsr     pushax                  ; Push number of bytes read
        ldy     #5
        jsr     ldaxysp                 ; Get size
        jsr     tosudivax               ; bytes / size -> a/x
@L99:   ldy     save                    ; Restore zp register
        sty     file
        ldy     save+1
        sty     file+1
        jmp     incsp6                  ; Drop params, return

.endproc

; ------------------------------------------------------------------------
; Data

.bss
save:  	.res	2
pb:     .res    1

