	.include	"global.s"

	;; ****************************************
	;; Beginning of module
	.title	"GB Runtime"
	.module	Runtime
	.area	_HEADER (ABS)

	;; Standard header for the GB
	.org	0x00
	RET			; Empty function (default for interrupts)

	.org	0x10
	.byte	0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01
	.byte	0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80

	;; Interrupt vectors
	.org	0x40		; VBL
.int_VBL:
	PUSH	HL
	LD	HL,#.int_0x40
	JP	.int

	.org	0x48		; LCD
.int_LCD:
	PUSH	HL
	LD	HL,#.int_0x48
	JP	.int

	.org	0x50		; TIM
.int_TIM:
	PUSH	HL
	LD	HL,#.int_0x50
	JP	.int

	.org	0x58		; SIO
.int_SIO:
	PUSH	HL
	LD	HL,#.int_0x58
	JP	.int

	.org	0x60		; JOY
.int_JOY:
	PUSH	HL
	LD	HL,#.int_0x60
	JP	.int

.int:
	PUSH	AF
	PUSH	BC
	PUSH	DE
1$:
	LD	A,(HL+)
	OR	(HL)
	JR	Z,2$
	PUSH	HL
	LD	A,(HL-)
	LD	L,(HL)
	LD	H,A
	CALL	3$
	POP	HL
	INC	HL
	JR	1$
2$:
	POP	DE
	POP	BC
	POP	AF
	POP	HL
	RETI

3$:
	JP	(HL)

	;; GameBoy Header

	;; DO NOT CHANGE...
	.org	0x100
.header:
	NOP
	JP	0x150
	.byte	0xCE,0xED,0x66,0x66
	.byte	0xCC,0x0D,0x00,0x0B
	.byte	0x03,0x73,0x00,0x83
	.byte	0x00,0x0C,0x00,0x0D
	.byte	0x00,0x08,0x11,0x1F
	.byte	0x88,0x89,0x00,0x0E
	.byte	0xDC,0xCC,0x6E,0xE6
	.byte	0xDD,0xDD,0xD9,0x99
	.byte	0xBB,0xBB,0x67,0x63
	.byte	0x6E,0x0E,0xEC,0xCC
	.byte	0xDD,0xDC,0x99,0x9F
	.byte	0xBB,0xB9,0x33,0x3E

	;; Title of the game
	.org	0x134
	.asciz	"Title"

	.org	0x144
	.byte	0,0,0

	;; Cartridge type is ROM only
	.org	0x147
	.byte	0

	;; ROM size is 32kB
	.org	0x148
	.byte	0

	;; RAM size is 0kB
	.org	0x149
	.byte	0

	;; Maker ID
	.org	0x14A
	.byte	0x00,0x00

	;; Version number
	.org	0x14C
	.byte	0x01

	;; Complement check
	.org	0x14D
	.byte	0x00

	;; Checksum
	.org	0x14E
	.byte	0x00,0x00

	;; ****************************************
	.org	0x150
.code_start:
	;; Beginning of the code
	DI			; Disable interrupts
	LD	D,A		; Store CPU type in D
	XOR	A
	;; Initialize the stack
	LD	SP,#.STACK
	;; Clear from 0xC000 to 0xDFFF
	LD	HL,#0xDFFF
	LD	C,#0x20
	LD	B,#0x00
1$:
	LD	(HL-),A
	DEC	B
	JR	NZ,1$
	DEC	C
	JR	NZ,1$
	;; Clear from 0xFE00 to 0xFEFF
	LD	HL,#0xFEFF
	LD	B,#0x00
2$:
	LD	(HL-),A
	DEC	B
	JR	NZ,2$
	;; Clear from 0xFF80 to 0xFFFF
	LD	HL,#0xFFFF
	LD	B,#0x80
3$:
	LD	(HL-),A
	DEC	B
	JR	NZ,3$
; 	LD	(.mode),A	; Clearing (.mode) is performed when clearing RAM
	;; Store CPU type
	LD	A,D
	LD	(__cpu),A

	;; Turn the screen off
	CALL	.display_off

	;; Initialize the display
	XOR	A
	LDH	(.SCY),A
	LDH	(.SCX),A
	LDH	(.STAT),A
	LDH	(.WY),A
	LD	A,#0x07
	LDH	(.WX),A

	;; Copy refresh_OAM routine to HIRAM
	LD	BC,#.refresh_OAM
	LD	HL,#.start_refresh_OAM
	LD	B,#.end_refresh_OAM-.start_refresh_OAM
