mod system;
mod memory;
mod math;
mod clock;
mod input;
mod screen;
mod stream;
mod file;

use bedrock_core::*;
pub use system::ReadOnlyTextBuffer;
pub use screen::{ScreenDimensions, ScreenPosition};

const LEN_PROGRAM_MEMORY: u16 = 0xffff;
const LEN_WORKING_STACK: u8 = 0xff;
const LEN_RETURN_STACK: u8 = 0xff;
const CONNECTED_DEVICES: u16 = 0b_1111_1100_1100_0000;


pub struct StandardDevices {
    pub memory: memory::MemoryDevice,
    pub math: math::MathDevice,
    pub clock: clock::ClockDevice,
    pub input: input::InputDevice,
    pub screen: screen::ScreenDevice,
    pub stream: stream::StreamDevice,
    pub file: file::FileDevice,

    pub name: ReadOnlyTextBuffer,

    pub wake_mask: u16,
    pub wake_id: u8,
}

impl StandardDevices {
    pub fn new() -> Self {
        Self {
            memory: memory::MemoryDevice::new(),
            math: math::MathDevice::new(),
            clock: clock::ClockDevice::new(),
            input: input::InputDevice::new(),
            screen: screen::ScreenDevice::new(),
            stream: stream::StreamDevice::new(),
            file: file::FileDevice::new(),

            name: ReadOnlyTextBuffer::from_text("Bedrock for PC"),

            wake_mask: 0x0000,
            wake_id: 0x00,
        }
    }

    pub fn can_wake(&mut self) -> bool {
        macro_rules! test_wake {
            ($flag:expr, $id:expr, $mask:expr) => {
                if $flag && self.wake_mask & $mask != 0  {
                    $flag = false; self.wake_id = $id; return true }
            };
        }
        self.clock.update_timers();
        // The order here is important. If clock comes first, it could
        // block out all other events with a fast enough recurring timer.
        // It might be preferable to implement a queue system, so that
        // the flags are tested in the opposite order to how recently
        // they were matched.
        test_wake!(self.input.wake_flag,  0x4, 0x0800);
        test_wake!(self.screen.wake_flag, 0x5, 0x0400);
        test_wake!(self.stream.wake_flag, 0x8, 0x0080);
        test_wake!(self.clock.wake_flag,  0x3, 0x1000);
        return false;
    }
}

