; buf - manage line buffer
;
; *** Variables ***
; Number of lines currently in the buffer
.equ	BUF_LINECNT	BUF_RAMSTART
; List of pointers to strings in scratchpad
.equ	BUF_LINES	@+2
; Points to the end of the scratchpad, that is, one byte after the last written
; char in it.
.equ	BUF_PADEND	@+ED_BUF_MAXLINES*2
; The in-memory scratchpad
.equ	BUF_PAD		@+2

.equ	BUF_RAMEND	@+ED_BUF_PADMAXLEN

; *** Code ***

; On initialization, we read the whole contents of target blkdev and add lines
; as we go.
bufInit:
	ld	hl, BUF_PAD	; running pointer to end of pad
	ld	de, BUF_PAD	; points to beginning of current line
	ld	ix, BUF_LINES	; points to current line index
	ld	bc, 0		; line count
	; init pad end in case we have an empty file.
	ld	(BUF_PADEND), hl
.loop:
	call	ioGetB
	jr	nz, .loopend
	or	a		; null? hum, weird. same as LF
	jr	z, .lineend
	cp	0x0a
	jr	z, .lineend
	ld	(hl), a
	inc	hl
	jr	.loop
.lineend:
	; We've just finished reading a line, writing each char in the pad.
	; Null terminate it.
	xor	a
	ld	(hl), a
	inc	hl
	; Now, let's register its pointer in BUF_LINES
	ld	(ix), e
	inc	ix
	ld	(ix), d
	inc	ix
	inc	bc
	ld	(BUF_PADEND), hl
	ld	de, (BUF_PADEND)
	jr	.loop
.loopend:
	ld	(BUF_LINECNT), bc
	ret

; transform line index HL into its corresponding memory address in BUF_LINES
; array.
bufLineAddr:
	push	de
	ex	de, hl
	ld	hl, BUF_LINES
	add	hl, de
	add	hl, de	; twice, because two bytes per line
	pop	de
	ret

; Read line number specified in HL and make HL point to its contents.
; Sets Z on success, unset if out of bounds.
bufGetLine:
	push	de		; --> lvl 1
	ld	de, (BUF_LINECNT)
	call	cpHLDE
	pop	de		; <-- lvl 1
	jp	nc, unsetZ	; HL > (BUF_LINECNT)
	call	bufLineAddr
	; HL now points to an item in BUF_LINES.
	call	intoHL
	; Now, HL points to our contents
	cp	a		; ensure Z
	ret

; Given line indexes in HL and DE where HL < DE < CNT, move all lines between
; DE and CNT by an offset of DE-HL. Also, adjust BUF_LINECNT by DE-HL.
; WARNING: no bounds check. The only consumer of this routine already does
; bounds check.
bufDelLines:
	; Let's start with setting up BC, which is (CNT-DE) * 2
	push	hl	; --> lvl 1
	ld	hl, (BUF_LINECNT)
	scf \ ccf
	sbc	hl, de
	; mult by 2 and we're done
	sla	l \ rl h
	push	hl \ pop bc
	pop	hl	; <-- lvl 1
	; Good! BC done. Now, let's adjust BUF_LINECNT by DE-HL
	push	hl	; --> lvl 1
	scf \ ccf
	sbc	hl, de	; HL -> nb of lines to delete, negative
	push	de	; --> lvl 2
	ld	de, (BUF_LINECNT)
	add	hl, de	; adding DE to negative HL
	ld	(BUF_LINECNT), hl
	pop	de	; <-- lvl 2
	pop	hl	; <-- lvl 1
	; Line count updated!
	; One other thing... is BC zero? Because if it is, then we shouldn't
	; call ldir (otherwise we're on for a veeeery long loop), BC=0 means
	; that only last lines were deleted. nothing to do.
	ld	a, b
	or	c
	ret	z	; BC==0, return

	; let's have invert HL and DE to match LDIR's signature
	ex	de, hl
	; At this point we have higher index in HL, lower index in DE and number
	; of bytes to delete in BC. It's convenient because it's rather close
	; to LDIR's signature! The only thing we need to do now is to translate
	; those HL and DE indexes in memory addresses, that is, multiply by 2
	; and add BUF_LINES
	push	hl	; --> lvl 1
	ex	de, hl
	call	bufLineAddr
	ex	de, hl
	pop	hl	; <-- lvl 1
	call	bufLineAddr
	; Both HL and DE are translated. Go!
	ldir
	ret

; Insert string where DE points to memory scratchpad, then insert that line
; at index HL, offsetting all lines by 2 bytes.
bufInsertLine:
	call	bufIndexInBounds
	jr	nz, .append
	push	de	; --> lvl 1, scratchpad ptr
	push	hl	; --> lvl 2, insert index
	; The logic below is mostly copy-pasted from bufDelLines, but with a
	; LDDR logic (to avoid overwriting). I learned, with some pain involved,
	; that generalizing this code wasn't working very well. I don't repeat
	; the comments, refer to bufDelLines
	ex	de, hl	; line index now in DE
	ld	hl, (BUF_LINECNT)
	scf \ ccf
	sbc	hl, de
	; mult by 2 and we're done
	sla	l \ rl h
	push	hl \ pop bc
	; From this point, we don't need our line index in DE any more because
	; LDDR will start from BUF_LINECNT-1 with count BC. We'll only need it
	; when it's time to insert the line in the space we make.
	ld	hl, (BUF_LINECNT)
	call	bufLineAddr
	; HL is pointing to *first byte* after last line. Our source needs to
	; be the second byte of the last line and our dest is the second byte
	; after the last line.
	push	hl \ pop	de
	dec	hl	; second byte of last line
	inc	de	; second byte beyond last line
	; HL = BUF_LINECNT-1, DE = BUF_LINECNT, BC is set. We're good!
	lddr
.set:
	; We still need to increase BUF_LINECNT
	ld	hl, (BUF_LINECNT)
	inc	hl
	ld	(BUF_LINECNT), hl
	; A space has been opened at line index HL. Let's fill it with our
	; inserted line.
	pop	hl		; <-- lvl 2, insert index
	call	bufLineAddr
	pop	de		; <-- lvl 1, scratchpad offset
	ld	(hl), e
	inc	hl
	ld	(hl), d
	ret
.append:
	; nothing to move, just put the line there. Let's piggy-back on the end
	; of the regular routine by carefully pushing the right register in the
	; right place.
	; But before that, make sure that HL isn't too high. The only place we
	; can append to is at (BUF_LINECNT)
	ld	hl, (BUF_LINECNT)
	push	de		; --> lvl 1
	push	hl		; --> lvl 2
	jr	.set

; copy string that HL points to to scratchpad and return its pointer in
; scratchpad, in HL.
bufScratchpadAdd:
	push	de
	ld	de, (BUF_PADEND)
	push	de	; --> lvl 1
	call	strcpyM
	inc	de		; pad end is last char + 1
	ld	(BUF_PADEND), de
	pop	hl	; <-- lvl 1
	pop	de
	ret

; Sets Z according to whether the line index in HL is within bounds.
bufIndexInBounds:
	push	de
	ld	de, (BUF_LINECNT)
	call	cpHLDE
	pop	de
	jr	c, .withinBounds
	; out of bounds
	jp	unsetZ
.withinBounds:
	cp	a	; ensure Z
	ret
