/src/apps/shell.rs
use core::{num::ParseIntError, slice};

use crate::stdlib;

enum CommandError {
    BadCommand,
    BadAddress,
    NeedArgument,
}

impl From<ParseIntError> for CommandError {
    fn from(_value: ParseIntError) -> Self {
        CommandError::BadAddress
    }
}

fn readline(buf: &mut [u8]) -> Option<&str> {
    let mut inbuf = [0u8; 100];
    let mut i = 0;
    loop {
        let n_read = stdlib::read(buf);
        for b in &buf[..n_read] {
            match *b {
                b'\r' | b'\n' => {
                    stdlib::print("\r\n");
                    // TODO: don't dump the rest of the buffer
                    buf.copy_from_slice(&inbuf);
                    let s = core::str::from_utf8(&buf[..i]).unwrap();
                    return Some(s);
                }
                0x08 => {
                    if i > 0 {
                        stdlib::print("\x08 \x08");
                        i -= 1;
                    }
                }
                0x03 => {
                    stdlib::print("\r\n");
                    return None;
                }
                b => {
                    inbuf[i] = b;
                    stdlib::write(&inbuf[i..i + 1]);
                    i += 1;
                }
            }
        }
    }
}

fn format_hex_u32(buf: &mut [u8], mut n: u32) {
    let mut i: i32 = 7;
    while i >= 0 {
        let d = n & 0xF;
        buf[i as usize] = match d {
            0..=9 => (0x30 + d) as u8,
            _ => (0x41 - 10 + d) as u8,
        };
        i -= 1;
        n >>= 4;
    }
}

fn print_hex_u32(n: u32) {
    let mut hexbuf = [0u8; 8];
    format_hex_u32(&mut hexbuf, n);
    stdlib::write(&hexbuf);
}

fn read_addr(a: u32) -> u32 {
    unsafe {
        let ptr = a as *const u32;
        *ptr
    }
}

fn parse_cmdline(cmdline: &str) -> Result<(), CommandError> {
    let mut parts = cmdline.split_ascii_whitespace();
    let cmd = parts.next().ok_or(CommandError::BadCommand)?;
    match cmd {
        "r" => {
            let astr = parts.next().ok_or(CommandError::NeedArgument)?;
            let mut aparts = astr.split('.');
            let from = aparts
                .next()
                .map(|s| u32::from_str_radix(s, 16))
                .ok_or(CommandError::BadAddress)??;
            print_hex_u32(from);
            stdlib::print(": ");
            match aparts.next() {
                Some(to) => {
                    let to = u32::from_str_radix(to, 16)?;
                    let mut line_c = 0;
                    for addr in (from..to).step_by(4) {
                        if line_c == 4 {
                            stdlib::print("\r\n");
                            print_hex_u32(addr);
                            stdlib::print(": ");
                            line_c = 0;
                        }
                        print_hex_u32(read_addr(addr));
                        stdlib::print(" ");
                        line_c += 1;
                    }
                }
                None => {
                    print_hex_u32(read_addr(from));
                }
            };

            stdlib::print("\r\n");
            Ok(())
        }
        "q" => {
            stdlib::exit();
        }
        "dog" | "cat" => {
            let tid = stdlib::spawn(if cmd == "cat" { cat } else { dog });
            if tid == 0xFFFFFFFF {
                stdlib::print("Failed to launch ");
                stdlib::print(cmd);
                stdlib::print("\r\n");
            } else {
                stdlib::print("spawned ");
                stdlib::print(cmd);
                stdlib::print(" at tid ");
                print_hex_u32(tid);
                stdlib::print("\r\n");
            }
            Ok(())
        }
        "ps" => {
            stdlib::ps();
            Ok(())
        }
        "unaligned" => unsafe {
            let p = 2 as *const u32;
            print_hex_u32(*p);
            Ok(())
        }
        "instr" => unsafe {
            let b: &[u16; 2] = &[0xf7f0, 0xa000]; // Should be an undefined instruction
            let fp: fn() = core::mem::transmute(b);
            fp();
            Ok(())
        }
        "panic" => {
            panic!("Test panic");
        }
        "kernelpanic" => {
            stdlib::kernel_panic();
            Ok(())
        }
        "k" => {
            let mut keybits = [0u32; 3];
            let mut db = [0u8; 1];
            stdlib::kb_read_matrix(&mut keybits);
            stdlib::print("   c1 c2 c3 c4 c5 c6 c7 c8 c9\r\n");
            for r in 0..9 {
                stdlib::print("r");
                db[0] = 0x30 + r + 1;
                stdlib::write(&db);
                for c in 0..9 {
                    let i = (r * 9 + c) as usize;
                    if keybits[i / 32] & 1 << (i % 32) != 0 {
                        db[0] = 0x31;
                    } else {
                        db[0] = 0x30;
                    }
                    stdlib::print("  ");
                    stdlib::write(&db);
                }
                stdlib::print("\r\n");
            }
            Ok(())
        }
        _ => Err(CommandError::BadCommand),
    }
}

pub fn shell(base: u32, _size: u32) -> ! {
    let mut input_buffer = unsafe { slice::from_raw_parts_mut(base as *mut u8, 100) };
    stdlib::sleep(1000);
    stdlib::print("\r\nShell 0.1\r\n");
    loop {
        stdlib::print("> ");
        if let Some(cmdline) = readline(&mut input_buffer) {
            match parse_cmdline(cmdline) {
                Ok(_) => (),
                Err(CommandError::BadCommand) => stdlib::print("Bad command\r\n"),
                Err(CommandError::BadAddress) => stdlib::print("Bad address\r\n"),
                Err(CommandError::NeedArgument) => stdlib::print("Need argument\r\n"),
            }
        }
    }
}

fn cat(_base: u32, _size: u32) -> ! {
    stdlib::sleep(1000);
    unsafe {
        // Try to read kernel memory
        let p = 0x20040000 as *const u32;
        let b = *p;
        stdlib::sleep(b);
    }
    stdlib::exit();
}

fn dog(_base: u32, _size: u32) -> ! {
    for _ in 0..10 {
        stdlib::print("woof\r\n");
        stdlib::sleep(1500);
    }
    stdlib::exit();
}