impl DeviceBus for StandardDevices {
    fn read_u8(&mut self, port: u8) -> u8 {
        macro_rules! read_hh { ($v:expr) => { ($v>>24) as u8 }; }
        macro_rules! read_hl { ($v:expr) => { ($v>>16) as u8 }; }
        macro_rules! read_lh { ($v:expr) => { ($v>>8)  as u8 }; }
        macro_rules! read_ll { ($v:expr) => {  $v      as u8 }; }
        macro_rules! read_h  { ($v:expr) => { ($v>>8)  as u8 }; }
        macro_rules! read_l  { ($v:expr) => {  $v      as u8 }; }
        macro_rules! read_b  { ($b:expr) => { if $b { 0xff } else { 0x00 } }; }
        macro_rules! no_read  { () => { 0x00 }; }

        match port {
            // System
            0x00 => self.name.read_byte(),
            0x01 => self.wake_id,
            0x02 => no_read!(),
            0x03 => no_read!(),
            0x04 => 0x00,
            0x05 => 0x00,
            0x06 => 0x00,
            0x07 => 0x00,
            0x08 => read_h!(LEN_PROGRAM_MEMORY),
            0x09 => read_l!(LEN_PROGRAM_MEMORY),
            0x0A => LEN_WORKING_STACK,
            0x0B => LEN_RETURN_STACK,
            0x0C => read_h!(CONNECTED_DEVICES),
            0x0D => read_l!(CONNECTED_DEVICES),
            0x0E => no_read!(),
            0x0F => no_read!(),
            // Memory
            0x10 => read_h!(self.memory.page_1),
            0x11 => read_l!(self.memory.page_1),
            0x12 => read_h!(self.memory.address_1),
            0x13 => read_l!(self.memory.address_1),
            0x14 => self.memory.read_from_head_1(),
            0x15 => self.memory.read_from_head_1(),
            0x16 => read_h!(self.memory.page_limit),
            0x17 => read_l!(self.memory.page_limit),
            0x18 => read_h!(self.memory.page_2),
            0x19 => read_l!(self.memory.page_2),
            0x1A => read_h!(self.memory.address_2),
            0x1B => read_l!(self.memory.address_2),
            0x1C => self.memory.read_from_head_2(),
            0x1D => self.memory.read_from_head_2(),
            0x1E => no_read!(),
            0x1F => no_read!(),
            // Math
            0x20 => no_read!(),
            0x21 => no_read!(),
            0x22 => no_read!(),
            0x23 => no_read!(),
            0x24 => no_read!(),
            0x25 => no_read!(),
            0x26 => no_read!(),
            0x27 => no_read!(),
            0x28 => read_h!(self.math.multiply_high()),
            0x29 => read_l!(self.math.multiply_high()),
            0x2A => read_h!(self.math.multiply_low()),
            0x2B => read_l!(self.math.multiply_low()),
            0x2C => read_h!(self.math.divide()),
            0x2D => read_l!(self.math.divide()),
            0x2E => read_h!(self.math.modulo()),
            0x2F => read_l!(self.math.modulo()),
            // Clock
            0x30 => self.clock.year(),
            0x31 => self.clock.month(),
            0x32 => self.clock.day(),
            0x33 => self.clock.hour(),
            0x34 => self.clock.minute(),
            0x35 => self.clock.second(),
            0x36 => read_h!(self.clock.update_uptime()),
            0x37 => read_l!(self.clock.uptime),
            0x38 => read_h!(self.clock.update_timer_1()),
            0x39 => read_l!(self.clock.timer_1),
            0x3A => read_h!(self.clock.update_timer_2()),
            0x3B => read_l!(self.clock.timer_2),
            0x3C => read_h!(self.clock.update_timer_3()),
            0x3D => read_l!(self.clock.timer_3),
            0x3E => read_h!(self.clock.update_timer_4()),
            0x3F => read_l!(self.clock.timer_4),
            // Input
            0x40 => read_h!(self.input.pointer_position.x),
            0x41 => read_l!(self.input.pointer_position.x),
            0x42 => read_h!(self.input.pointer_position.y),
            0x43 => read_l!(self.input.pointer_position.y),
            0x44 => read_b!(self.input.pointer_active),
            0x45 => self.input.pointer_buttons,
            0x46 => self.input.read_horizontal_scroll(),
            0x47 => self.input.read_vertical_scroll(),
            0x48 => 0xff,
            0x49 => self.input.text_queue.pop_front().unwrap_or(0),
            0x4A => self.input.modifiers,
            0x4B => self.input.navigation,
            0x4C => self.input.controller_1,
            0x4D => self.input.controller_2,
            0x4E => self.input.controller_3,
            0x4F => self.input.controller_4,
            // Screen
            0x50 => read_h!(self.screen.cursor.x),
            0x51 => read_l!(self.screen.cursor.x),
            0x52 => read_h!(self.screen.cursor.y),
            0x53 => read_l!(self.screen.cursor.y),
            0x54 => read_h!(self.screen.dimensions.width),
            0x55 => read_l!(self.screen.dimensions.width),
            0x56 => read_h!(self.screen.dimensions.height),
            0x57 => read_l!(self.screen.dimensions.height),
            0x58 => no_read!(),
            0x59 => no_read!(),
            0x5A => no_read!(),
            0x5B => no_read!(),
            0x5C => no_read!(),
            0x5D => no_read!(),
            0x5E => no_read!(),
            0x5F => no_read!(),
            // Stream
            // 0x80 => todo!(),
            // 0x81 => todo!(),
            // 0x82 => todo!(),
            // 0x83 => todo!(),
            // 0x84 => todo!(),
            // 0x85 => todo!(),
            // 0x86 => todo!(),
            // 0x87 => todo!(),
            // 0x88 => todo!(),
            // 0x89 => todo!(),
            // 0x8A => todo!(),
            // 0x8B => todo!(),
            // 0x8C => todo!(),
            // 0x8D => todo!(),
            // 0x8E => todo!(),
            // 0x8F => todo!(),
            // File
            0x90 => read_b!(self.file.entry.is_some()),
            0x91 => read_b!(self.file.op_success),
            0x92 => self.file.name_buffer.read_byte(),
            0x93 => read_b!(self.file.entry_type()),
            0x94 => self.file.read_byte(),
            0x95 => self.file.read_byte(),
            0x96 => self.file.read_child_name(),
            0x97 => read_b!(self.file.child_type()),
            0x98 => read_hh!(self.file.pointer()),
            0x99 => read_hl!(self.file.pointer()),
            0x9A => read_lh!(self.file.pointer()),
            0x9B => read_ll!(self.file.pointer()),
            0x9C => read_hh!(self.file.length()),
            0x9D => read_hl!(self.file.length()),
            0x9E => read_lh!(self.file.length()),
            0x9F => read_ll!(self.file.length()),

            _ => unimplemented!("Reading from device port 0x{port:02x}"),
        }
    }

