;; #LAYOUT# STD *        #TAKE
;; #LAYOUT# X16 *        #IGNORE
;; #LAYOUT# *   KERNAL_0 #TAKE
;; #LAYOUT# *   *        #IGNORE

;
; Official Kernal routine, described in:
;
; - [RG64] C64 Programmers Reference Guide   - page 295
; - [CM64] Computes Mapping the Commodore 64 - page 220
;
; CPU registers that has to be preserved (see [RG64]): none
;

; Variables used:
; - KEYTAB  - keyboard matrix pointer
; - DELAY   - counter for first delay between key repeats
; - KOUNT   - counter for next delays between key repeats
; - SHFLAG  - SHIFT / CTRL / VENDOR keypress status
; - NDX     - number of characters in keyboard buffer
; - XMAX    - maximum size of keyboard buffer
; - LSTX    - matrix coordinate of last pressed key
; - LSTSHF  - previous status of SHFLAG
; - MODE    - flag, whether charset toggle (SHIFT+VENDOR) is allowed
; - RPTFLG  - whether key repeat is allowed


; Routine takes some ideas from TWW/CTR proposal, see here:
; - http://codebase64.org/doku.php?id=base:scanning_the_keyboard_the_correct_and_non_kernal_way


SCNKEY:

!ifdef CONFIG_MB_M65 {

	jsr M65_MODEGET
	bcs scnkey_legacy_mode

	; Map KERNAL_C segment

	lda VIC_CTRLA
	pha
	ora #%00100000
	sta VIC_CTRLA

	; Call the native mode routine

	jsr (VKC__m65_scnkey)

	; Restore mapping and quit

	pla
	sta VIC_CTRLA

	rts

scnkey_legacy_mode:

}

	; Prepare for SHFLAG update

	lda SHFLAG
	sta LSTSHF
	lda #$00
	sta SHFLAG

!ifndef CONFIG_JOY2_CURSOR { !ifdef CONFIG_KEY_FAST_SCAN {

	; Check for any activity - speed optimization, but cannot be done
	; that early if we are to scan the joystick #2

	ldx #$00
	stx CIA1_PRA                       ; connect all the rows
!ifdef CONFIG_KEYBOARD_C128 {
	stx VIC_XSCAN
}
	dex                                ; puts $FF
	cpx CIA1_PRB
	beq scnkey_no_keys

} }

	; Retrieve SHIFT / VENDOR / CTRL / ALT / CAPS LOCK status

!ifdef CONFIG_KEYBOARD_C128_CAPS_LOCK {

	; First check if this is really a C128 - to avoid false positive
	ldx #$00
	stx VIC_XSCAN
	dex                                ; puts $FF
	cpx VIC_XSCAN
	beq @1                             ; branch if C64, C128 will keep some bits cleared

	lda CPU_R6510
	and #$40
	bne @1                             ; branch if no CAPS LOCK

	lda #KEY_FLAG_CAPSL
	sta SHFLAG
@1:

!ifndef !CONFIG_KEYBOARD_C128 {
	stx VIC_XSCAN                      ; disconnect C128 keys if no C128 keyboard is supported
} }

	ldy #(__kb_matrix_bucky_confmask_end - kb_matrix_bucky_confmask - 1)
scnkey_bucky_loop:
	lda kb_matrix_bucky_confmask, y
	sta CIA1_PRA
!ifdef CONFIG_KEYBOARD_C128 {
	lda kb_matrix_bucky_confmask_c128, y
	sta VIC_XSCAN
}
	lda kb_matrix_bucky_testmask, y
	and CIA1_PRB
	bne @3                             ; branch if not pressed
	lda SHFLAG
	ora kb_matrix_bucky_shflag, y
	sta SHFLAG
@3:
	dey
	bpl scnkey_bucky_loop

	; Set KEYTAB vector

	lda KEYLOG+1
	bne @11
	jsr scnkey_set_keytab              ; KEYLOG routine on zeropage? most likely vector not set
	+bra scnkey_keytab_set_done