4$:
	LD	A,(HL+)
	LDH	(C),A
	INC	C
	DEC	B
	JR	NZ,4$

	;; Install interrupt routines
	LD	BC,#.vbl
	CALL	.add_VBL
	LD	BC,#.serial_IO
	CALL	.add_SIO

	;; Standard color palettes
	LD	A,#0b11100100	; Grey 3 = 11 (Black)
				; Grey 2 = 10 (Dark grey)
				; Grey 1 = 01 (Light grey)
				; Grey 0 = 00 (Transparent)
	LDH	(.BGP),A
	LDH	(.OBP0),A
	LD	A,#0b00011011
	LDH	(.OBP1),A

	;; Turn the screen on
	LD	A,#0b11000000	; LCD		= On
				; WindowBank	= 0x9C00
				; Window	= Off
				; BG Chr	= 0x8800
				; BG Bank	= 0x9800
				; OBJ		= 8x8
				; OBJ		= Off
				; BG		= Off
	LDH	(.LCDC),A
	XOR	A
	LDH	(.IF),A
	LD	A,#0b00001001	; Pin P10-P13	=   Off
				; Serial I/O	=   On
				; Timer Ovfl	=   Off
				; LCDC		=   Off
				; V-Blank	=   On
	LDH	(.IE),A

	XOR	A
	LDH	(.NR52),A	; Turn sound off
	LDH	(.SC),A		; Use external clock
	LD	A,#.DT_IDLE
	LDH	(.SB),A		; Send IDLE byte
	LD	A,#0x80
	LDH	(.SC),A		; Use external clock

	XOR	A		; Erase the malloc list
	LD	(_malloc_heap_start+0),A
	LD	(_malloc_heap_start+1),A
	LD	(.sys_time+0),A	; Zero the system clock
	LD	(.sys_time+1),A	

	CALL	.init

	EI			; Enable interrupts

	;; Call the main function
	CALL	_main

99$:
	JR	99$		; Wait forever

	.org	.MODE_TABLE
	;; Jump table for modes
	RET

	;; ****************************************

	;; Ordering of segments for the linker
	.area	_CODE
	.area	_DATA
	.area	_LIT
	.area	_BSS
	.area	_HEAP		; HEAP is for malloc

	.area	_BSS

__cpu::
	.ds	0x01		; GB type (GB, PGB, CGB)
.mode::
	.ds	0x01		; Current mode
__io_out::
	.ds	0x01		; Byte to send
__io_in::
	.ds	0x01		; Received byte
__io_status::
	.ds	0x01		; Status of serial IO
.vbl_done::
	.ds	0x01		; Is VBL interrupt finished?
.sys_time::
_sys_time::
	.ds	0x02		; System time in VBL units
.int_0x40::
	.blkw	0x08
.int_0x48::
	.blkw	0x08
.int_0x50::
	.blkw	0x08
.int_0x58::
	.blkw	0x08
.int_0x60::
	.blkw	0x08

	;; Runtime library
	.area	_CODE

	;; Call the initialization function for the mode specified in HL
.set_mode::
	LD	A,L
	LD	(.mode),A

	;; AND to get rid of the extra flags
	AND	#0x03
	LD	L,A
	LD	BC,#.MODE_TABLE
	SLA	L		; Multiply mode by 4
	SLA	L
	ADD	HL,BC
	JP	(HL)		; Jump to initialization routine

	;; Add interrupt routine in BC to the interrupt list
.add_VBL::
	LD	HL,#.int_0x40
	JP	.add_int
.add_LCD::
	LD	HL,#.int_0x48
	JP	.add_int
.add_TIM::
	LD	HL,#.int_0x50
	JP	.add_int
.add_SIO::
	LD	HL,#.int_0x58
	JP	.add_int
.add_JOY::
	LD	HL,#.int_0x60
	JP	.add_int

	;; Remove interrupt BC from interrupt list HL if it exists
	;; Abort if a 0000 is found (end of list)
	;; Will only remove last int on list
.remove_int::
1$:
	LD	A,(HL+)
	LD	E,A
	LD	D,(HL)
	OR	D
	RET	Z		; No interrupt found

	LD	A,E
	CP	C
	JR	NZ,1$
	LD	A,D
	CP	B
	JR	NZ,1$

	XOR	A
	LD	(HL-),A
	LD	(HL),A
	INC	A		; Clear Z flag
	RET
	
	;; Add interrupt routine in BC to the interrupt list in HL
.add_int::
1$:
	LD	A,(HL+)
	OR	(HL)
	JR	Z,2$
	INC	HL
	JR	1$
2$:
	LD	(HL),B
	DEC	HL
	LD	(HL),C
	RET

	
	;; VBlank interrupt
.vbl:
	LD	HL,#.sys_time
	INC	(HL)
	JR	NZ,2$
	INC	HL
	INC	(HL)
