pub const CURRENT_BUFFER: u16 = 65535
pub const ALL_BUFFERS: u16 = 65535
pub const DEFAULT_CHUNK_SIZE: u16 = 1024

pub const ADJUST_NOT: u8 = 0
pub const ADJUST_NEGATE: u8 = 1
pub const ADJUST_SET: u8 = 2
pub const ADJUST_ADD: u8 = 3
pub const ADJUST_ADD_CARRY: u8 = 4
pub const ADJUST_AND: u8 = 5
pub const ADJUST_OR: u8 = 6
pub const ADJUST_XOR: u8 = 7

pub const ADJUST_ADVANCED_OFFSET: u8 = 0x10
pub const ADJUST_OPERAND_FROM_BUFFER: u8 = 0x20
pub const ADJUST_MULTIPLE_TARGETS: u8 = 0x40
pub const ADJUST_MULTIPLE_OPERANDS: u8 = 0x80

pub const REVERSE_VALUE_8: u8 = 0
pub const REVERSE_VALUE_16: u8 = 1
pub const REVERSE_VALUE_32: u8 = 2
pub const REVERSE_VALUE_CUSTOM: u8 = 3
pub const REVERSE_WITHIN_CHUNKS: u8 = 4
pub const REVERSE_BLOCKS: u8 = 8

fn buffers_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"
    }
}

fn buffers_vdu_u16(value: u16) {
    buffers_vdu(cast<u8>(value))
    buffers_vdu(cast<u8>(value >> 8))
}

fn buffers_vdu_u24(value: u24) {
    buffers_vdu(cast<u8>(value))
    buffers_vdu(cast<u8>(value >> 8))
    buffers_vdu(cast<u8>(value >> 16))
}

fn buffers_command(buffer_id: u16, command_id: u8) {
    buffers_vdu(23)
    buffers_vdu(0)
    buffers_vdu(0xA0)
    buffers_vdu_u16(buffer_id)
    buffers_vdu(command_id)
}

pub fn buffer_write_block(buffer_id: u16, data: ptr<u8>, len: u16) {
    buffers_command(buffer_id, 0)
    buffers_vdu_u16(len)

    let index: u16 = 0
    while index < len {
        buffers_vdu(*(data + index))
        index += 1
    }
}

pub fn buffer_write_block_chunked(buffer_id: u16, data: ptr<u8>, len: u24, chunk_size: u16) {
    let offset: u24 = 0
    let actual_chunk_size: u16 = chunk_size
    if actual_chunk_size == 0 {
        actual_chunk_size = DEFAULT_CHUNK_SIZE
    }

    while offset < len {
        let chunk_len: u16 = actual_chunk_size
        let remaining: u24 = len - offset
        if remaining < cast<u24>(chunk_len) {
            chunk_len = cast<u16>(remaining)
        }

        buffer_write_block(buffer_id, data + offset, chunk_len)
        offset += cast<u24>(chunk_len)
    }
}

pub fn buffer_call(buffer_id: u16) {
    buffers_command(buffer_id, 1)
}

pub fn buffer_clear(buffer_id: u16) {
    buffers_command(buffer_id, 2)
}

pub fn buffer_clear_all() {
    buffer_clear(ALL_BUFFERS)
}

pub fn buffer_create_writable(buffer_id: u16, len: u16) {
    buffers_command(buffer_id, 3)
    buffers_vdu_u16(len)
}

pub fn buffer_set_output_stream(buffer_id: u16) {
    buffers_command(buffer_id, 4)
}

pub fn buffer_detach_output_stream() {
    buffer_set_output_stream(CURRENT_BUFFER)
}

pub fn buffer_restore_output_stream() {
    buffer_set_output_stream(0)
}

pub fn buffer_adjust_unary(buffer_id: u16, operation: u8, offset: u16) {
    buffers_command(buffer_id, 5)
    buffers_vdu(operation)
    buffers_vdu_u16(offset)
}