@11:
!ifdef HAS_OPCODES_65CE02 {
	jsr (KEYLOG)
} else {
	jsr scnkey_via_keylog
}

scnkey_keytab_set_done:

!ifdef CONFIG_JOY2_CURSOR {

	; Check for control port 2 activity

	ldx #$00
	stx CIA1_DDRA                      ; set port to input to read joystick

	lda CIA1_PRA                       ; read the joystick 2 status

	dex                                ; puts $FF
	stx CIA1_DDRA                      ; set port back to output

	and #%00001111                     ; filter out anything but joystick movement
	cmp #%00001111

	bne scnkey_joystick_filtered

!ifdef CONFIG_KEY_FAST_SCAN {

	inx                                ; puts $00
	stx CIA1_PRA                       ; connect all the rows
!ifdef CONFIG_KEYBOARD_C128 {
	stx VIC_XSCAN
}
	dex                                ; puts $FF
	cpx CIA1_PRB
	beq scnkey_no_keys

} ; CONFIG_KEY_FAST_SCAN
} ; CONFIG_JOY2_CURSOR

	; Check for control port 1 activity (can interfere with keyboard)

	jsr keyboard_disconnect            ; disconnect all the rows, .X will be $FF
	cpx CIA1_PRB                       ; only control port activity will be reported
!ifdef CONFIG_JOY1_CURSOR {
	bne scnkey_joystick_1              ; use joystick for cursor keys
} else {
	bne scnkey_no_keys                 ; if activity detected do not scan the keyboard
}

	; Check if KEYTAB is not 0 (happens if more than one bucky key is pressed)
	; High byte equal to 0 = table considered invalid
	
	lda KEYTAB+1
	beq scnkey_no_keys

	; Scan the keyboard matrix

	ldy #$FF                           ; offset in key matrix table, $FF for not found yet
!ifdef CONFIG_KEYBOARD_C128 {
	jsr scnkey_128
}
	ldx #$07
scnkey_matrix_loop:
	lda kb_matrix_row_keys, x
	sta CIA1_PRA
	lda kb_matrix_bucky_filter, x      ; filter out bucky keys
	ora CIA1_PRB
	cmp #$FF
	beq scnkey_matrix_loop_next        ; skip if no key pressed from this row
	cpy #$FF
	bne scnkey_no_keys                 ; jam, more than one key pressed
	; We have at least one key pressed in this row, we need to find which one exactly
	ldy #$07
scnkey_matrix_loop_inner:
	cmp kb_matrix_row_keys, y
	bne @4                             ; not this particular key
	tya                                ; now .A contains key offset within a row
	clc
	adc kb_matrix_row_offsets, x       ; now .A contains key offset from the matrix start
	tay
	jmp scnkey_matrix_loop_next
@4:
	dey
	bpl scnkey_matrix_loop_inner
	bmi scnkey_no_keys                 ; branch always, multiple keys must have been pressed
scnkey_matrix_loop_next:
	dex
	bpl scnkey_matrix_loop

	; Scanning complete - check if we got a key

	cpy #$FF
	beq scnkey_no_keys

	; To be extra sure, check for possible control port 1 interference once again

	jsr keyboard_disconnect            ; disconnect all the rows, .X will be $FF
	cpx CIA1_PRB                       ; only control port activity will be reported
	beq scnkey_got_key                 ; branch if joystick activity NOT detected

	; FALLTROUGH

scnkey_no_keys:

	; Mark no key press

	lda #$40
	sta LSTX
	rts

!ifdef CONFIG_JOY1_CURSOR {

scnkey_joystick_1:

	; Prepare joystick 1 status

	lda CIA1_PRB
	and #%00001111                     ; filter out anything but movement

	; FALLTROUGH
}

!ifdef CONFIG_JOY1_OR_JOY2_CURSOR {

scnkey_joystick_filtered:

	; Decode joystick event using appropriate matrix

	ldy #$03
@5:
	cmp kb_matrix_joy_status, y
	beq @6 
	dey
	bpl @5

	; Not found

	rts
@6:
	; Found

	lda kb_matrix_joy_keytab_lo, y
	sta KEYTAB+0
	lda kb_matrix_joy_keytab_hi, y
	sta KEYTAB+1
	lda kb_matrix_joy_keytab_idx, y
	tay
	
	; FALLTROUGH
}

