; *** Consts ***
; maximum number of lines (line number maximum, however, is always 0xffff)
.equ	BUF_MAXLINES	0x100
; Size of the string pool
.equ	BUF_POOLSIZE	0x1000

; *** Variables ***
; A pointer to the first free line
.equ	BUF_LFREE	BUF_RAMSTART
; A pointer to the first free byte in the pool
.equ	BUF_PFREE	@+2
; The line index. Each record consists of 4 bytes: 2 for line number,
; 2 for pointer to string in pool. Kept in order of line numbers.
.equ	BUF_LINES	@+2
; The line pool. A list of null terminated strings. BUF_LINES records point
; to those strings.
.equ	BUF_POOL	@+BUF_MAXLINES*4
.equ	BUF_RAMEND	@+BUF_POOLSIZE

bufInit:
	ld	hl, BUF_LINES
	ld	(BUF_LFREE), hl
	ld	hl, BUF_POOL
	ld	(BUF_PFREE), hl
	cp	a		; ensure Z
	ret

; Add line at (HL) with line number DE to the buffer. The string at (HL) should
; not contain the line number prefix or the whitespace between the line number
; and the comment.
; Note that an empty string is *not* an error. It will be saved as a line.
; Z for success.
; Error conditions are:
; * not enough space in the pool
; * not enough space in the line index
bufAdd:
	; Check whether we have enough pool space. This is done in all cases.
	call	strlen
	inc	a		; strlen doesn't include line termination
	exx			; preserve HL and DE
	ld	hl, (BUF_PFREE)
	call	addHL
	ld	de, BUF_RAMEND
	sbc	hl, de
	exx			; restore
	; no carry? HL >= BUF_RAMEND, error. Z already unset
	ret	nc

	; Check the kind of operation we make: add, insert or replace?
	call	bufFind
	jr	z, .replace	; exact match, replace
	call	c, .insert	; near match, insert

	; do we have enough index space?
	exx			; preserve HL and DE
	ld	hl, (BUF_LFREE)
	ld	de, BUF_POOL-4
	or	a		; reset carry
	sbc	hl, de
	exx			; restore
	; no carry? HL >= BUF_POOL, error. Z already unset
	ret	nc

	; We have enough space.
	; set line index data
	push	de	; --> lvl 1
	ld	(ix), e
	ld	(ix+1), d
	ld	de, (BUF_PFREE)
	ld	(ix+2), e
	ld	(ix+3), d

	; Increase line index size
	ld	de, (BUF_LFREE)
	inc	de \ inc de \ inc de \ inc de
	ld	(BUF_LFREE), de

	; Fourth step: copy string to pool
	ld	de, (BUF_PFREE)
	call	strcpyM
	ld	(BUF_PFREE), de
	pop	de	; <-- lvl 1
	ret

; No need to add a new line, just replace the current one.
.replace:
	ld	(ix), e
	ld	(ix+1), d
	push	de
	ld	de, (BUF_PFREE)
	ld	(ix+2), e
	ld	(ix+3), d
	call	strcpyM
	ld	(BUF_PFREE), de
	pop	de
	ret

; An insert is exactly like an add, except that lines following insertion point
; first.
.insert:
	push	hl
	push	de
	push	bc
	; We want a LDDR that moves from (BUF_LFREE)-1 to (BUF_LFREE)+3
	; for a count of (BUF_LFREE)-BUF_LINES
	ld	hl, (BUF_LFREE)
	ld	de, BUF_LINES
	or	a		; clear carry
	sbc	hl, de
	ld	b, h
	ld	c, l
	ld	hl, (BUF_LFREE)
	ld	d, h
	ld	e, l
	dec	hl
	inc	de \ inc de \ inc de
	lddr
	pop	bc
	pop	de
	pop	hl
	ret

; Set IX to point to the beginning of the pool.
; Z set if (IX) is a valid line, unset if the pool is empty.
bufFirst:
	ld	ix, BUF_LINES
	jp	bufEOF

; Given a valid line record in IX, move IX to the next line.
; This routine doesn't check that IX is valid. Ensure IX validity before
; calling.
bufNext:
	inc	ix \ inc ix \ inc ix \ inc ix
	jp	bufEOF

; Returns whether line index at IX is past the end of file, that is,
; whether IX == (BUF_LFREE)
; Z is set when not EOF, unset when EOF.
bufEOF:
	push	hl
	push	de
	push	ix \ pop hl
	or	a		; clear carry
	ld	de, (BUF_LFREE)
	sbc	hl, de
	jr	z, .empty
	cp	a		; ensure Z
.end:
	pop	de
	pop	hl
	ret
.empty:
	call	unsetZ
	jr	.end

; Given a line index in (IX), set HL to its associated string pointer.
bufStr:
	ld	l, (ix+2)
	ld	h, (ix+3)
	ret

; Browse lines looking for number DE. Set IX to point to one of these :
; 1 - an exact match
; 2 - the first found line to have a higher line number
; 3 - EOF
; Set Z on an exact match, C on a near match, NZ and NC on EOF.
bufFind:
	call	bufFirst
	ret	nz
.loop:
	ld	a, d
	cp	(ix+1)
	ret	c	; D < (IX+1), situation 2
	jr	nz, .next
	ld	a, e
	cp	(ix)
	ret	c	; E < (IX), situation 2
	ret	z	; exact match!
.next:
	call	bufNext
	ret	nz
	jr	.loop