use clap::{App, Arg, AppSettings};

use crate::{Result, Shell, Arguments};

use std::fs::{self, DirEntry};
use std::time::SystemTime;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;


fn perms_to_str(is_dir: bool, permissions: u32) -> String {
    let mut out: String = String::with_capacity(10);

    out.push(if is_dir { 'd' } else { '-' });

    out.push(if permissions & 0o400 > 0 { 'r' } else { '-' });
    out.push(if permissions & 0o200 > 0 { 'w' } else { '-' });
    out.push(if permissions & 0o100 > 0 { 'x' } else { '-' });

    out.push(if permissions &  0o40 > 0 { 'r' } else { '-' });
    out.push(if permissions &  0o20 > 0 { 'w' } else { '-' });
    out.push(if permissions &  0o10 > 0 { 'x' } else { '-' });

    out.push(if permissions &   0o4 > 0 { 'r' } else { '-' });
    out.push(if permissions &   0o2 > 0 { 'w' } else { '-' });
    out.push(if permissions &   0o1 > 0 { 'x' } else { '-' });

    out
}

fn since(time: SystemTime) -> String {
    let now = SystemTime::now();

    if now < time {
        String::from("future?!")
    } else {
        let duration = now.duration_since(time).unwrap().as_secs();

        let mut duration = (duration, "sec");

        for &(x, unit) in &[
                            (60, "min"),
                            (60, "hour"),
                            (24, "day"),
                            (31, "month"),
                            (12, "year"),
                           ] {
            if duration.0 > x {
                duration = (duration.0 / x, unit);
            } else {
                break;
            }
        }

        if duration.0 == 1 {
            format!("{:?} {} ago", duration.0, duration.1)
        } else {
            format!("{:?} {}s ago", duration.0, duration.1)
        }
    }
}

cfg_if! {
    if #[cfg(unix)] {
        #[inline]
        fn decorate(entry: &DirEntry) -> String {
            let meta = entry.metadata().unwrap();
            format!("{} {:5} {:5}  {:14} {:?}",
                perms_to_str(meta.is_dir(), meta.mode()), meta.uid(), meta.gid(),
                meta.modified().ok().map_or_else(|| String::from("-"), since),
                entry.path())
        }
    } else {
        #[inline]
        fn decorate(entry: &DirEntry) -> String {
            let meta = entry.metadata().unwrap();
            format!("{} {:5} {:5}  {:14} {:?}",
                perms_to_str(meta.is_dir(), 0), 0, 0,
                meta.modified().ok().map_or_else(|| String::from("-"), since),
                entry.path())
        }
    }
}

pub fn ls(sh: &mut Shell, args: Arguments) -> Result<()> {
    let matches = App::new("ls")
        .setting(AppSettings::DisableVersion)
        .arg(Arg::with_name("path")
            .multiple(true)
        )
        .arg(Arg::with_name("long").short("l").help("Show more infos"))
        .arg(Arg::with_name("a").short("a").help("Dummy option"))
        .arg(Arg::with_name("h").short("h").help("Dummy option"))
        .get_matches_from_safe(args)?;

    let long = matches.is_present("long");

    let paths = match matches.values_of("path") {
        Some(paths) => paths.into_iter().collect(),
        None => vec!["."],
    };

    for path in paths {
        debug!("ls: {:?}", path);

        match fs::read_dir(&path) {
            Ok(entries) => {
                for entry in entries {
                    let entry = entry.unwrap();
                    if long {
                        shprintln!(sh, "{}", decorate(&entry));
                    } else {
                        shprintln!(sh, "{:?}", entry.path());
                    }
                }
            },
            Err(err) => shprintln!(sh, "{:?}: {:?}", path, err),
        }
    }

    Ok(())
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_permission_formatter() {
        assert_eq!("drwxrwxrwx", perms_to_str(true, 0o777));
        assert_eq!("-rwxrwxrwx", perms_to_str(false, 0o777));
        assert_eq!("drwxr-xr-x", perms_to_str(true, 0o755));
        assert_eq!("-rw-r--r--", perms_to_str(false, 0o644));
        assert_eq!("drwx------", perms_to_str(true, 0o700));
        assert_eq!("-rwxr-xr-x", perms_to_str(false, 0o4755));
        assert_eq!("----------", perms_to_str(false, 0o000));
        assert_eq!("----------", perms_to_str(false, 0o4000));
    }
}