scnkey_got_key: ; .Y should now contain the key offset in matrix pointed by KEYTAB

	cpy LSTX
	beq scnkey_try_repeat              ; branch if the same key as previously
	sty LSTX

	; Reset key repeat counters - see [CM64] page 58

	; Note: according to [CM64] it should be $10 for initializing DELAY, $06 for
	; initializing KOUNT for the first time and $04 for subsequent ones - but replicating
	; this would make our implementation longer, probably without improving the compatibility.
	;
	; Besides - since I am the one who writes the code, I will make the values exactly how I like them :D

	lda #CONFIG_KEY_DELAY
	sta DELAY

	; FALLTROUGH

scnkey_output_key:

	; Check if we have free space in the keyboard buffer

	lda NDX
	cmp XMAX
	bcs scnkey_buffer_full             ; no space in buffer

	; Reinitialize secondary counter

	lda #$03
	sta KOUNT

	; Retrieve the PETSCII code

!ifdef CONFIG_KEYBOARD_C128 {

	cpy #$40
	bcc scnkey_output_get_keycode

	lda kb_matrix_128 - $41, y         ; retrieve key code from C128 extended matrix
	jmp scnkey_got_petscii

	; FALLTROUGH

scnkey_output_get_keycode:

	lda (KEYTAB), y	                   ; retrieve key code from standard matrix

} ; CONFIG_KEYBOARD_C128

!ifdef CONFIG_KEYBOARD_C128_CAPS_LOCK {

	; Check if we need special handling for a CAPS LOCK key
	; This is shorter than a separate set of tables

scnkey_handle_caps_lock:

!ifndef CONFIG_KEYBOARD_C128 {
	lda (KEYTAB), y
}
	tax 
	lda SHFLAG
	and #(%00000111 + KEY_FLAG_CAPSL)
	cmp #KEY_FLAG_CAPSL
	bne @7                             ; branch if no special CAPS LOCK handling needed

	cpx #$41                           ; 'A'
	bcc @7
	cpx #$5B                           ; 'Z' + 1
	bcs @7

	txa
	clc
	adc #$20                           ; turn lowercase into uppercase
	tax 
@7:
	txa

} ; CONFIG_KEYBOARD_C128_CAPS_LOCK

!ifndef CONFIG_KEYBOARD_C128 { !ifndef CONFIG_KEYBOARD_C128_CAPS_LOCK {

	lda (KEYTAB), y
} }

	; Output PETSCII code to the keyboard buffer

scnkey_got_petscii:

	beq scnkey_no_keys                 ; branch if we have no PETSCII code for this key
	ldy NDX
	sta KEYD, y
	inc NDX

	; FALLTROUGH

scnkey_done:

	rts

scnkey_try_repeat:

!ifndef CONFIG_KEY_REPEAT_ALWAYS {

	; Check whether we should repeat keys - first the flag, afterwards hardcoded list

	lda RPTFLG
	bmi scnkey_handle_repeat           ; branch if we should repeat always

	tya
	ldx #(__kb_matrix_alwaysrepeat_end - kb_matrix_alwaysrepeat - 1)
@9:
	cmp kb_matrix_alwaysrepeat, x
	beq scnkey_handle_repeat
	dex
	bpl @9
	bmi scnkey_done

	; FALLTROUGH

scnkey_handle_repeat:

} ; no CONFIG_KEY_REPEAT_ALWAYS

	; Countdown before first repeat

	lda DELAY
	beq @10
	dec DELAY

	rts
@10:
	; Countdown before subsequent repeats

	lda KOUNT
	beq scnkey_output_key               ; if second counter is also 0, we can repeat the key
	dec KOUNT

	rts

scnkey_buffer_full:

	; Keyboard buffer was full - at least make sure the key
	; will be repeated as soon as possible

	lda #$00
	sta DELAY
	sta KOUNT

	rts
