use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
    static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^(\d+)(b|[kmgt]i?b?)$").unwrap();
}

// SI prefixes (powers of 10)
const KILO: u64 = 1000;
const MEGA: u64 = KILO * 1000;
const GIGA: u64 = MEGA * 1000;
const TERA: u64 = GIGA * 1000;

// Binary prefixes (powers of 2)
const KIBI: u64 = 1024;
const MEBI: u64 = KIBI * 1024;
const GIBI: u64 = MEBI * 1024;
const TEBI: u64 = GIBI * 1024;

pub trait FromByteUnit: Sized {
    type Err;

    fn from_str_byte_unit(str: impl Into<String>) -> Result<Self, Self::Err>;
}

impl FromByteUnit for u64 {
    type Err = anyhow::Error;

    fn from_str_byte_unit(str: impl Into<String>) -> Result<Self, Self::Err> {
        let str = str.into();
        let captures = SIZE_CAPTURES
            .captures(str.as_str())
            .ok_or_else(|| anyhow!("Invalid size representation"))?;

        let quantity = captures
            .get(1)
            .and_then(|v| v.as_str().parse::<u64>().ok())
            .ok_or_else(|| anyhow!("Not a number"))?;

        let multiplier = match &captures.get(2).map_or("b", |m| m.as_str()).to_lowercase()[..] {
            v if v.starts_with("ki") => KIBI,
            v if v.starts_with('k') => KILO,
            v if v.starts_with("mi") => MEBI,
            v if v.starts_with('m') => MEGA,
            v if v.starts_with("gi") => GIBI,
            v if v.starts_with('g') => GIGA,
            v if v.starts_with("ti") => TEBI,
            v if v.starts_with('t') => TERA,
            "b" => 1,
            _ => return Err(anyhow!("This doesn't work!")),
        };

        let size = quantity * multiplier;

        Ok(size)
    }
}
