paru 1.11.0

Feature packed AUR helper
use crate::config::Config;
use crate::repo;

use alpm::Ver;
use aur_depends::Actions;

use ansi_term::Style;
use chrono::{DateTime, NaiveDateTime};
use tr::tr;
use unicode_width::UnicodeWidthStr;

struct ToInstall {
    install: Vec<String>,
    make_install: Vec<String>,
    aur: Vec<String>,
    make_aur: Vec<String>,
}

pub fn opt(opt: &Option<String>) -> String {
    opt.clone().unwrap_or_else(|| tr!("None"))
}

pub fn date(date: i64) -> String {
    let date = NaiveDateTime::from_timestamp(date, 0);
    let date = DateTime::<chrono::Utc>::from_utc(date, chrono::Utc);
    date.to_rfc2822()
}

pub fn ymd(date: i64) -> String {
    let date = NaiveDateTime::from_timestamp(date, 0);
    let date = DateTime::<chrono::Utc>::from_utc(date, chrono::Utc);
    date.format("%Y-%m-%d").to_string()
}

pub fn print_indent<S: AsRef<str>>(
    color: Style,
    start: usize,
    indent: usize,
    cols: Option<usize>,
    sep: &str,
    value: impl IntoIterator<Item = S>,
) {
    let v = value.into_iter();

    match cols {
        Some(cols) if cols > indent + 2 => {
            let mut pos = start;

            let mut iter = v.peekable();

            if let Some(word) = iter.next() {
                print!("{}", color.paint(word.as_ref()));
                pos += word.as_ref().len();
            }

            if iter.peek().is_some() && pos + sep.len() < cols {
                print!("{}", sep);
                pos += sep.len();
            }

            while let Some(word) = iter.next() {
                let word = word.as_ref();

                if pos + word.len() > cols {
                    print!("\n{:>padding$}", "", padding = indent);
                    pos = indent;
                }

                print!("{}", color.paint(word));
                pos += word.len();

                if iter.peek().is_some() && pos + sep.len() < cols {
                    print!("{}", sep);
                    pos += sep.len();
                }
            }
        }
        _ => {
            let mut iter = v;
            if let Some(word) = iter.next() {
                print!("{}", color.paint(word.as_ref()));
            }

            for word in iter {
                print!("{}{}", sep, color.paint(word.as_ref()));
            }
        }
    }
    println!();
}

use ansi_term::Color;

pub fn color_repo(enabled: bool, name: &str) -> String {
    if !enabled {
        return name.to_string();
    }

    let mut col: u32 = 5;

    for &b in name.as_bytes() {
        col = (b as u32).wrapping_add(((col as u32) << 4).wrapping_add(col as u32));
    }

    col = (col % 6) + 9;
    let col = Style::from(Color::Fixed(col as u8)).bold();
    col.paint(name).to_string()
}

fn to_install(actions: &Actions) -> ToInstall {
    let install = actions
        .install
        .iter()
        .filter(|p| !p.make)
        .map(|p| format!("{}-{}", p.pkg.name(), p.pkg.version()))
        .collect::<Vec<_>>();
    let make_install = actions
        .install
        .iter()
        .filter(|p| p.make)
        .map(|p| format!("{}-{}", p.pkg.name(), p.pkg.version()))
        .collect::<Vec<_>>();

    let mut build = actions.build.clone();
    for base in &mut build {
        base.pkgs.retain(|p| !p.make);
    }
    build.retain(|b| !b.pkgs.is_empty());
    let build = build.iter().map(|p| p.to_string()).collect::<Vec<_>>();

    let mut make_build = actions.build.clone();
    for base in &mut make_build {
        base.pkgs.retain(|p| p.make);
    }
    make_build.retain(|b| !b.pkgs.is_empty());
    let make_build = make_build.iter().map(|p| p.to_string()).collect::<Vec<_>>();

    ToInstall {
        install,
        make_install,
        aur: build,
        make_aur: make_build,
    }
}

pub fn print_install(config: &Config, actions: &Actions) {
    let c = config.color;

    println!();

    let to = to_install(actions);

    if !to.install.is_empty() {
        let fmt = format!("{} ({}) ", tr!("Repo"), to.install.len());
        let start = 17 + to.install.len().to_string().len();
        print!("{}", c.bold.paint(fmt));
        print_indent(Style::new(), start, 4, config.cols, "  ", to.install);
    }

    if !to.make_install.is_empty() {
        let fmt = format!("{} ({}) ", tr!("Repo Make"), to.make_install.len());
        let start = 22 + to.make_install.len().to_string().len();
        print!("{}", c.bold.paint(fmt));
        print_indent(Style::new(), start, 4, config.cols, "  ", to.make_install);
    }

    if !to.aur.is_empty() {
        let fmt = format!("{} ({}) ", "Aur", to.aur.len());
        let start = 16 + to.aur.len().to_string().len();
        print!("{}", c.bold.paint(fmt));
        print_indent(Style::new(), start, 4, config.cols, "  ", to.aur);
    }

    if !to.make_aur.is_empty() {
        let fmt = format!("{} ({}) ", tr!("Aur Make"), to.make_aur.len());
        let start = 16 + to.make_aur.len().to_string().len();
        print!("{}", c.bold.paint(fmt));
        print_indent(Style::new(), start, 4, config.cols, "  ", to.make_aur);
    }

    println!();
}

