use crate::logo::{ARCH, VOID};
use cmd_lib::run_fun;
use colored::{Color, Colorize};
use regex::Regex;
use std::{env, fs, path::Path};

pub struct Specs {
    pub logo: String,
    pub os: Option<String>,
    pub kernel: Option<String>,
    pub uptime: Option<String>,
    pub packages: Option<String>,
    pub shell: Option<String>,
    pub monitors: Option<String>,
    pub wmde: Option<String>,
    pub theme: [Option<String>; 4],
    pub terminal: Option<String>,
    pub cpu: Option<String>,
    pub gpu: Option<String>,
    pub memory: Option<String>,
    pub disk: Option<String>,
    pub music: Option<String>,
    pub colours: [String; 2],
    pub shade: Color,
}

impl Specs {
    pub fn new() -> Self {
        Self {
            logo: "".to_string(),
            os: None,
            kernel: None,
            uptime: None,
            packages: None,
            shell: None,
            monitors: None,
            wmde: None,
            theme: [None, None, None, None],
            terminal: None,
            cpu: None,
            gpu: None,
            memory: None,
            disk: None,
            music: None,
            colours: ["".to_string(), "".to_string()],
            shade: Color::White,
        }
    }

    pub fn get(&mut self) {
        self.os = Self::get_os();
        if let Some(os) = &self.os {
            let (logo, shade) = match os.as_str() {
                "Void Linux" => (VOID, Color::Green),
                "Arch Linux" => (ARCH, Color::Blue),
                _ => ("", Color::White),
            };
            self.logo = logo.to_string();
            self.shade = shade;
        }
        self.kernel = Self::get_kernel();
        self.uptime = Self::get_uptime();
        self.packages = Self::get_packages();
        self.shell = Self::get_shell();
        self.monitors = Self::get_monitors();
        self.wmde = Self::get_wmde();
        self.theme = Self::get_theme();
        self.terminal = Self::get_terminal();
        self.cpu = Self::get_cpu();
        self.gpu = Self::get_gpu();
        self.memory = Self::get_memory();
        self.disk = Self::get_disk();
        self.music = Self::get_music();
        self.colours = Self::get_colours();
    }

    pub fn get_os() -> Option<String> {
        let raw = fs::read_to_string("/etc/os-release").ok()?;
        let regex = Regex::new("(?m)^ID=\"?([^\"\n]*)\"?$").unwrap();
        let id = regex.captures(&raw)?.get(1)?;
        Some(
            match id.as_str() {
                "arch" => "Arch Linux",
                "void" => "Void Linux",
                _ => "Linux",
            }
            .to_string(),
        )
    }

    pub fn get_kernel() -> Option<String> {
        run_fun!(uname -rms).ok()
    }

    pub fn get_uptime() -> Option<String> {
        Some(run_fun!(uptime -p).ok()?.chars().skip(3).collect())
    }

    pub fn get_packages() -> Option<String> {
        let mut out = String::new();
        if let Ok(n) = run_fun!(xbps-query -l | wc -l) {
            out.push_str(&format!("{} (xbps)", n))
        }
        if let Ok(n) = run_fun!(pacman -Q | wc -l) {
            out.push_str(&format!("{} (pacman)", n))
        }
        Some(out)
    }

    pub fn get_shell() -> Option<String> {
        let raw = env::var("SHELL").ok()?;
        let path = Path::new(&raw);
        Some(path.file_name()?.to_str()?.to_string())
    }

    pub fn get_monitors() -> Option<String> {
        let raw = run_fun!(xrandr --listactivemonitors).ok()?;
        let res = Regex::new(r"(\d+)/\d+x(\d+)").unwrap();
        let mut out = vec![];
        for i in raw.split('\n').skip(1) {
            let data = res.captures(i)?;
            let (w, h) = (data.get(1)?.as_str(), data.get(2)?.as_str());
            out.push(format!("{}x{}", w, h));
        }
        Some(out.join(", "))
    }