2$:	
	CALL	.refresh_OAM

	.if	0
	;; Verify that only one VBlank interrupt occured
	LD	A,(.vbl_done)
	OR	A
	JR	Z,1$
	LDH	A,(.BGP)
	CPL
	LDH	(.BGP),A
1$:
	.endif

	LD	A,#0x01
	LD	(.vbl_done),A
	RET

	;; Wait for VBL interrupt to be finished
.wait_vbl_done::
_wait_vbl_done::
	;; Check if the screen is on
	LDH	A,(.LCDC)
	ADD	A
	RET	NC		; Return if screen is off
1$:
	HALT			; Wait for any interrupt
	NOP			; HALT sometimes skips the next instruction
	LD	A,(.vbl_done)	; Was it a VBlank interrupt?
	;; Warning: we may lose a VBlank interrupt, if it occurs now
	OR	A
	JR	Z,1$		; No: back to sleep!

	XOR	A
	LD	(.vbl_done),A
	RET

.display_off::
_display_off::
	;; Check if the screen is on
	LDH	A,(.LCDC)
	ADD	A
	RET	NC		; Return if screen is off
1$:				; We wait for the *NEXT* VBL 
	LDH	A,(.LY)
	CP	#0x92		; Smaller than or equal to 0x91?
	JR	NC,1$		; Loop until smaller than or equal to 0x91
2$:
	LDH	A,(.LY)
	CP	#0x91		; Bigger than 0x90?
	JR	C,2$		; Loop until bigger than 0x90

	LDH	A,(.LCDC)
	AND	#0b01111111
	LDH	(.LCDC),A	; Turn off screen
	RET

	;; Copy OAM data to OAM RAM
.start_refresh_OAM:
	LD	A,#>.OAM
	LDH	(.DMA),A	; Put A into DMA registers
	LD	A,#0x28		; We need to wait 160 ns
1$:
	DEC	A
	JR	NZ,1$
	RET
.end_refresh_OAM:

	;; Serial interrupt
.serial_IO::
	LD	A,(__io_status) ; Get status

	CP	#.IO_RECEIVING
	JR	NZ,10$

	;; Receiving data
	LDH	A,(.SB)		; Get data byte
	LD	(__io_in),A	; Store it
	LD	A,#.IO_IDLE
	JR	11$

10$:

	CP	#.IO_SENDING
	JR	NZ,99$

	;; Sending data
	LDH	A,(.SB)		; Get data byte
	CP	#.DT_RECEIVING
	JR	Z,11$
	LD	A,#.IO_ERROR
	JR	12$
11$:
	LD	A,#.IO_IDLE
12$:
	LD	(__io_status),A ; Store status

	XOR	A
	LDH	(.SC),A		; Use external clock
	LD	A,#.DT_IDLE
	LDH	(.SB),A		; Reply with IDLE byte
99$:
	LD	A,#0x80
	LDH	(.SC),A		; Enable transfer with external clock
	RET

_mode::
	LDA	HL,2(SP)	; Skip return address
	LD	L,(HL)
	LD	H,#0x00
	CALL	.set_mode
	RET

_get_mode::
	LD	HL,#.mode
	LD	E,(HL)
	RET
	
_enable_interrupts::
	EI
	RET

_disable_interrupts::
	DI
	RET

.reset::
_reset::
	LD	A,(__cpu)
	JP	.code_start

_set_interrupts::
	DI
	LDA	HL,2(SP)	; Skip return address
	XOR	A
	LDH	(.IF),A		; Clear pending interrupts
	LD	A,(HL)
	LDH	(.IE),A
	EI			; Enable interrupts
	RET

_add_VBL::
	PUSH	BC
	LDA	HL,4(SP)	; Skip return address and registers
	LD	C,(HL)
	INC	HL
	LD	B,(HL)
	CALL	.add_VBL
	POP	BC
	RET

_add_LCD::
	PUSH	BC
	LDA	HL,4(SP)	; Skip return address and registers
	LD	C,(HL)
	INC	HL
	LD	B,(HL)
	CALL	.add_LCD
	POP	BC
	RET

_add_TIM::
	PUSH	BC
	LDA	HL,4(SP)	; Skip return address and registers
	LD	C,(HL)
	INC	HL
	LD	B,(HL)
	CALL	.add_TIM
	POP	BC
	RET

_add_SIO::
	PUSH	BC
	LDA	HL,4(SP)	; Skip return address and registers
	LD	C,(HL)
	INC	HL
	LD	B,(HL)
	CALL	.add_SIO
	POP	BC
	RET

_add_JOY::
	PUSH	BC
	LDA	HL,4(SP)	; Skip return address and registers
	LD	C,(HL)
	INC	HL
	LD	B,(HL)
	CALL	.add_JOY
	POP	BC
	RET

	.area	_HEAP
_malloc_heap_start::
