;; NOTE: automatically generated by the build system.
.include "../config/generated.s"

.segment "HEADER"
    ;; Bytes 0-3: NES\0 magic header.
    .byte 'N', 'E', 'S', $1A

    ;; Standard 32KB + 8KB NROM cartridge.
    .byte $02, $01

    ;; Horizontal mirroring, no battery.
    .byte $00

    ;; We use the NES 2.0 header mainly to differentiate between NTSC vs PAL in
    ;; a way that emulators won't ignore it.
    .byte $08

    ;; Blanked out stuff.
    .res $04, $00

    ;; NTSC vs PAL
    .ifdef PAL
        .byte $01
    .else
        .byte $00
    .endif

    ;; Blanked out stuff.
    .res $03, $00

.segment "CODE"

.include "../include/apu.s"
.include "../include/oam.s"
.include "../include/ppu.s"
.include "../include/asm.s"
.include "../include/joypad.s"
.include "../include/globals.s"
.include "../include/debug.s"

.include "assets.s"
.include "background.s"
.include "prng.s"
.include "explosions.s"
.include "player.s"
.include "enemies.s"
.include "bullets.s"
.include "items.s"
.include "title.s"
.include "over.s"
.include "driver.s"
.include "interrupts.s"

;; Pretty standard reset function, nothing crazy.
.proc reset
    ;; Disable interrupts and decimal mode.
    sei
    cld

    ;; Disable APU frame counter.
    ldx #$40
    stx APU::m_frame_counter

    ;; Setup the stack.
    ldx #$FF
    txs

    ;; Disable NMIs and the APU's DMC.
    inx
    stx PPU::m_control
    stx PPU::m_mask
    stx APU::m_dmc

    ;; First PPU wait.
    bit PPU::m_status
@vblankwait1:
    bit PPU::m_status
    bpl @vblankwait1

    ;; Initialize the counter for frame drops before any NMIs can come in.
    .ifdef PARTIAL
        lda #0
        sta Debug::zp_frame_drops
    .endif

    ;; Reset all sprites by simply moving the Y coordinate out of screen.
    lda #$EF
    ldx #0
@sprite_reset_loop:
    sta OAM::m_sprites, x
    inx
    inx
    inx
    inx
    bne @sprite_reset_loop

    ;; DMA setup for sprite reset.
    lda #$00
    sta OAM::m_address
    lda #$02
    sta OAM::m_dma

    ;; Second PPU wait. After that the PPU is stable.
@vblankwait2:
    bit PPU::m_status
    bpl @vblankwait2

    ;; NOTE: palettes are not initialized here as it's going to be one of the
    ;; first things done on `main` code.

    __fallthrough__ main
.endproc

.proc main
    ;; TODO: high score initialization has to happen here.

@init:
    ;; Disable the PPU and zero out variables which shadow PPU registers.
    lda #0
    sta PPU::m_mask
    sta PPU::zp_mask
    sta PPU::zp_control

    ;; Initialize other global variables which the rest of the game assume to
    ;; have zero as their initial values. Note that it's important to have these
    ;; variables defined before
    sta Globals::zp_flags
    sta Globals::zp_multiplayer
    sta Joypad::zp_buttons
    sta Joypad::zp_prev
    sta Player::zp_state

    ;; Initialize the level. We allow the build system to pass its own value for
    ;; this in `LEVEL`, just in case we want to debug the enemy of a specific
    ;; level.
    .ifdef LEVEL
        lda #LEVEL
        sta Globals::zp_level
        and #%00000111
        sta Globals::zp_level_kind
    .else
        sta Globals::zp_level
        sta Globals::zp_level_kind
    .endif

    ;; Initialize the assets for the game. If the first sprite is not
    ;; initialized to zero, then we are not coming from reset() but from
    ;; starting after a "Game over". In this case skip this initialization.
    lda OAM::m_address
    bne @skip_assets
    jsr Assets::init
@skip_assets:

    ;; Initialize some variables from the "Game Over" side of the game.
    jsr Over::init

    ;; Initialize variables from the game's driver that need to be set before
    ;; NMIs start ticking.
    jsr Driver::init_before_nmi

    ;; Initialize some PAL-specific constants.
    .ifdef PAL
        lda #0
        sta Driver::zp_pal_counter
    .endif

    ;; We shadow the PPU control register in memory. Otherwise we go into the
    ;; title as expected.
    jsr Title::init
    lda #%10001000
    sta PPU::zp_control
    sta PPU::m_control

    cli

    ;; Enable back the PPU
    lda #%00011110
    sta PPU::zp_mask
    sta PPU::m_mask

@main_game_loop:
    ;; Select the joypad from the active player and read it.
    lda Globals::zp_multiplayer
    and #$01
    tax
    READ_JOYPAD_X

    lda Globals::zp_flags
    and #%00000011
    beq @title_screen
    cmp #1
    beq @game_screen
    jmp @over

@title_screen:
    ;; If we are in a transitioning state, avoid the update from the title.
    lda Globals::zp_flags
    and #%00000100
    bne @do_switch

    jsr Title::update
    beq @set_flags

@do_switch:
    ;; Start was pressed by the player, switch to the main game.
    jsr Driver::switch
    jmp @set_flags

@game_screen:
    jsr Driver::update
    __fallthrough__ @set_flags

@set_flags:
    lda #%10000000
    ora Globals::zp_flags
    sta Globals::zp_flags
@wait_for_render:
    bit Globals::zp_flags
    bmi @wait_for_render

    ;; Rendering is done, we can perform another iteration of the loop!
    jmp @main_game_loop

@over:
    ;; Display the "Game over" screen if it hasn't been displayed yet. After
    ;; that, if we detect that the user wants to go back to the title screen we
    ;; go back to @init to initialize everything again except for the score
    ;; which should be preserved. Otherwise we go back to the main game loop,
    ;; which effectively means to just sit and wait until for the right player
    ;; input.
    jsr Over::handle
    sta Globals::zp_tmp0

    ;; Wait for the PPU to render the screen.
@set_flags_over:
    lda #%10000000
    ora Globals::zp_flags
    sta Globals::zp_flags
@wait_for_render_over:
    bit Globals::zp_flags
    bmi @wait_for_render_over

    ;; Can the player start over?
    lda Globals::zp_tmp0
    beq @main_game_loop

    ;; We will skip the initialization of the assets so to avoid writing into
    ;; the first nametable when it's just fine. That being said, we still need
    ;; to prepare palettes for the title screen. Do it now before starting over.
    jsr Assets::prepare_for_title_screen

    jmp @init
.endproc

.segment "VECTORS"
    .addr nmi, reset, irq