    pub fn get_wmde() -> Option<String> {
        let de = env::var("XDG_DESKTOP_SESSION")
            .or_else(|_| env::var("XDG_CURRENT_DESKTOP"))
            .or_else(|_| env::var("DESKTOP_SESSION"));
        if let Ok(de) = de {
            Some(de)
        } else {
            let path = env::var("HOME").ok()? + "/.xinitrc";
            let raw = fs::read_to_string(path).ok()?;
            let wm = Regex::new("(?m)^exec [^\n]* ([^\n]*)$").unwrap();
            Some(wm.captures(&raw)?.get(1)?.as_str().to_string())
        }
    }

    pub fn get_theme() -> [Option<String>; 4] {
        Self::do_theme().unwrap_or([None, None, None, None])
    }

    fn do_theme() -> Option<[Option<String>; 4]> {
        let loc = env::var("HOME").ok()? + "/.config/gtk-3.0/settings.ini";
        let mut theme = None;
        let mut icons = None;
        let mut font = None;
        let mut cursor = None;
        for i in fs::read_to_string(loc).ok()?.split('\n') {
            let i = i.split('=');
            match i.collect::<Vec<_>>().as_slice() {
                ["gtk-theme-name", name] => theme = Some((*name).to_string()),
                ["gtk-icon-theme-name", name] => icons = Some((*name).to_string()),
                ["gtk-font-name", name] => font = Some((*name).to_string()),
                ["gtk-cursor-theme-name", name] => cursor = Some((*name).to_string()),
                _ => (),
            }
        }
        Some([theme, icons, font, cursor])
    }

    pub fn get_terminal() -> Option<String> {
        let window = env::var("WINDOWID").ok()?;
        let proc = run_fun!(xwininfo -id $window).ok()?;
        let s = Regex::new("\"(.*)\"").unwrap();
        Some(s.captures(&proc)?.get(1)?.as_str().to_string())
    }

    pub fn get_cpu() -> Option<String> {
        let raw = fs::read_to_string("/proc/cpuinfo").ok()?;
        let model = Regex::new(r"(?m)model name\s*:\s*([^\n]*)$").unwrap();
        let cores = Regex::new(r"(?m)siblings\s*:\s*([^\n]*)$").unwrap();
        Some(format!(
            "{} (x{})",
            model.captures(&raw)?.get(1)?.as_str(),
            cores.captures(&raw)?.get(1)?.as_str(),
        ))
    }

    pub fn get_gpu() -> Option<String> {
        Some(
            run_fun!(lspci -mm | grep VGA)
                .ok()?
                .split('"')
                .nth(5)?
                .to_string(),
        )
    }

    pub fn get_memory() -> Option<String> {
        let raw = run_fun!(free -m).ok()?.split('\n').nth(1)?.to_string();
        let total: usize = raw.split_whitespace().nth(1)?.parse().ok()?;
        let used: usize = raw.split_whitespace().nth(2)?.parse().ok()?;
        Some(format!("{}mb / {}mb", used, total))
    }

    pub fn get_disk() -> Option<String> {
        let raw = run_fun!(df /home).ok()?.split('\n').nth(1)?.to_string();
        let used: usize = raw.split_whitespace().nth(2)?.parse().ok()?;
        let available: usize = raw.split_whitespace().nth(3)?.parse().ok()?;
        let percent = raw.split_whitespace().nth(4)?;
        let total = used + available;
        Some(format!(
            "{}gb / {}gb ({} used)",
            used / 1_048_576,
            total / 1_048_576,
            percent
        ))
    }

    pub fn get_music() -> Option<String> {
        run_fun!(playerctl metadata -f "{{ artist }} - {{ title }}").ok()
    }

    pub fn get_colours() -> [String; 2] {
        let dark_scheme = format!(
            "{}{}{}{}{}{}{}{}",
            "   ".on_black(),
            "   ".on_red(),
            "   ".on_green(),
            "   ".on_blue(),
            "   ".on_yellow(),
            "   ".on_magenta(),
            "   ".on_cyan(),
            "   ".on_white(),
        );
        let light_scheme = format!(
            "{}{}{}{}{}{}{}{}",
            "   ".on_bright_black(),
            "   ".on_bright_red(),
            "   ".on_bright_green(),
            "   ".on_bright_blue(),
            "   ".on_bright_yellow(),
            "   ".on_bright_magenta(),
            "   ".on_bright_cyan(),
            "   ".on_bright_white(),
        );
        [dark_scheme, light_scheme]
    }
}
