import agon.console
import agon.keyboard
import agon.sprites
import agon.vdp

const SCREEN_W: u16 = 320
const PLAYER_TOP: u16 = 210
const PLAYER_Y: u16 = 218
const PLAYER_STEP: u16 = 8
const BULLET_STEP: u16 = 12
const ALIEN_COLS: u8 = 5
const ALIEN_ROWS: u8 = 3
const ALIEN_COUNT: u8 = 15
const ALIEN_SPACING_X: u16 = 38
const ALIEN_SPACING_Y: u16 = 24
const ALIEN_MOVE_RATE: u8 = 4
const SPRITE_COUNT: u8 = 17

const PLAYER_BITMAP: u8 = 0
const BULLET_BITMAP: u8 = 1
const ALIEN_BITMAP: u8 = 2
const PLAYER_SPRITE: u8 = 0
const BULLET_SPRITE: u8 = 1
const FIRST_ALIEN_SPRITE: u8 = 2

global player_x: u16 = 152
global bullet_x: u16 = 0
global bullet_y: u16 = 0
global bullet_active: u8 = 0
global alien_base_x: u16 = 46
global alien_base_y: u16 = 44
global alien_dir: u8 = 1
global alien_tick: u8 = 0
global remaining: u8 = ALIEN_COUNT
global alive: [u8; ALIEN_COUNT] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

fn init_screen() {
    console.mode(8)
    console.clear()
    console.cursor_enable(0)
    vdp.set_logical_coordinates()
}

fn draw_hud() {
    console.tab(0, 0)
    console.color(vdp.COLOR_WHITE)
    console.print("EZRA INVADERS  A/D MOVE  SPACE FIRE  Q QUIT")
}

fn alien_index(row: u8, col: u8) -> u8 {
    return (row * ALIEN_COLS) + col
}

fn alien_x(col: u8) -> u16 {
    return alien_base_x + (cast<u16>(col) * ALIEN_SPACING_X)
}

fn alien_y(row: u8) -> u16 {
    return alien_base_y + (cast<u16>(row) * ALIEN_SPACING_Y)
}

fn alien_sprite_id(index: u8) -> u8 {
    return FIRST_ALIEN_SPRITE + index
}

fn draw_player_shape(x: u16, y: u16) {
    vdp.rectangle(x, y + 16, x + 24, y + 8)
    vdp.line(x + 4, y + 8, x + 12, y)
    vdp.line(x + 20, y + 8, x + 12, y)
}

fn draw_bullet_shape(x: u16, y: u16) {
    vdp.line(x + 1, y, x + 1, y + 8)
}

fn draw_sprite_row(x0: u16, x1: u16, y: u16) {
    vdp.line(x0, y, x1, y)
    vdp.line(x0, y + 1, x1, y + 1)
}

fn draw_alien_shape(x: u16, y: u16) {
    draw_sprite_row(x + 4, x + 6, y)
    draw_sprite_row(x + 16, x + 18, y)
    draw_sprite_row(x + 6, x + 8, y + 2)
    draw_sprite_row(x + 14, x + 16, y + 2)
    draw_sprite_row(x + 4, x + 18, y + 4)
    draw_sprite_row(x + 2, x + 6, y + 6)
    draw_sprite_row(x + 8, x + 14, y + 6)
    draw_sprite_row(x + 16, x + 20, y + 6)
    draw_sprite_row(x, x + 22, y + 8)
    draw_sprite_row(x, x + 2, y + 10)
    draw_sprite_row(x + 4, x + 18, y + 10)
    draw_sprite_row(x + 20, x + 22, y + 10)
    draw_sprite_row(x, x + 2, y + 12)
    draw_sprite_row(x + 4, x + 6, y + 12)
    draw_sprite_row(x + 16, x + 18, y + 12)
    draw_sprite_row(x + 20, x + 22, y + 12)
    draw_sprite_row(x + 6, x + 10, y + 14)
    draw_sprite_row(x + 14, x + 18, y + 14)
}

fn capture_current_bitmap(id: u8, left: u16, top: u16, right: u16, bottom: u16) {
    vdp.move_to(left, top)
    vdp.move_to(right, bottom)
    sprites.capture_bitmap(id)
}

fn build_sprite_bitmaps() {
    sprites.enable_hardware_sprites()

    vdp.gcol(vdp.GCOL_SET, vdp.COLOR_CYAN)
    draw_player_shape(8, 32)
    capture_current_bitmap(PLAYER_BITMAP, 8, 32, 32, 48)

    vdp.gcol(vdp.GCOL_SET, vdp.COLOR_YELLOW)
    draw_bullet_shape(44, 32)
    capture_current_bitmap(BULLET_BITMAP, 44, 32, 46, 40)

    vdp.gcol(vdp.GCOL_SET, vdp.COLOR_GREEN)
    draw_alien_shape(56, 32)
    capture_current_bitmap(ALIEN_BITMAP, 56, 32, 78, 47)

    console.clear()
}

