; Scrolling text console with line wrapping, 30x29 characters.
; Buffers lines for speed. Will work even if PPU doesn't
; support scrolling (until text reaches bottom). Keeps border
; along bottom in case TV cuts it off.
;
; Defers most initialization until first newline, at which
; point it clears nametable and makes palette non-black.
;
; ** ASCII font must already be in CHR, and mirroring
; must be vertical or single-screen.

.ifndef CONSOLE_COLOR
	CONSOLE_COLOR = $30     ; white
.endif

console_screen_width = 32 ; if lower than 32, left-justifies

; Number of characters of margin on left and right, to avoid
; text getting cut off by common TVs. OK if either/both are 0.
console_left_margin  = 1
console_right_margin = 1

console_width = console_screen_width - console_left_margin - console_right_margin

zp_byte console_pos ; 0 to console_width
zp_byte console_scroll
zp_byte console_temp
bss_res console_buf,console_width


; Initializes console
console_init:
	; Flag that console hasn't been initialized
	setb console_scroll,$FF
	
	setb console_pos,0
	rts


; Hides console by disabling PPU rendering and blacking out
; first four entries of palette.
; Preserved: A, X, Y
console_hide:
	pha
	jsr console_wait_vbl_
	setb PPUMASK,0
	lda #$0F
	jsr console_load_palette_
	pla
	rts


; Shows console display
; Preserved: X, Y
console_show:
	pha
	lda #CONSOLE_COLOR
	jsr console_show_custom_color_
	pla
	rts


; Prints char A to console. Will not appear until
; a newline or flush occurs.
; Preserved: A, X, Y
console_print:
	cmp #10
	beq console_newline
	
	sty console_temp
	
	ldy console_pos
	cpy #console_width
	beq console_full_
	sta console_buf,y
	iny
	sty console_pos
	
	ldy console_temp
	rts
	

; Displays current line and starts new one
; Preserved: A, X, Y
console_newline:
	pha
	jsr console_wait_vbl_
	jsr console_flush_
	jsr console_scroll_up_
	setb console_pos,0
	pla
	rts


; Displays current line's contents without scrolling.
; Preserved: A, X, Y
console_flush:
	pha
	jsr console_wait_vbl_
	jsr console_flush_
	jsr console_apply_scroll_
	pla
	rts


;**** Internal routines ****

console_full_:
	ldy console_temp

	; Line is full
	
	; If space, treat as newline
	cmp #' '
	beq console_newline

	; Wrap current line at appropriate point
	pha
	tya
	pha
	jsr console_wrap_
	pla
	tay
	pla
	
	jmp console_print
	

; Inserts newline into buffer at appropriate position, leaving
; next line ready in buffer
; Preserved: X, console_temp
console_wrap_:
	; Find beginning of last word
	ldy #console_width
	lda #' '
:       dey
	bmi console_newline
	cmp console_buf,y
	bne :-
	
	; y = 0 to console_width-1
	
	; Flush through current word and put remaining
	; in buffer for next line
	jsr console_wait_vbl_
	
	; Time to last PPU write: 207 + 32*(26 + 10)
	
	lda console_scroll
	jsr console_set_ppuaddr_
	
	stx console_pos         ; save X
	
	ldx #0
	
	; Print everything before last word
:       lda console_buf,x
	sta PPUDATA
	inx
	dey
	bpl :-
	
	; x = 1 to console_width
	
	; Move last word to beginning of buffer, and
	; print spaces for rest of line
	ldy #0
	beq :++
:       lda #' '
	sta PPUDATA
	lda console_buf,x
	inx
	sta console_buf,y
	iny
:       cpx #console_width
	bne :--
	
	ldx console_pos         ; restore X
	
	; Append new text after that
	sty console_pos
	
	; FALL THROUGH


; Scrolls up 8 pixels and clears one line BELOW new line
; Preserved: X, console_temp
console_scroll_up_:
	; Scroll up 8 pixels
	lda console_scroll
	jsr console_add_8_to_scroll_
	sta console_scroll
	
	; Clear line AFTER that on screen
	jsr console_add_8_to_scroll_
	jsr console_set_ppuaddr_
	
	ldy #console_width
	lda #' '
:       sta PPUDATA
	dey
	bne :-
	; FALL THROUGH


; Applies current scrolling position to PPU
; Preserved: X, Y, console_temp
console_apply_scroll_:
	lda #0
	sta PPUADDR
	sta PPUADDR
	
	sta PPUSCROLL
	lda console_scroll
	jsr console_add_8_to_scroll_
	jsr console_add_8_to_scroll_
	sta PPUSCROLL
	rts


; Sets PPU address for row
; In: A = scroll position
; Preserved: X, Y
console_set_ppuaddr_:
	sta console_temp
	lda #$08
	asl console_temp
	rol a
	asl console_temp
	rol a
	sta PPUADDR
	lda console_temp
	ora #console_left_margin
	sta PPUADDR
	rts
	

; A = (A + 8) % 240
; Preserved: X, Y
console_add_8_to_scroll_:
	cmp #240-8
	bcc :+
	adc #16-1;+1 for set carry
:       adc #8
	rts


console_show_custom_color_:
	pha
	jsr console_wait_vbl_
	setb PPUMASK,PPUMASK_BG0
	pla
	jsr console_load_palette_
	jmp console_apply_scroll_


console_load_palette_:
	pha
	setb PPUADDR,$3F
	setb PPUADDR,$00
	setb PPUDATA,$0F        ; black
	pla
	sta PPUDATA
	sta PPUDATA
	sta PPUDATA
	rts


; Initializes PPU if necessary, then waits for VBL
; Preserved: A, X, Y, console_temp
console_wait_vbl_:
	lda console_scroll
	cmp #$FF
	bne @already_initialized
	
	; Deferred initialization of PPU until first use of console
	
	; In case PPU doesn't support scrolling, start a
	; couple of lines down
	setb console_scroll,16
	
	jsr console_hide
	tya
	pha
	
	; Fill nametable with spaces
	setb PPUADDR,$20
	setb PPUADDR,$00
	ldy #240
	lda #' '
:       sta PPUDATA
	sta PPUDATA
	sta PPUDATA
	sta PPUDATA
	dey
	bne :-
	
	; Clear attributes
	lda #0
	ldy #$40
:       sta PPUDATA
	dey
	bne :-

	pla
	tay
	
	jsr console_show
@already_initialized:
	jmp wait_vbl_optional


; Flushes current line
; Preserved: X, Y
console_flush_:
	lda console_scroll
	jsr console_set_ppuaddr_
	
	sty console_temp
	
	; Copy line
	ldy #0
	beq :++
:       lda console_buf,y
	sta PPUDATA
	iny
:       cpy console_pos
	bne :--
	
	ldy console_temp
	rts
