#![allow(non_camel_case_types)]

use std::sync::{Arc, Mutex};
use std::{env::*, fs::OpenOptions, path::PathBuf};

use super::{Builtin, BuiltinError as Error, BuiltinResult as Result};
use crate::*;

pub struct cd;
impl Builtin for cd {
    fn name(&self) -> &str {
        "cd"
    }
    fn io(
        &self,
        se: Arc<Mutex<Session>>,
        args: &[BString],
        _stdin: &mut dyn Read,
        _stdout: &mut dyn Write,
    ) -> Result {
        let mut dir = match current_dir() {
            Ok(path) => path.as_os_str().as_bytes().to_vec(),
            Err(_) => vec![b'.'],
        };

        std::mem::swap(&mut dir, &mut se.lock().unwrap().prev_path);

        match args.get(0).map(|v| &v[..]) {
            Some(b"-") => {
                let _ = set_current_dir(OsStr::from_bytes(&dir));
            }
            Some(path) => {
                let _ = set_current_dir(OsStr::from_bytes(path));
            }
            None => {
                if let Some(home) = std::env::var_os("HOME") {
                    let _ = set_current_dir(home);
                }
            }
        }

        Ok(())
    }
}

pub struct clear;
impl Builtin for clear {
    fn name(&self) -> &str {
        "clear"
    }

    fn io(
        &self,
        _session: Arc<Mutex<Session>>,
        _args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> Result {
        stdout.write_all(b"\x1B[2J\x1B[1;1H")?;
        Ok(())
    }
}

/// restart shell
pub struct re;
impl Builtin for re {
    fn name(&self) -> &str {
        "re"
    }

    fn io(
        &self,
        session: Arc<Mutex<Session>>,
        _args: &[BString],
        _stdin: &mut dyn Read,
        _stdout: &mut dyn Write,
    ) -> Result {
        let session = session.lock().unwrap();
        session.raw.disable();
        crate::reload::begin_reload();
        session.raw.enable(); // something went wrong, let's restore raw mode
        Ok(())
    }
}

pub struct Sink {
    name: &'static str,
    append: bool,
}

impl Builtin for Sink {
    fn name(&self) -> &str {
        self.name
    }

    fn io(
        &self,
        _session: Arc<Mutex<Session>>,
        args: &[BString],
        stdin: &mut dyn Read,
        _stdout: &mut dyn Write,
    ) -> Result {
        let Some(path) = args.get(0) else {
            // TODO exit code
            return Ok(());
        };
        let path = PathBuf::from(OsStr::from_bytes(path));
        let mut file = OpenOptions::new()
            .write(true)
            .create(true)
            .append(self.append)
            .open(path)?;
        std::io::copy(stdin, &mut file)?;
        Ok(())
    }
}

pub const fn sink(name: &'static str, append: bool) -> Sink {
    Sink { name, append }
}

// TODO
// from" => todo!("read from file"),

pub struct from;
impl Builtin for from {
    fn name(&self) -> &str {
        "from"
    }

    fn io(
        &self,
        _session: Arc<Mutex<Session>>,
        args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> Result {
        let Some(path) = args.get(0) else {
            // TODO exit code
            return Ok(());
        };
        let path = PathBuf::from(OsStr::from_bytes(path));
        let mut file = OpenOptions::new().read(true).open(path)?;
        std::io::copy(&mut file, stdout)?;
        Ok(())
    }
}

pub struct _type;
impl Builtin for _type {
    fn name(&self) -> &str {
        "type"
    }

    fn io(
        &self,
        session: Arc<Mutex<Session>>,
        args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> Result {
        for arg in args {
            let mut kind = String::from("not found");
            {
                let _se = session.lock().unwrap();
                // TODO: look up functions in session

                for b in super::BUILTINS {
                    if b.name().as_bytes() == &arg[..] {
                        kind = String::from("builtin");
                        break;
                    }
                }
            };
            writeln!(stdout, "{} is {}", String::from_utf8_lossy(arg), kind)?;
        }

        Ok(())
    }
}

pub struct builtins;
impl Builtin for builtins {
    fn name(&self) -> &str {
        "builtins"
    }

    fn io(
        &self,
        _session: Arc<Mutex<Session>>,
        _args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> Result {
        for b in super::BUILTINS {
            write!(stdout, "{} ", b.name())?;
        }
        writeln!(stdout)?;
        Ok(())
    }
}

pub struct history;
impl Builtin for history {
    fn name(&self) -> &str {
        "history"
    }

    fn io(
        &self,
        session: Arc<Mutex<Session>>,
        _args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> Result {
        // TODO: better history querying
        let hist = session.lock().unwrap().history.clone();
        for entry in hist {
            stdout.write_all(&entry)?;
            stdout.write_all(b"\n")?;
        }
        Ok(())
    }
}

pub struct escape;
impl Builtin for escape {
    fn name(&self) -> &str {
        "escape"
    }

    fn io(
        &self,
        _session: Arc<Mutex<Session>>,
        args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> Result {
        for arg in args.iter() {
            let escaped = arg.escape_ascii().to_string();
            stdout.write_all(escaped.as_bytes())?;
            stdout.write_all(b" ")?;
        }
        Ok(())
    }
}