pub fn buffer_adjust_u8(buffer_id: u16, operation: u8, offset: u16, operand: u8) {
    buffers_command(buffer_id, 5)
    buffers_vdu(operation)
    buffers_vdu_u16(offset)
    buffers_vdu(operand)
}

pub fn buffer_adjust_range_u8(buffer_id: u16, operation: u8, offset: u16, count: u16, operand: u8) {
    buffers_command(buffer_id, 5)
    buffers_vdu(operation | ADJUST_MULTIPLE_TARGETS)
    buffers_vdu_u16(offset)
    buffers_vdu_u16(count)
    buffers_vdu(operand)
}

pub fn buffer_adjust_advanced_u8(buffer_id: u16, operation: u8, offset: u24, operand: u8) {
    buffers_command(buffer_id, 5)
    buffers_vdu(operation | ADJUST_ADVANCED_OFFSET)
    buffers_vdu_u24(offset)
    buffers_vdu(operand)
}

pub fn buffer_adjust_advanced_block_u8(buffer_id: u16, operation: u8, offset: u24, block: u16, operand: u8) {
    buffers_command(buffer_id, 5)
    buffers_vdu(operation | ADJUST_ADVANCED_OFFSET)
    buffers_vdu_u24(offset | 0x800000u24)
    buffers_vdu_u16(block)
    buffers_vdu(operand)
}

pub fn buffer_jump(buffer_id: u16) {
    buffers_command(buffer_id, 7)
}

pub fn buffer_jump_to_end() {
    buffer_jump(CURRENT_BUFFER)
}

pub fn buffer_jump_offset(buffer_id: u16, offset: u24) {
    buffers_command(buffer_id, 9)
    buffers_vdu_u24(offset)
}

pub fn buffer_call_offset(buffer_id: u16, offset: u24) {
    buffers_command(buffer_id, 11)
    buffers_vdu_u24(offset)
}

pub fn buffer_consolidate(buffer_id: u16) {
    buffers_command(buffer_id, 14)
}

pub fn buffer_split(buffer_id: u16, block_size: u16) {
    buffers_command(buffer_id, 15)
    buffers_vdu_u16(block_size)
}

pub fn buffer_split_to_sequential_buffers(buffer_id: u16, block_size: u16, target_buffer_id: u16) {
    buffers_command(buffer_id, 17)
    buffers_vdu_u16(block_size)
    buffers_vdu_u16(target_buffer_id)
}

pub fn buffer_split_by_width(buffer_id: u16, width: u16, block_count: u16) {
    buffers_command(buffer_id, 18)
    buffers_vdu_u16(width)
    buffers_vdu_u16(block_count)
}

pub fn buffer_split_by_width_to_sequential_buffers(buffer_id: u16, width: u16, block_count: u16, target_buffer_id: u16) {
    buffers_command(buffer_id, 20)
    buffers_vdu_u16(width)
    buffers_vdu_u16(block_count)
    buffers_vdu_u16(target_buffer_id)
}

pub fn buffer_spread_blocks_to_sequential_buffers(buffer_id: u16, target_buffer_id: u16) {
    buffers_command(buffer_id, 22)
    buffers_vdu_u16(target_buffer_id)
}

pub fn buffer_reverse_blocks(buffer_id: u16) {
    buffers_command(buffer_id, 23)
}

pub fn buffer_reverse_data(buffer_id: u16, options: u8) {
    buffers_command(buffer_id, 24)
    buffers_vdu(options)
}

pub fn buffer_reverse_data_custom_value_size(buffer_id: u16, options: u8, value_size: u16) {
    buffers_command(buffer_id, 24)
    buffers_vdu(options | REVERSE_VALUE_CUSTOM)
    buffers_vdu_u16(value_size)
}

pub fn buffer_reverse_data_within_chunks(buffer_id: u16, options: u8, chunk_size: u16) {
    buffers_command(buffer_id, 24)
    buffers_vdu(options | REVERSE_WITHIN_CHUNKS)
    buffers_vdu_u16(chunk_size)
}

pub fn buffer_debug_info(buffer_id: u16) {
    buffers_command(buffer_id, 128)
}
