; pad - read input from MD controller
;
; Conveniently expose an API to read the status of a MD pad A. Moreover,
; implement a mechanism to input arbitrary characters from it. It goes as
; follow:
;
; * Direction pad select characters. Up/Down move by one, Left/Right move by 5\
; * Start acts like Return
; * A acts like Backspace
; * B changes "character class": lowercase, uppercase, numbers, special chars.
;   The space character is the first among special chars.
; * C confirms letter selection
;
; *** Consts ***
;
.equ	PAD_CTLPORT	0x3f
.equ	PAD_D1PORT	0xdc

.equ	PAD_UP		0
.equ	PAD_DOWN	1
.equ	PAD_LEFT	2
.equ	PAD_RIGHT	3
.equ	PAD_BUTB	4
.equ	PAD_BUTC	5
.equ	PAD_BUTA	6
.equ	PAD_START	7

; *** Variables ***
;
; Button status of last padUpdateSel call. Used for debouncing.
.equ	PAD_SELSTAT	PAD_RAMSTART
; Button status of last padGetC call.
.equ	PAD_GETCSTAT	PAD_SELSTAT+1
; Current selected character
.equ	PAD_SELCHR	PAD_GETCSTAT+1
; When non-zero, will be the next char returned in GetC. So far, only used for
; LF that is feeded when Start is pressed.
.equ	PAD_NEXTCHR	PAD_SELCHR+1
.equ	PAD_RAMEND	PAD_NEXTCHR+1

; *** Code ***

padInit:
	ld	a, 0xff
	ld	(PAD_SELSTAT), a
	ld	(PAD_GETCSTAT), a
	xor	a
	ld	(PAD_NEXTCHR), a
	ld	a, 'a'
	ld	(PAD_SELCHR), a
	ret

; Put status for port A in register A. Bits, from MSB to LSB:
; Start - A - C - B - Right - Left - Down - Up
; Each bit is high when button is unpressed and low if button is pressed. For
; example, when no button is pressed, 0xff is returned.
padStatus:
	; This logic below is for the Genesis controller, which is modal. TH is
	; an output pin that swiches the meaning of TL and TR. When TH is high
	; (unselected), TL = Button B and TR = Button C. When TH is low
	; (selected), TL = Button A and TR = Start.
	push	bc
	ld	a, 0b11111101	; TH output, unselected
	out	(PAD_CTLPORT), a
	in	a, (PAD_D1PORT)
	and	0x3f		; low 6 bits are good
	ld	b, a		; let's store them
	; Start and A are returned when TH is selected, in bits 5 and 4. Well
	; get them, left-shift them and integrate them to B.
	ld	a, 0b11011101	; TH output, selected
	out	(PAD_CTLPORT), a
	in	a, (PAD_D1PORT)
	and	0b00110000
	sla	a
	sla	a
	or	b
	pop	bc
	ret

; From a pad status in A, update current char selection and return it.
; Returns the same Z as padStatus: set if unchanged, unset if changed
padUpdateSel:
	call	padStatus
	push	hl
	ld	hl, PAD_SELSTAT
	cp	(hl)
	ld	(hl), a
	pop	hl
	jr	z, .nothing	; nothing changed
	bit	PAD_UP, a
	jr	z, .up
	bit	PAD_DOWN, a
	jr	z, .down
	bit	PAD_LEFT, a
	jr	z, .left
	bit	PAD_RIGHT, a
	jr	z, .right
	bit	PAD_BUTB, a
	jr	z, .nextclass
	jr	.nothing
.up:
	ld	a, (PAD_SELCHR)
	inc	a
	jr	.setchr
.down:
	ld	a, (PAD_SELCHR)
	dec	a
	jr	.setchr
.left:
	ld	a, (PAD_SELCHR)
	dec	a \ dec a \ dec a \ dec a \ dec a
	jr	.setchr
.right:
	ld	a, (PAD_SELCHR)
	inc	a \ inc a \ inc a \ inc a \ inc a
	jr	.setchr
.nextclass:
	; Go to the beginning of the next "class" of characters
	push	bc
	ld	a, (PAD_SELCHR)
	ld	b, '0'
	cp	b
	jr	c, .setclass	; A < '0'
	ld	b, ':'
	cp	b
	jr	c, .setclass
	ld	b, 'A'
	cp	b
	jr	c, .setclass
	ld	b, '['
	cp	b
	jr	c, .setclass
	ld	b, 'a'
	cp	b
	jr	c, .setclass
	ld	b, ' '
	; continue to .setclass
.setclass:
	ld	a, b
	pop	bc
	; continue to .setchr
.setchr:
	; check range first
	cp	0x7f
	jr	nc, .tooHigh
	cp	0x20
	jr	nc, .setchrEnd	; not too low
	; too low, probably because we overdecreased. Let's roll over
	ld	a, '~'
	jr	.setchrEnd
.tooHigh:
	; too high, probably because we overincreased. Let's roll over
	ld	a, ' '
	; continue to .setchrEnd
.setchrEnd:
	ld	(PAD_SELCHR), a
	jp	unsetZ
.nothing:
	cp	a		; ensure Z
	ld	a, (PAD_SELCHR)
	ret

padGetC:
	ld	a, (PAD_NEXTCHR)
	or	a
	jr	nz, .nextchr
	call	padStatus
	push	hl
	ld	hl, PAD_GETCSTAT
	cp	(hl)
	ld	(hl), a
	pop	hl
	jp	z, unsetZ	; nothing changed
	bit	PAD_BUTC, a
	jr	z, .advance
	bit	PAD_BUTA, a
	jr	z, .backspace
	bit	PAD_START, a
	jr	z, .return
	jp	unsetZ
.return:
	ld	a, ASCII_LF
	ld	(PAD_NEXTCHR), a
	; continue to .advance
.advance:
	ld	a, (PAD_SELCHR)
	cp	a
	ret
.backspace:
	ld	a, ASCII_BS
	cp	a
	ret
.nextchr:
	; We have a "next char", return it and clear it.
	cp	a		; ensure Z
	push	af
	xor	a
	ld	(PAD_NEXTCHR), a
	pop	af
	ret