extern crate libc;

use chrono::{DateTime, Local};
use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};
use std::os::unix::fs::PermissionsExt;
use std::{env, error::Error, fs, fs::DirEntry, path::Path, process};

const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const BLUE: &str = "\x1b[34m";
const MAGENTA: &str = "\x1b[35m";
const CYAN: &str = "\x1b[36m";
const BLACK: &str = "\x1b[30m";
const BOLD: &str = "\x1b[1m";
const RESET: &str = "\x1b[0m";

fn main() {
    let mut args = env::args();
    args.next();

    let file_path = args.next().unwrap_or_else(|| ".".to_string());
    let path = Path::new(&file_path);

    if !path.exists() {
        eprintln!(
            "{}Path doesn't exist. Path: {}{:#?}{}",
            RED, GREEN, path, RESET
        );
    }

    match run(path) {
        Ok(entries) => {
            entries.into_iter().for_each(|e| e.print());
        }
        Err(e) => {
            eprintln!("{}", e);
            process::exit(1);
        }
    }
}

struct Entry {
    mode: (bool, u32),
    size: u64,
    modified: DateTime<Local>,
    file_name: String,
}

impl Entry {
    fn print(&self) {
        println!(
            "{} {} {} {}",
            parse_permissions(self.mode),
            format_args!("{}{:>5} {}B{}", MAGENTA, self.size, BOLD, RESET),
            format_args!(
                "{}{}{}",
                CYAN,
                self.modified.format("%_d %b %H %H:%M"),
                RESET
            ),
            format_args!("{}{}{}", BLUE, self.file_name, RESET),
        );
    }
}

fn run(dir: &Path) -> Result<Vec<Entry>, Box<dyn Error>> {
    let mut entries: Vec<Entry> = Vec::new();

    //let prev_dir = env::join_paths([Path::new("../"), dir].iter())?;
    //entries.push(get_entry(prev_dir)?);

    if !dir.is_dir() {
        return Ok(entries);
    }

    for entry in fs::read_dir(dir)? {
        entries.push(get_entry(entry?)?);
    }

    Ok(entries)
}

fn get_entry(entry: DirEntry) -> Result<Entry, Box<dyn Error>> {
    let file_name = entry
        .file_name()
        .into_string()
        .map_err(|f| format!("Invalid entry: {:?}", f))?;
    let metadata = entry.metadata()?;
    let size = metadata.len();
    let modified: DateTime<Local> = DateTime::from(metadata.modified()?);
    let mode = (metadata.is_dir(), metadata.permissions().mode());

    Ok(Entry {
        mode,
        size,
        modified,
        file_name,
    })
}

fn parse_permissions(mode: (bool, u32)) -> String {
    let directory = match mode.0 {
        true => format!("{}{}d{}", BOLD, BLUE, RESET),
        false => format!("{}.{}", BLACK, RESET),
    };
    let user = triplet(mode.1, S_IRUSR, S_IWUSR, S_IXUSR);
    let group = triplet(mode.1, S_IRGRP, S_IWGRP, S_IXGRP);
    let other = triplet(mode.1, S_IROTH, S_IWOTH, S_IXOTH);

    [directory, user, group, other].join("")
}

fn triplet(mode: u32, read: u32, write: u32, execute: u32) -> String {
    match (mode & read, mode & write, mode & execute) {
        (0, 0, 0) => format!("{}---{}", BLACK, RESET),
        (_, 0, 0) => format!("{}{}r{}{}--{}", BOLD, GREEN, RESET, BLACK, RESET),
        (0, _, 0) => format!("{}-{}{}w{}{}-{}", BLACK, BOLD, YELLOW, RESET, BLACK, RESET),
        (0, 0, _) => format!("{}--{}{}x{}", BLACK, BOLD, RED, RESET),
        (_, 0, _) => format!(
            "{}{}r{}{}-{}{}x{}",
            BOLD, GREEN, RESET, BLACK, BOLD, RED, RESET
        ),
        (_, _, 0) => format!("{}{}r{}w{}{}-{}", BOLD, GREEN, YELLOW, RESET, BLACK, RESET),
        (0, _, _) => format!("{}-{}{}w{}x{}", BLACK, BOLD, YELLOW, GREEN, RESET),
        (_, _, _) => format!("{}{}r{}w{}x{}", BOLD, GREEN, YELLOW, GREEN, RESET),
    }
}
