pub const VDU_LF: u8 = 10
pub const VDU_CR: u8 = 13
pub const VDU_BELL: u8 = 7
pub const VDU_CURSOR_LEFT: u8 = 8
pub const VDU_CURSOR_RIGHT: u8 = 9
pub const VDU_CLEAR: u8 = 12
pub const VDU_CURSOR_UP: u8 = 11
pub const VDU_CLG: u8 = 16
pub const VDU_COLOR: u8 = 17
pub const VDU_GCOL: u8 = 18
pub const VDU_PALETTE: u8 = 19
pub const VDU_MODE: u8 = 22
pub const VDU_SYSTEM: u8 = 23
pub const VDU_VIEWPORT: u8 = 24
pub const VDU_PLOT: u8 = 25
pub const VDU_RESET_VIEWPORT: u8 = 26
pub const VDU_TEXT_VIEWPORT: u8 = 28
pub const VDU_ORIGIN: u8 = 29
pub const VDU_HOME: u8 = 30
pub const VDU_TAB: u8 = 31

pub const COLOR_BLACK: u8 = 0
pub const COLOR_RED: u8 = 1
pub const COLOR_GREEN: u8 = 2
pub const COLOR_YELLOW: u8 = 3
pub const COLOR_BLUE: u8 = 4
pub const COLOR_MAGENTA: u8 = 5
pub const COLOR_CYAN: u8 = 6
pub const COLOR_WHITE: u8 = 7
pub const COLOR_BRIGHT_BLACK: u8 = 8
pub const COLOR_BRIGHT_RED: u8 = 9
pub const COLOR_BRIGHT_GREEN: u8 = 10
pub const COLOR_BRIGHT_YELLOW: u8 = 11
pub const COLOR_BRIGHT_BLUE: u8 = 12
pub const COLOR_BRIGHT_MAGENTA: u8 = 13
pub const COLOR_BRIGHT_CYAN: u8 = 14
pub const COLOR_BRIGHT_WHITE: u8 = 15

pub const MODE_8: u8 = 8
pub const MODE_8_WIDTH: u16 = 320
pub const MODE_8_HEIGHT: u16 = 240
pub const MODE_8_RIGHT: u16 = 319
pub const MODE_8_BOTTOM: u16 = 239

pub const GCOL_SET: u8 = 0
pub const GCOL_OR: u8 = 1
pub const GCOL_AND: u8 = 2
pub const GCOL_EOR: u8 = 3
pub const GCOL_INVERT: u8 = 4
pub const GCOL_NOP: u8 = 5

pub const PLOT_MOVE_ABS: u8 = 0x04
pub const PLOT_LINE_ABS: u8 = 0x05
pub const PLOT_POINT: u8 = 0x45
pub const PLOT_CIRCLE_CENTER: u8 = 0x94
pub const PLOT_CIRCLE_RADIUS: u8 = 0x91
pub port VDP_EMULATOR_EXIT_PORT: u8 = 0x00

pub fn vdu(ch: u8) {
    asm volatile(in ch: u8 as reg8, clobber af, clobber bc, clobber de, clobber hl, clobber flags) {
        "ld a, {ch}"
        "rst.lis 10h"
    }
}

pub fn vdu_u16(value: u16) {
    vdu(cast<u8>(value))
    vdu(cast<u8>(value >> 8))
}

pub fn vdu_newline() {
    vdu(VDU_CR)
    vdu(VDU_LF)
}

pub fn clear_screen() {
    vdu(VDU_CLEAR)
}

pub fn clg() {
    vdu(VDU_CLG)
}

pub fn vdu_bell() {
    vdu(VDU_BELL)
}

pub fn screen_mode(number: u8) {
    vdu(VDU_MODE)
    vdu(number)
}

pub fn mode_8() {
    screen_mode(MODE_8)
    set_logical_coordinates()
}

pub fn cursor_home() {
    vdu(VDU_HOME)
}

pub fn cursor_left() {
    vdu(VDU_CURSOR_LEFT)
}

pub fn cursor_right() {
    vdu(VDU_CURSOR_RIGHT)
}

pub fn cursor_down() {
    vdu(VDU_LF)
}

pub fn cursor_up() {
    vdu(VDU_CURSOR_UP)
}

pub fn cursor_tab(x: u8, y: u8) {
    vdu(VDU_TAB)
    vdu(x)
    vdu(y)
}

pub fn vdp_cursor_enable(enabled: u8) {
    vdu(VDU_SYSTEM)
    vdu(1)
    vdu(enabled)
}

pub fn text_color(value: u8) {
    vdu(VDU_COLOR)
    vdu(value)
}

pub fn text_background(value: u8) {
    vdu(VDU_COLOR)
    vdu(value + 128)
}

