use std::ffi::OsStr;
use std::io::{self, IsTerminal, Read, Write};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::process::Command;

mod cursor;
mod linebuf;
mod parse;
mod raw;
mod panic;

use linebuf::LineBuf;
use raw::*;

use crate::cursor::{Direction, move_cursor};

macro_rules! print {
    ($($x:tt)*) => {{
        write!(io::stdout(), $($x)*).unwrap();
        io::stdout().flush().unwrap();
    }}
}

macro_rules! println {
    () => {{
        println!("")
    }};
    ($($x:tt)*) => {{
        write!(io::stdout(), $($x)*).unwrap();
        write!(io::stdout(), "\r\n").unwrap();
        io::stdout().flush().unwrap();
    }};
}

const PROMPT: &str = "> ";

fn run_command(raw: &ScopedRawMode, line: Vec<u8>) {

    let parsed = parse::do_parse(&line);
    println!("{parsed:?}");

    let mut words: Vec<&[u8]> = line.split(|x| *x == b' ').collect();
    words.retain(|w| !w.is_empty());

    if words.is_empty() {
        print!("{PROMPT}");
        return;
    }

    let cmd = words[0];

    match cmd {
        b"cd" => {
            let target: &Path = match words.get(1) {
                Some(&b"-") => todo!("prev"),
                Some(path) => OsStr::from_bytes(path).as_ref(),
                None => todo!("homedir"),
            };

            if let Err(_) = std::env::set_current_dir(target) {
                print!("ERR {PROMPT}");
            } else {
                print!("{PROMPT}");
            }

            return;
        }
        _ => (),
    }

    let mut cmd = Command::new(OsStr::from_bytes(cmd));
    for arg in words[1..].iter() {
        cmd.arg(OsStr::from_bytes(arg));
    }

    raw.disable();
    let status = cmd.status();
    raw.enable();

    let status_string = match status {
        Ok(ec) if ec.success() => String::new(),
        Ok(ec) => format!("{ec} "),
        Err(_) => String::from("IO ERROR "),
    };

    print!("{status_string}{PROMPT}");
}

fn main() {
    crate::panic::hook();

    let stdin = io::stdin();
    let stdout = io::stdout();

    if !stdin.is_terminal() {
        println!("need to run in a tty");
        return;
    }

    let fd = stdin.as_raw_fd();
    let raw = ScopedRawMode::on_fd(fd);
    raw.enable();

    let mut stdin = stdin.lock();
    let mut stdout = stdout.lock();

    let mut buffer = [0u8; 1];
    let mut line = LineBuf::new();

    print!("{PROMPT}");

    loop {
        let Ok(_) = stdin.read_exact(&mut buffer) else {
            break;
        };

        match buffer[0] {
            // EOF
            4 => {
                break;
            }

            // Enter
            b'\r' => {
                let line = line.dump();
                if !line.is_empty() {
                    print!("\r\n");
                    run_command(&raw, line);
                }
            }

            // Backspace (127 on most systems)
            127 => {
                if line.del_left().is_some() {
                    print!("\x08 \x08");
                }
            }

            // Escape sequence
            27 => {
                let mut seq = [0u8; 2];
                stdin.read_exact(&mut seq).unwrap();

                if seq[0] == b'[' {
                    match seq[1] {
                        b'A' => {
                            // up
                        }
                        b'B' => {
                            // down
                        }
                        b'C' => {
                            move_cursor(Direction::Right, 1);
                            line.right();
                        }
                        b'D' => {
                            move_cursor(Direction::Left, 1);
                            line.left();
                        }
                        x => todo!("escape character {x}"),
                    }
                }
            }

            // Normal character
            x => {
                line.add(x);
                stdout.write_all(&[x]).unwrap();
                line.display_post();
            }
        }
    }

    raw.disable();
    println!("bye");
}
