#if not(GAMEBOY)
#warn gb_hardware module should be only used on Game Boy targets
#endif

#pragma zilog_syntax


asm void __start() @ $150 {
    di

    ld sp, $DFFF
    xor a
    ldh (reg_irq_flag), a
    ? ld a,ief_vblank
    ldh (reg_irq_enable), a


    ? call disable_lcd
    ? call clear_ram

    // copy the DMA routine to hi RAM
    ld hl, __sprite_dma_template
    ld de, __sprite_dma_actual
    ld c, __sprite_dma_actual.length
    __copy_sprite_dma_template:
    ld a,(hli)
    ld (de),a
    inc de
    dec c
    jr nz, __copy_sprite_dma_template


    ? jp main
    ? jp __start
}

asm void clear_ram() {
    ? xor a
    ? ld hl,$c000
    ? ld bc,$1ff0
    ? call memset
    ? ld hl,$8000
    ? ld bc,$2000
    ? call memset
    ? ld hl,$fe00
    ? ld bc,$a0
    ? call memset
    ? ld hl,$ff80
    ? ld bc,$7f
    ? call memset
    ? ret
}

inline asm void memset(word hl, word bc) {
    ? inc c
    ? inc b
    ? jr __memzero_start
    __memzero_loop:
    ld (hli), a
    __memzero_start:
    dec c
    jr nz, __memzero_loop
    dec b
    jr nz, __memzero_loop
    ? ret
}

interrupt void __on_vblank(){
    on_vblank()
}
interrupt void __on_lcdc(){
    on_lcdc()
}
interrupt void __on_timer(){
    on_timer()
}
interrupt void __on_serial(){
    on_serial()
}
interrupt void __on_joypad(){
    on_joypad()
}

// TODO: optimize?
const array __vectors @ $40 = [
    $c3, __on_vblank.addr.lo, __on_vblank.addr.hi, 0,0,0,0,0,
    $c3, __on_lcdc.addr.lo, __on_lcdc.addr.hi, 0,0,0,0,0,
    $c3, __on_timer.addr.lo, __on_timer.addr.hi, 0,0,0,0,0,
    $c3, __on_serial.addr.lo, __on_serial.addr.hi, 0,0,0,0,0,
    $c3, __on_joypad.addr.lo, __on_joypad.addr.hi, 0,0,0,0,0
]

array oam[$a0] @$fe00

volatile byte reg_joypad @$ff00
volatile byte reg_irq_flag @$ff0f
volatile byte reg_lcd_ctrl @$ff40
volatile byte reg_lcd_status @$ff41
volatile byte reg_scroll_y @$ff42
volatile byte reg_scroll_x @$ff43
volatile byte reg_dma @$ff46
volatile byte reg_bg_palette @$ff47
volatile byte reg_obj0_palette @$ff48
volatile byte reg_obj1_palette @$ff49
volatile byte reg_window_y @$ff4A
volatile byte reg_window_x @$ff4B
volatile byte reg_vram_bank @$ff4f
volatile byte reg_irq_enable @$ffff

const byte lcd_on = $80
const byte lcd_off = 0
const byte lcd_win_9c00 = $40
const byte lcd_win_9800 = 0
const byte lcd_win_on = $20
const byte lcd_win_off = 0
const byte lcd_tile_8000 = $10
const byte lcd_tile_8800 = 0
const byte lcd_map_9c00 = 8
const byte lcd_map_9800 = 0
const byte lcd_obj_16 = 4
const byte lcd_obj_8 = 0
const byte lcd_obj_on = 2
const byte lcd_obj_off = 0
const byte lcd_bg_on = 1
const byte lcd_bg_off = 1

const byte ief_vblank = 1

segment(hiram)
array __sprite_dma_actual[8]

asm void __sprite_dma_template() {
    ldh (reg_dma),a
    ld a, $28
__sprite_dma_wait:
    dec a
    jr nz, __sprite_dma_wait
    ret
    __sprite_dma_end:
}

asm void sprite_dma(byte a) {
    jp __sprite_dma_actual
}

asm byte wait_for_vblank() {
    ld hl, reg_irq_flag
    __wait_for_vblank_loop:
    ld a, ief_vblank
    halt
    and (hl)
    jr z, __wait_for_vblank_loop
    ? ld a, $ff ^ ief_vblank
    ? and (hl)
    ? ret
}

asm void disable_lcd() {
    ? call wait_for_vblank
    ldh a,(reg_lcd_ctrl)
    res 7,a
    ldh (reg_lcd_ctrl),a
    ? ret
}
asm void enable_lcd() {
    ? call wait_for_vblank
    ldh a,(reg_lcd_ctrl)
    set 7,a
    ldh (reg_lcd_ctrl),a
    ? ret
}