; ed - line editor
;
; A text editor modeled after UNIX's ed, but simpler. The goal is to stay tight
; on resources and to avoid having to implement screen management code (that is,
; develop the machinery to have ncurses-like apps in Collapse OS).
;
; ed has a mechanism to avoid having to move a lot of memory around at each
; edit. Each line is an element in an doubly-linked list and each element point
; to an offset in the "scratchpad". The scratchpad starts with the file
; contents and every time we change or add a line, that line goes to the end of
; the scratch pad and linked lists are reorganized whenever lines are changed.
; Contents itself is always appended to the scratchpad.
;
; That's on a resourceful UNIX system.
;
; That doubly linked list on the z80 would use 7 bytes per line (prev, next,
; offset, len), which is a bit much.
;
; We sacrifice speed for memory usage by making that linked list into a simple
; array of pointers to line contents in scratchpad. This means that we
; don't have an easy access to line length and we have to move a lot of memory
; around whenever we add or delete lines. Hopefully, "LDIR" will be our friend
; here...
;
; *** Requirements ***
; BLOCKDEV_SIZE
; FS_HANDLE_SIZE
; _blkGetB
; _blkPutB
; _blkSeek
; _blkTell
; addHL
; cpHLDE
; fsFindFN
; fsOpen
; fsGetB
; fsPutB
; fsSetSize
; intoHL
; printstr
; printcrlf
; stdioReadLine
; stdioPutC
; unsetZ
;
; *** Variables ***
;
.equ	ED_CURLINE	ED_RAMSTART
.equ	ED_RAMEND	@+2

edMain:
	; because ed only takes a single string arg, we can use HL directly
	call	ioInit
	ret	nz
	; diverge from UNIX: start at first line
	ld	hl, 0
	ld	(ED_CURLINE), hl

	call	bufInit

.mainLoop:
	ld	a, ':'
	call	stdioPutC
	call	stdioReadLine	; --> HL
	; Now, process line.
	call	printcrlf
	call	cmdParse
	jp	nz, .error
	ld	a, (CMD_TYPE)
	cp	'q'
	jr	z, .doQ
	cp	'w'
	jr	z, .doW
	; The rest of the commands need an address
	call	edReadAddrs
	jr	nz, .error
	ld	a, (CMD_TYPE)
	cp	'i'
	jr	z, .doI
	; The rest of the commands don't allow addr == cnt
	push	hl		; --> lvl 1
	ld	hl, (BUF_LINECNT)
	call	cpHLDE
	pop	hl		; <-- lvl 1
	jr	z, .error
	ld	a, (CMD_TYPE)
	cp	'd'
	jr	z, .doD
	cp	'a'
	jr	z, .doA
	jr	.doP

.doQ:
	xor	a
	ret

.doW:
	ld	a, 3		; seek beginning
	call	ioSeek
	ld	de, 0		; cur line
.wLoop:
	push	de \ pop hl
	call	bufGetLine	; --> buffer in (HL)
	jr	nz, .wEnd
	call	ioPutLine
	jr	nz, .error
	inc	de
	jr	.wLoop
.wEnd:
	; Set new file size
	call	ioTell
	call	ioSetSize
	; for now, writing implies quitting
	; TODO: reload buffer
	xor	a
	ret
.doD:
	ld	(ED_CURLINE), de
	; bufDelLines expects an exclusive upper bound, which is why we inc DE.
	inc	de
	call	bufDelLines
	jr	.mainLoop
.doA:
	inc	de
.doI:
	call	stdioReadLine	; --> HL
	call	bufScratchpadAdd	; --> HL
	; insert index in DE, line offset in HL. We want the opposite.
	ex	de, hl
	ld	(ED_CURLINE), hl
	call	bufInsertLine
	call	printcrlf
	jr	.mainLoop

.doP:
	push	hl
	call	bufGetLine
	jr	nz, .error
	call	printstr
	call	printcrlf
	pop	hl
	call	cpHLDE
	jr	z, .doPEnd
	inc	hl
	jr	.doP
.doPEnd:
	ld	(ED_CURLINE), hl
	jp	.mainLoop
.error:
	ld	a, '?'
	call	stdioPutC
	call	printcrlf
	jp	.mainLoop


; Transform an address "cmd" in IX into an absolute address in HL.
edResolveAddr:
	ld	a, (ix)
	cp	RELATIVE
	jr	z, .relative
	cp	EOF
	jr	z, .eof
	; absolute
	ld	l, (ix+1)
	ld	h, (ix+2)
	ret
.relative:
	ld	hl, (ED_CURLINE)
	push	de
	ld	e, (ix+1)
	ld	d, (ix+2)
	add	hl, de
	pop	de
	ret
.eof:
	ld	hl, (BUF_LINECNT)
	dec	hl
	ret

; Read absolute addr1 in HL and addr2 in DE. Also, check bounds and set Z if
; both addresses are within bounds, unset if not.
edReadAddrs:
	ld	ix, CMD_ADDR2
	call	edResolveAddr
	ld	de, (BUF_LINECNT)
	ex	de, hl		; HL: cnt DE: addr2
	call	cpHLDE
	jp	c, unsetZ	; HL (cnt) < DE (addr2). no good
	ld	ix, CMD_ADDR1
	call	edResolveAddr
	ex	de, hl		; HL: addr2, DE: addr1
	call	cpHLDE
	jp	c, unsetZ	; HL (addr2) < DE (addr1). no good
	ex	de, hl		; HL: addr1, DE: addr2
	cp	a		; ensure Z
	ret