    fn write_u8(&mut self, val: u8, port: u8) -> Option<Signal> {
        macro_rules! write_hh { ($v:expr) => { $v = $v & 0x00ffffff | ((val as u32) << 24) }; }
        macro_rules! write_hl { ($v:expr) => { $v = $v & 0xff00ffff | ((val as u32) << 16) }; }
        macro_rules! write_lh { ($v:expr) => { $v = $v & 0x00ff     | ((val as u32) << 8)  }; }
        macro_rules! write_ll { ($v:expr) => { $v = $v & 0xff00     |  (val as u32)        }; }
        macro_rules! write_h  { ($v:expr) => { $v = $v & 0x00ff     | ((val as u16) << 8)  }; }
        macro_rules! write_l  { ($v:expr) => { $v = $v & 0xff00     |  (val as u16)        }; }
        macro_rules! no_write { () => { () }; }

        match port {
            // System
            0x00 => self.name.reset_pointer(),
            0x01 => no_write!(),
            0x02 =>   write_h!(self.wake_mask),
            0x03 => { write_l!(self.wake_mask); return Some(Signal::Sleep) },
            0x04 => no_write!(),
            0x05 => no_write!(),
            0x06 => no_write!(),
            0x07 => no_write!(),
            0x08 => no_write!(),
            0x09 => no_write!(),
            0x0A => no_write!(),
            0x0B => no_write!(),
            0x0C => no_write!(),
            0x0D => no_write!(),
            0x0E => no_write!(),
            0x0F => no_write!(),
            // Memory
            0x10 => write_h!(self.memory.page_1),
            0x11 => write_l!(self.memory.page_1),
            0x12 => write_h!(self.memory.address_1),
            0x13 => write_l!(self.memory.address_1),
            0x14 => self.memory.write_to_head_1(val),
            0x15 => self.memory.write_to_head_1(val),
            0x16 => no_write!(),
            0x17 => no_write!(),
            0x18 => write_h!(self.memory.page_2),
            0x19 => write_l!(self.memory.page_2),
            0x1A => write_h!(self.memory.address_2),
            0x1B => write_l!(self.memory.address_2),
            0x1C => self.memory.write_to_head_2(val),
            0x1D => self.memory.write_to_head_2(val),
            0x1E =>   write_h!(self.memory.copy_length),
            0x1F => { write_l!(self.memory.copy_length); self.memory.copy() },
            // Math
            0x20 => write_h!(self.math.operand_1),
            0x21 => write_l!(self.math.operand_1),
            0x22 => write_h!(self.math.operand_2),
            0x23 => write_l!(self.math.operand_2),
            0x24 => no_write!(),
            0x25 => no_write!(),
            0x26 => no_write!(),
            0x27 => no_write!(),
            0x28 => no_write!(),
            0x29 => no_write!(),
            0x2A => no_write!(),
            0x2B => no_write!(),
            0x2C => no_write!(),
            0x2D => no_write!(),
            0x2E => no_write!(),
            0x2F => no_write!(),
            // Clock
            0x30 => no_write!(),
            0x31 => no_write!(),
            0x32 => no_write!(),
            0x33 => no_write!(),
            0x34 => no_write!(),
            0x35 => no_write!(),
            0x36 => no_write!(),
            0x37 => no_write!(),
            0x38 =>   write_h!(self.clock.timer_1),
            0x39 => { write_l!(self.clock.timer_1); self.clock.set_timer_1() },
            0x3A =>   write_h!(self.clock.timer_2),
            0x3B => { write_l!(self.clock.timer_2); self.clock.set_timer_2() },
            0x3C =>   write_h!(self.clock.timer_3),
            0x3D => { write_l!(self.clock.timer_3); self.clock.set_timer_3() },
            0x3E =>   write_h!(self.clock.timer_4),
            0x3F => { write_l!(self.clock.timer_4); self.clock.set_timer_4() },
            // Input
            0x40 => no_write!(),
            0x41 => no_write!(),
            0x42 => no_write!(),
            0x43 => no_write!(),
            0x44 => no_write!(),
            0x45 => no_write!(),
            0x46 => no_write!(),
            0x47 => no_write!(),
            0x48 => no_write!(),
            0x49 => self.input.text_queue.clear(),
            0x4A => no_write!(),
            0x4B => no_write!(),
            0x4C => no_write!(),
            0x4D => no_write!(),
            0x4E => no_write!(),
            0x4F => no_write!(),
            // Screen
            0x50 => write_h!(self.screen.cursor.x),
            0x51 => write_l!(self.screen.cursor.x),
            0x52 => write_h!(self.screen.cursor.y),
            0x53 => write_l!(self.screen.cursor.y),
            0x54 =>   write_h!(self.screen.dimensions.width),
            0x55 => { write_l!(self.screen.dimensions.width);  self.screen.set_size() },
            0x56 =>   write_h!(self.screen.dimensions.height),
            0x57 => { write_l!(self.screen.dimensions.height); self.screen.set_size() },
            0x58 => self.screen.set_palette_high(val),
            0x59 => self.screen.set_palette_low(val),
            0x5A => self.screen.sprite_buffer.set_colour_high(val),
            0x5B => self.screen.sprite_buffer.set_colour_low(val),
            0x5C => self.screen.sprite_buffer.push(val),
            0x5D => self.screen.sprite_buffer.push(val),
            0x5E => self.screen.draw(val),
            0x5F => self.screen.shunt(val),
            // Stream
            0x86 => self.stream.write_stdout(val),
            0x87 => self.stream.write_stdout(val),
            // File
            0x90 => self.file.write_to_open_port(val),
            0x91 => self.file.write_to_move_port(val),
            0x92 => self.file.set_name_pointer(val),
            0x93 => self.file.ascend_to_parent(),
            0x94 => self.file.write_byte(val),
            0x95 => self.file.write_byte(val),
            0x96 => self.file.set_child_name_pointer(val),
            0x97 => self.file.descend_to_child(),
            0x98 =>   write_hh!(self.file.new_pointer),
            0x99 =>   write_hl!(self.file.new_pointer),
            0x9A =>   write_lh!(self.file.new_pointer),
            0x9B => { write_ll!(self.file.new_pointer); self.file.commit_pointer() },
            0x9C =>   write_hh!(self.file.new_length),
            0x9D =>   write_hl!(self.file.new_length),
            0x9E =>   write_lh!(self.file.new_length),
            0x9F => { write_ll!(self.file.new_length); self.file.commit_length() },

            _ => unimplemented!("Writing to device port 0x{port:02x}"),
        };

        return None;

    }
}