pub fn gcol(op: u8, value: u8) {
    vdu(VDU_GCOL)
    vdu(op)
    vdu(value)
}

pub fn draw_color(value: u8) {
    gcol(GCOL_SET, value)
}

pub fn clear_graphics_color(value: u8) {
    draw_color(value)
    clg()
}

pub fn palette(logical: u8, physical: u8, red: u8, green: u8, blue: u8) {
    vdu(VDU_PALETTE)
    vdu(logical)
    vdu(physical)
    vdu(red)
    vdu(green)
    vdu(blue)
}

pub fn set_pixel_coordinates() {
    vdu(VDU_SYSTEM)
    vdu(0)
    vdu(0xC0)
    vdu(1)
}

pub fn set_logical_coordinates() {
    vdu(VDU_SYSTEM)
    vdu(0)
    vdu(0xC0)
    vdu(0)
}

pub fn plot(plot_mode: u8, x: u16, y: u16) {
    vdu(VDU_PLOT)
    vdu(plot_mode)
    vdu_u16(x)
    vdu_u16(y)
}

pub fn move_to(x: u16, y: u16) {
    plot(PLOT_MOVE_ABS, x, y)
}

pub fn line_to(x: u16, y: u16) {
    plot(PLOT_LINE_ABS, x, y)
}

pub fn point(x: u16, y: u16) {
    plot(PLOT_POINT, x, y)
}

pub fn line(x0: u16, y0: u16, x1: u16, y1: u16) {
    move_to(x0, y0)
    line_to(x1, y1)
}

pub fn horizontal_line(left: u16, right: u16, y: u16) {
    line(left, y, right, y)
}

pub fn vertical_line(x: u16, bottom: u16, top: u16) {
    line(x, bottom, x, top)
}

pub fn rectangle(left: u16, bottom: u16, right: u16, top: u16) {
    move_to(left, bottom)
    line_to(right, bottom)
    line_to(right, top)
    line_to(left, top)
    line_to(left, bottom)
}

pub fn filled_rectangle(left: u16, bottom: u16, right: u16, top: u16) {
    if top < bottom {
        return
    }

    let y: u16 = bottom
    while y <= top {
        horizontal_line(left, right, y)
        y += 1
    }
}

pub fn framed_rectangle(left: u16, bottom: u16, right: u16, top: u16, fill_color: u8, border_color: u8) {
    gcol(GCOL_SET, fill_color)
    filled_rectangle(left, bottom, right, top)
    gcol(GCOL_SET, border_color)
    rectangle(left, bottom, right, top)
}

pub fn triangle(x0: u16, y0: u16, x1: u16, y1: u16, x2: u16, y2: u16) {
    move_to(x0, y0)
    line_to(x1, y1)
    line_to(x2, y2)
    line_to(x0, y0)
}

pub fn circle(x: u16, y: u16, radius: u16) {
    plot(PLOT_CIRCLE_CENTER, x, y)
    plot(PLOT_CIRCLE_RADIUS, radius, 0)
}

pub fn graphics_viewport(left: u16, bottom: u16, right: u16, top: u16) {
    vdu(VDU_VIEWPORT)
    vdu_u16(left)
    vdu_u16(bottom)
    vdu_u16(right)
    vdu_u16(top)
}

pub fn reset_viewport() {
    vdu(VDU_RESET_VIEWPORT)
}

pub fn text_viewport(left: u8, bottom: u8, right: u8, top: u8) {
    vdu(VDU_TEXT_VIEWPORT)
    vdu(left)
    vdu(bottom)
    vdu(right)
    vdu(top)
}

pub fn origin(x: u16, y: u16) {
    vdu(VDU_ORIGIN)
    vdu_u16(x)
    vdu_u16(y)
}

pub fn clear_flags(mask: u8) {
    asm volatile(in mask: u8 as reg8, clobber a, clobber c, clobber af, clobber flags, clobber memory) {
        "ld c, a"
        "ld a, 40h"
        "rst.lis 08h"
    }
}

pub fn wait_for_flags(mask: u8) {
    asm volatile(in mask: u8 as reg8, clobber a, clobber c, clobber af, clobber flags, clobber memory) {
        "ld c, a"
        "ld a, 41h"
        "rst.lis 08h"
    }
}

pub fn busy_delay(outer_count: u16, inner_count: u16) {
    let outer: u16 = 0
    while outer < outer_count {
        let inner: u16 = 0
        while inner < inner_count {
            asm volatile {
                "nop"
            }
            inner += 1
        }
        outer += 1
    }
}

pub fn frame_delay() {
    busy_delay(28, 700)
}

pub fn vdp_exit_emulator(code: u8) {
    out VDP_EMULATOR_EXIT_PORT, code
}