fn repo<'a>(config: &'a Config, pkg: &str) -> &'a str {
    let (_, dbs) = repo::repo_aur_dbs(config);

    if dbs.is_empty() {
        return "aur";
    }

    let db = dbs
        .iter()
        .find(|db| db.pkg(pkg).is_ok())
        .map(|db| db.name())
        .unwrap_or_else(|| dbs.first().unwrap().name());

    db
}

fn old_ver<'a>(config: &'a Config, pkg: &str) -> Option<&'a Ver> {
    let (_, dbs) = repo::repo_aur_dbs(config);

    if dbs.is_empty() {
        return config.alpm.localdb().pkg(pkg).ok().map(|p| p.version());
    }

    dbs.iter()
        .find_map(|db| db.pkg(pkg).ok())
        .map(|p| p.version())
}

pub fn print_install_verbose(config: &Config, actions: &Actions) {
    let c = config.color;
    let bold = c.bold;
    let db = config.alpm.localdb();

    let package = tr!("Repo ({})", actions.install.len());
    let aur = tr!("Aur ({})", actions.iter_build_pkgs().count());
    let old = tr!("Old Version");
    let new = tr!("New Version");
    let make = tr!("Make Only");
    let yes = tr!("Yes");
    let no = tr!("No");

    let package_len = actions
        .install
        .iter()
        .map(|pkg| pkg.pkg.db().unwrap().name().len() + 1 + pkg.pkg.name().len())
        .chain(Some(package.width()))
        .max()
        .unwrap_or_default();

    let old_len = actions
        .install
        .iter()
        .filter_map(|pkg| db.pkg(pkg.pkg.name()).ok())
        .map(|pkg| pkg.version().len())
        .chain(Some(old.width()))
        .max()
        .unwrap_or_default();

    let new_len = actions
        .install
        .iter()
        .map(|pkg| pkg.pkg.version().len())
        .chain(Some(new.width()))
        .max()
        .unwrap_or_default();

    let make_len = yes.width().max(no.width()).max(make.width());

    let aur_len = actions
        .iter_build_pkgs()
        .map(|pkg| repo(config, &pkg.pkg.name).len() + 1 + pkg.pkg.name.len())
        .chain(Some(aur.width()))
        .max()
        .unwrap_or_default();

    let aur_old_len = actions
        .iter_build_pkgs()
        .filter_map(|pkg| old_ver(config, &pkg.pkg.name))
        .map(|v| v.len())
        .chain(Some(old.width()))
        .max()
        .unwrap_or_default();

    let aur_new_len = actions
        .iter_build_pkgs()
        .map(|pkg| pkg.pkg.version.len())
        .chain(Some(new.width()))
        .max()
        .unwrap_or_default();

    let package_len = package_len.max(aur_len);
    let old_len = old_len.max(aur_old_len);
    let new_len = new_len.max(aur_new_len);

    if let Some(cols) = config.cols {
        if package_len + 2 + old_len + 2 + new_len + 2 + make_len > cols {
            eprintln!(
                "{} {}",
                c.warning.paint("::"),
                tr!("insufficient columns available for table display")
            );

            print_install(config, actions);
            return;
        }
    }

    if !actions.install.is_empty() {
        println!();
        println!(
            "{}{:<package_len$}  {}{:<old_len$}  {}{:<new_len$}  {}",
            bold.paint(&package),
            "",
            bold.paint(&old),
            "",
            bold.paint(&new),
            "",
            bold.paint(&make),
            package_len = package_len - package.width(),
            old_len = old_len - old.width(),
            new_len = new_len - new.width(),
        );

        let mut install = actions.install.clone();
        install.sort_by(|a, b| {
            a.pkg
                .db()
                .unwrap()
                .name()
                .cmp(b.pkg.db().unwrap().name())
                .then(a.pkg.name().cmp(b.pkg.name()))
        });

        for pkg in &install {
            println!(
                "{:<package_len$}  {:<old_len$}  {:<new_len$}  {}",
                format!("{}/{}", pkg.pkg.db().unwrap().name(), pkg.pkg.name()),
                db.pkg(pkg.pkg.name())
                    .map(|pkg| pkg.version().as_str())
                    .unwrap_or(""),
                pkg.pkg.version().as_str(),
                if pkg.make { &yes } else { &no }
            );
        }
    }

    if !actions.build.is_empty() {
        println!();
        println!(
            "{}{:<package_len$}  {}{:<old_len$}  {}{:<new_len$}  {}",
            bold.paint(&aur),
            "",
            bold.paint(&old),
            "",
            bold.paint(&new),
            "",
            bold.paint(&make),
            package_len = package_len - aur.width(),
            old_len = old_len - old.width(),
            new_len = new_len - new.width(),
        );

        for pkg in actions.iter_build_pkgs() {
            println!(
                "{:<package_len$}  {:<old_len$}  {:<new_len$}  {}",
                format!("{}/{}", repo(config, &pkg.pkg.name), pkg.pkg.name),
                old_ver(config, &pkg.pkg.name)
                    .map(|v| v.as_str())
                    .unwrap_or_default(),
                pkg.pkg.version,
                if pkg.make { &yes } else { &no }
            );
        }
    }

    println!();
}