fn setup_one_sprite(id: u8, bitmap_id: u8) {
    sprites.select_sprite(id)
    sprites.clear_frames()
    sprites.add_frame(bitmap_id)
    sprites.select_frame(0)
    sprites.set_hardware()
    sprites.show()
}

fn setup_sprites() {
    setup_one_sprite(PLAYER_SPRITE, PLAYER_BITMAP)
    setup_one_sprite(BULLET_SPRITE, BULLET_BITMAP)
    sprites.hide()

    let index: u8 = 0
    while index < ALIEN_COUNT {
        setup_one_sprite(alien_sprite_id(index), ALIEN_BITMAP)
        index += 1
    }

    sprites.activate(SPRITE_COUNT)
}

fn sync_player_sprite() {
    sprites.select_sprite(PLAYER_SPRITE)
    sprites.move_sprite_to(player_x, PLAYER_TOP)
    sprites.show()
}

fn sync_bullet_sprite() {
    sprites.select_sprite(BULLET_SPRITE)
    if bullet_active == 0 {
        sprites.hide()
        return
    }

    sprites.move_sprite_to(bullet_x - 1, bullet_y - 8)
    sprites.show()
}

fn sync_alien_sprites() {
    let row: u8 = 0
    while row < ALIEN_ROWS {
        let col: u8 = 0
        while col < ALIEN_COLS {
            let index: u8 = alien_index(row, col)
            sprites.select_sprite(alien_sprite_id(index))
            if alive[index] != 0 {
                sprites.move_sprite_to(alien_x(col), alien_y(row))
                sprites.show()
            } else {
                sprites.hide()
            }
            col += 1
        }
        row += 1
    }
}

fn sync_sprites() {
    sync_player_sprite()
    sync_bullet_sprite()
    sync_alien_sprites()
    sprites.update()
}

fn fire_bullet() {
    if bullet_active != 0 {
        return
    }

    bullet_active = 1
    bullet_x = player_x + 12
    bullet_y = PLAYER_Y - 12
}

fn handle_input() -> u8 {
    let key: u8 = keyboard.ascii()

    if key == 'q' || key == 'Q' {
        return 1
    }

    if key == 'a' || key == 'A' || key == 'z' || key == 'Z' {
        if player_x > PLAYER_STEP {
            player_x -= PLAYER_STEP
        }
    }

    if key == 'd' || key == 'D' || key == 'x' || key == 'X' {
        if player_x < SCREEN_W - 32 {
            player_x += PLAYER_STEP
        }
    }

    if key == ' ' {
        fire_bullet()
    }

    keyboard.clear_state()
    return 0
}

fn update_bullet() {
    if bullet_active == 0 {
        return
    }

    if bullet_y > BULLET_STEP {
        bullet_y -= BULLET_STEP
    } else {
        bullet_active = 0
    }
}

fn bullet_hits_alien(row: u8, col: u8) -> u8 {
    let x: u16 = alien_x(col)
    let y: u16 = alien_y(row)
    if bullet_x >= x && bullet_x <= x + 22 && bullet_y >= y && bullet_y <= y + 16 {
        return 1
    }
    return 0
}

fn check_bullet_collisions() {
    if bullet_active == 0 {
        return
    }

    let row: u8 = 0
    while row < ALIEN_ROWS {
        let col: u8 = 0
        while col < ALIEN_COLS {
            let index: u8 = alien_index(row, col)
            if alive[index] != 0 && bullet_hits_alien(row, col) != 0 {
                alive[index] = 0
                remaining -= 1
                bullet_active = 0
                return
            }
            col += 1
        }
        row += 1
    }
}

fn step_aliens() {
    alien_tick += 1
    if alien_tick < ALIEN_MOVE_RATE {
        return
    }
    alien_tick = 0

    if alien_dir != 0 {
        if alien_base_x < 138 {
            alien_base_x += 6
        } else {
            alien_dir = 0
            alien_base_y += 10
        }
        return
    }

    if alien_base_x > 12 {
        alien_base_x -= 6
    } else {
        alien_dir = 1
        alien_base_y += 10
    }
}

fn update_game() {
    update_bullet()
    check_bullet_collisions()
    step_aliens()
}

fn delay_frame() {
    let outer: u16 = 0
    while outer < 28 {
        let inner: u16 = 0
        while inner < 700 {
            asm volatile {
                "nop"
            }
            inner += 1
        }
        outer += 1
    }
}

fn show_win() {
    sprites.reset_sprites()
    console.clear()
    console.color(vdp.COLOR_YELLOW)
    console.print("YOU SAVED AGON!\n")
}

fn show_loss() {
    sprites.reset_sprites()
    console.clear()
    console.color(vdp.COLOR_RED)
    console.print("INVADERS LANDED.\n")
}

fn main() {
    init_screen()
    build_sprite_bitmaps()
    setup_sprites()
    draw_hud()
    sync_sprites()
    keyboard.clear_state()

    loop {
        if handle_input() != 0 {
            sprites.reset_sprites()
            return
        }

        update_game()
        sync_sprites()

        if remaining == 0 {
            show_win()
            return
        }

        if alien_base_y > 178 {
            show_loss()
            return
        }

        delay_frame()
    }
}
