; *******************************************
; *    PAT80 COMPOSITE PAL VIDEO ADAPTER    *
; *      Character generator module         *
; *******************************************
;
; @language: AVR ASM
;
; This file is part of Pat80 IO Devices.
;
; Pat80 IO Devices is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; Pat80 IO Devices is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with Pat80 IO Devices.  If not, see <http://www.gnu.org/licenses/>.
;
;
; This module generates the character pixels using the font present in rom
; and adds it on the framebuffer in the position indicated by POS_COARSE (Y).

.equ LINE_COLUMNS = 46	; number of columns (characters or chunks) per line

; Draws character in register A to the screen at current coords (Y)
; @param (HIGH_ACCUM) ascii code to display
; @modifies r0 (A), r1, r2, r3, r17, HIGH_ACCUM, Y, Z
draw_char:
	; Check char is valid
	cpi HIGH_ACCUM, 0x7f
	brlo draw_char_valid
	ret

	draw_char_valid:
	; Glyph's first byte is at:
	; glyph_pointer = font_starting_mem_pos + (ascii_code * number_of_bytes_per_font)
	; But all the fonts are 1 byte large, so a glyph is 1*height bytes:
	; glyph_pointer = FONT + (ascii_code * FONT_HEIGHT)

	; Load first glyph position on Z
	ldi ZH, high(FONT<<1)
	ldi ZL, low(FONT<<1)
	; Obtain offset multiplying ascii_code * number_of_bytes_per_font
	ldi r17, FONT_HEIGHT
	mul HIGH_ACCUM, r17	; result overwrites r0 and r1!
	; 16-bit addition between gliph's first byte position and offset (and store result in Z) to obtain our glyph position
	add ZL, r0
	adc ZH, r1
	; Z contain our glyph's first byte position: draw it
	; Obtain drawing position in framebuffer memory (in Y)
	call update_mem_pointer
	; The drawing consist of FONT_HEIGHT cycles. Every glyph byte is placed on its own line
	; on screen. To do this, we place it LINE_COLUMNS bytes after the previous one.
	clr HIGH_ACCUM
	draw_char_loop:
		; Load glyph line byte from program memory (and point to the next)
		lpm A, Z+
		; Write glyph line to framebuffer at chunk cursor position (Y)
		st Y, A
		; Increment chunk cursor position (Y) to next line of the same char column
		adiw YH:YL,LINE_COLUMNS
		; Decrement loop counter and exit if reached 0
		inc HIGH_ACCUM
		cpi HIGH_ACCUM, FONT_HEIGHT
		brlo draw_char_loop

	; Char drawing is complete. Increment cursor position
	inc POS_COLUMN
	; Check if end of line
	cpi POS_COLUMN, LINE_COLUMNS
	brsh draw_char_eol
	ret
	draw_char_eol:
		; end of line
		clr POS_COLUMN	; reset column to 0
		; Move cursor to next line
		ldi HIGH_ACCUM, FONT_HEIGHT
		add POS_ROWP, HIGH_ACCUM
		; check if reached end of screen
		cpi POS_ROWP, SCREEN_HEIGHT
		brsh draw_char_eos
		ret
	draw_char_eos:
		; end of screen: scroll screen but leave line pointer to last line
		call scroll_screen
		ret

; Sets the cursor to 0,0 and clears fine position
cursor_pos_home:
	; Set all positions to 0
	clr POS_COLUMN
	clr POS_ROWP
	ret

; Draws a newline
; Moves cursor to start of following screen line
; Takes care of particular cases, i.e. end of screen (shifts all screen up by one line)
draw_carriage_return:
	; Move cursor to line start
	ldi POS_COLUMN, 0
	; Move cursor to next line
	ldi HIGH_ACCUM, FONT_HEIGHT
	add POS_ROWP, HIGH_ACCUM
	; Check if end of screen
	cpi POS_ROWP, SCREEN_HEIGHT
	brsh draw_carriage_return_eos
	ret
	draw_carriage_return_eos:
	call scroll_screen
	ret

; Scrolls the screen by one line (=LINE_COLUMNS*FONT_HEIGHT bytes)
; and clears the last line (FRAMEBUFFER_END - LINE_COLUMNS*FONT_HEIGHT bytes)
; @uses A, Z
scroll_screen:
	; "Read" Pointer to first char of second line
	ldi YH, high(FRAMEBUFFER+(LINE_COLUMNS*FONT_HEIGHT))
	ldi YL, low(FRAMEBUFFER+(LINE_COLUMNS*FONT_HEIGHT))
	; "Write" Pointer to first char of first line
	ldi ZH, high(FRAMEBUFFER)
	ldi ZL, low(FRAMEBUFFER)
	; Copy data
	scroll_screen_copy_loop:
		ld A, Y+
		st Z+, A
		cpi YH, high(FRAMEBUFFER_END)
		brne scroll_screen_copy_loop
		cpi YL, low(FRAMEBUFFER_END)
		brne scroll_screen_copy_loop
	; All the lines have been "shifted" up by one line.
	; The first line is lost and the last is duplicate. Clear the last.
	clr A
	scroll_screen_clear_loop:
		st Z+, A
		cpi r31, high(FRAMEBUFFER_END)
		brne scroll_screen_clear_loop
		cpi r30, low(FRAMEBUFFER_END)
		brne scroll_screen_clear_loop
	; Last line cleared. Set cursor position
	clr POS_COLUMN	; cursor to first column
	ldi POS_ROWP, SCREEN_HEIGHT-FONT_HEIGHT
	ret

; Sets the Y register to point to the cursor's first line memory position
; The cursor's position is represented by registers POS_COLUMN and POS_ROWP
update_mem_pointer:
	; Compute memory pointer offset: offset = (LINE_COLUMNS*POS_ROWP)+POS_COLUMN
	; LINE_COLUMNS*POS_ROWP
	ldi HIGH_ACCUM, LINE_COLUMNS
	mul HIGH_ACCUM, POS_ROWP	; result overwrites r0 and r1!
	; ...+POS_COLUMN
	add r0, POS_COLUMN
	clr HIGH_ACCUM
	adc r1, HIGH_ACCUM
	; Set pointer to start of framebuffer
	ldi YL, low(FRAMEBUFFER)
	ldi YH, high(FRAMEBUFFER)
	; Add offset to pointer
	add YL, r0
	adc YH, r1
	ret

clear_screen:
	ldi YH, high(FRAMEBUFFER)
	ldi YL, low(FRAMEBUFFER)
	load_mem_loop:
		clr HIGH_ACCUM
		;ser HIGH_ACCUM
		st Y+, HIGH_ACCUM
		; if reached the last framebuffer byte, exit cycle
		cpi YH, high(FRAMEBUFFER_END)
		brne load_mem_loop	; if not 0, repeat h_picture_loop
		cpi YL, low(FRAMEBUFFER_END)
		brne load_mem_loop	; if not 0, repeat h_picture_loop
	ret