use crate::{FamlValue, ast::faml_value::Distance};
use anyhow::anyhow;
use std::{collections::HashMap, f64::consts::PI, time::Duration};

pub trait InvokeExt {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue>;
}

impl InvokeExt for FamlValue {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        match self {
            FamlValue::None => ().invoke(func, args),
            FamlValue::Bool(b) => b.invoke(func, args),
            FamlValue::Int64(i) => i.invoke(func, args),
            FamlValue::Float64(f) => f.invoke(func, args),
            FamlValue::String(s) => s.invoke(func, args),
            FamlValue::Array(arr) => arr.invoke(func, args),
            FamlValue::Map(map) => map.invoke(func, args),
            FamlValue::Duration(dur) => dur.invoke(func, args),
            FamlValue::Distance(dist) => dist.invoke(func, args),
        }
    }
}

impl InvokeExt for () {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        match func {
            "to_str" if args.len() == 0 => Ok("null".to_string().into()),
            _ => Err(anyhow!(
                "unknown ().{func} with args[count: {}]",
                args.len()
            )),
        }
    }
}

impl InvokeExt for bool {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        match func {
            "to_str" if args.len() == 0 => Ok(self.to_string().into()),
            _ => Err(anyhow!(
                "unknown bool.{func} with args[count: {}]",
                args.len()
            )),
        }
    }
}

impl InvokeExt for i64 {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        if args.len() == 0 {
            Ok(match func {
                "abs" => self.abs().into(),
                "acos" => (*self as f64).acos().into(),
                "acosh" => (*self as f64).acosh().into(),
                "asin" => (*self as f64).asin().into(),
                "asinh" => (*self as f64).asinh().into(),
                "atan" => (*self as f64).atan().into(),
                "atanh" => (*self as f64).atanh().into(),
                "cbrt" => (*self as f64).cbrt().into(),
                "cos" => (*self as f64).cos().into(),
                "cosh" => (*self as f64).cosh().into(),
                "degree_to_radian" => ((*self as f64) * PI / 180.0).into(),
                "exp" => (*self as f64).exp().into(),
                "exp2" => (*self as f64).exp2().into(),
                "gamma" => (*self as f64).f64_gamma().into(),
                "is_negative" => self.is_negative().into(),
                "is_positive" => self.is_positive().into(),
                "ln" => (*self as f64).ln().into(),
                "log10" => (*self as f64).log10().into(),
                "log2" => (*self as f64).log2().into(),
                "radian_to_degree" => ((*self as f64) / PI * 180.0).into(),
                "sin" => (*self as f64).sin().into(),
                "sinh" => (*self as f64).sinh().into(),
                "sqrt" => (*self as f64).sqrt().into(),
                "tan" => (*self as f64).tan().into(),
                "tanh" => (*self as f64).tanh().into(),
                "to_float" => (*self as f64).into(),
                "to_quantified" => (*self as f64).to_quantified().into(),
                "to_str" => self.to_string().into(),
                _ => Err(anyhow!(
                    "unknown i64.{func} with args[count: {}]",
                    args.len()
                ))?,
            })
        } else if args.len() == 1 {
            if let Some(arg) = args[0].as_int() {
                Ok(match func {
                    "max" => (*self).max(arg).into(),
                    "min" => (*self).min(arg).into(),
                    _ => Err(anyhow!(
                        "unknown i64.{func} with args[count: {}]",
                        args.len()
                    ))?,
                })
            } else if let Some(arg) = args[0].as_float() {
                Ok(match func {
                    "atan2" => (*self as f64).atan2(arg).into(),
                    "hypot" => (*self as f64).hypot(arg).into(),
                    "log" => (*self as f64).log(arg).into(),
                    "max" => (*self as f64).max(arg).into(),
                    "min" => (*self as f64).min(arg).into(),
                    "pow" => (*self as f64).powf(arg).into(),
                    _ => Err(anyhow!(
                        "unknown i64.{func} with args[count: {}]",
                        args.len()
                    ))?,
                })
            } else {
                Err(anyhow!(
                    "unknown i64.{func} with args[count: {}]",
                    args.len()
                ))
            }
        } else {
            Err(anyhow!(
                "unknown i64.{func} with args[count: {}]",
                args.len()
            ))
        }
    }
}

impl InvokeExt for f64 {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        if args.len() == 0 {
            Ok(match func {
                "abs" => self.abs().into(),
                "acos" => self.acos().into(),
                "acosh" => self.acosh().into(),
                "asin" => self.asin().into(),
                "asinh" => self.asinh().into(),
                "atan" => self.atan().into(),
                "atanh" => self.atanh().into(),
                "cbrt" => self.cbrt().into(),
                "ceil" => self.ceil().into(),
                "ceili" => (self.ceil() as i64).into(),
                "cos" => self.cos().into(),
                "cosh" => self.cosh().into(),
                "exp" => self.exp().into(),
                "exp2" => self.exp2().into(),
                "floor" => self.floor().into(),
                "floori" => (self.floor() as i64).into(),
                "fract" => self.fract().into(),
                "gamma" => self.f64_gamma().into(),
                "is_finite" => self.is_finite().into(),
                "is_infinite" => self.is_infinite().into(),
                "is_nan" => self.is_nan().into(),
                "is_negative" => self.is_sign_negative().into(),
                "is_positive" => self.is_sign_positive().into(),
                "ln" => self.ln().into(),
                "log10" => self.log10().into(),
                "log2" => self.log2().into(),
                "next_down" => self.next_down().into(),
                "next_up" => self.next_up().into(),
                "round" => self.round().into(),
                "roundi" => (self.round() as i64).into(),
                "round_ties_even" => self.round_ties_even().into(),
                "round_ties_eveni" => (self.round_ties_even() as i64).into(),
                "signum" => self.signum().into(),
                "sin" => self.sin().into(),
                "sinh" => self.sinh().into(),
                "sqrt" => self.sqrt().into(),
                "tan" => self.tan().into(),
                "tanh" => self.tanh().into(),
                "trunc" => self.trunc().into(),
                "trunci" => (self.trunc() as i64).into(),
                "to_quantified" => self.to_quantified().into(),
                "to_degrees" => self.to_degrees().into(),
                "to_radians" => self.to_radians().into(),
                "to_str" => self.to_string().into(),
                _ => Err(anyhow!(
                    "unknown f64.{func} with args[count: {}]",
                    args.len()
                ))?,
            })
        } else if args.len() == 1 {
            if let Some(arg) = args[0].as_float() {
                Ok(match func {
                    "atan2" => self.atan2(arg).into(),
                    "hypot" => self.hypot(arg).into(),
                    "log" => self.log(arg).into(),
                    "max" => self.max(arg).into(),
                    "min" => self.min(arg).into(),
                    "pow" => self.powf(arg).into(),
                    _ => Err(anyhow!(
                        "unknown f64.{func} with args[count: {}]",
                        args.len()
                    ))?,
                })
            } else {
                Err(anyhow!(
                    "unknown f64.{func} with args[count: {}]",
                    args.len()
                ))
            }
        } else {
            Err(anyhow!(
                "unknown f64.{func} with args[count: {}]",
                args.len()
            ))
        }
    }
}

impl InvokeExt for String {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        if args.len() == 0 {
            Ok(match func {
                "is_empty" => FamlValue::Bool(self.is_empty()),
                "len" => FamlValue::Int64(self.len() as i64),
                "lines" => self.invoke("split", &vec![FamlValue::String("\n".to_string())])?,
                "to_lowercase" => FamlValue::String(self.to_lowercase()),
                "to_str" => FamlValue::String(self.clone()),
                "to_uppercase" => FamlValue::String(self.to_uppercase()),
                "trim" => FamlValue::String(self.trim().to_string()),
                _ => Err(anyhow!(
                    "unknown string.{func} with args[count: {}]",
                    args.len()
                ))?,
            })
        } else {
            if func == "split" || func == "split_once" || func == "split_without_empty" {
                let args: Vec<_> = args.iter().map(|p| p.as_str()).collect();
                let mut ret: Vec<FamlValue> = vec![];
                let mut target = &self[..];
                let is_nempty = func == "split_without_empty";
                while !target.is_empty() {
                    let mut ps: Vec<_> = args
                        .iter()
                        .filter_map(|p| target.find(p).map(|n| (n, p.len())))
                        .collect();
                    ps.sort_by_key(|p| p.0);
                    if let Some((n, len)) = ps.first() {
                        let r = target[..*n].to_string();
                        if !is_nempty || r.len() > 0 {
                            ret.push(FamlValue::String(r));
                        }
                        target = &target[*n + *len..];
                    }
                    if ps.len() == 0 || func == "split_once" {
                        let r = target.to_string();
                        if !is_nempty || r.len() > 0 {
                            ret.push(FamlValue::String(r));
                        }
                        break;
                    }
                }
                Ok(FamlValue::Array(ret))
            } else if args.len() == 1 {
                Ok(match func {
                    "contains" => {
                        let arg = args[0].as_str();
                        FamlValue::Bool(self.contains(&arg))
                    }
                    "ends_with" => {
                        let arg = args[0].as_str();
                        FamlValue::Bool(self.ends_with(&arg))
                    }
                    "find" => {
                        let arg = args[0].as_str();
                        match self.find(&arg) {
                            Some(n) => FamlValue::Int64(n as i64),
                            None => FamlValue::None,
                        }
                    }
                    "repeat" => {
                        let arg = args[0]
                            .as_int()
                            .ok_or(anyhow!("only type[int] arg for method[repeat]"))?;
                        let mut ret = "".to_string();
                        for _ in 0..arg {
                            ret += self;
                        }
                        FamlValue::String(ret)
                    }
                    "rfind" => {
                        let arg = args[0].as_str();
                        match self.rfind(&arg) {
                            Some(n) => FamlValue::Int64(n as i64),
                            None => FamlValue::None,
                        }
                    }
                    "split_at" => {
                        let arg = args[0]
                            .as_int()
                            .ok_or(anyhow!("only type[int] arg for method[split_at]"))?;
                        match self.len() >= arg as usize && arg >= 0 {
                            true => FamlValue::Array(vec![
                                FamlValue::String(self[..arg as usize].to_string()),
                                FamlValue::String(self[arg as usize..].to_string()),
                            ]),
                            false => FamlValue::Array(vec![FamlValue::String(self.to_string())]),
                        }
                    }
                    "starts_with" => {
                        let arg = args[0].as_str();
                        FamlValue::Bool(self.starts_with(&arg))
                    }
                    _ => Err(anyhow!(
                        "unknown string.{func} with args[count: {}]",
                        args.len()
                    ))?,
                })
            } else if args.len() == 2 {
                let pre = args[0].as_str();
                let post = args[1].as_str();
                Ok(FamlValue::String(match func {
                    "replace_once" => self.replacen(&pre, &post, 1),
                    "replace" => self.replace(&pre, &post),
                    _ => panic!("unreachable"),
                }))
            } else {
                Err(anyhow!(
                    "unknown string.{func} with args[count: {}]",
                    args.len()
                ))?
            }
        }
    }
}

impl InvokeExt for Vec<FamlValue> {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        match func {
            "join" if args.len() == 1 => {
                let sep = args[0].as_str();
                let mut ret = "".to_string();
                for (i, item) in self.iter().enumerate() {
                    if i > 0 {
                        ret += &sep;
                    }
                    ret += &item.as_str();
                }
                Ok(FamlValue::String(ret))
            }
            "len" if args.len() == 0 => Ok(FamlValue::Int64(self.len() as i64)),
            "pop" if args.len() == 0 => self.pop().ok_or(anyhow!("Array is empty")),
            "push" => {
                for arg in args {
                    self.push(arg.clone());
                }
                Ok(FamlValue::None)
            }
            "reverse" if args.len() == 0 => {
                let mut ret = self.clone();
                ret.reverse();
                Ok(FamlValue::Array(ret))
            }
            "to_str" if args.len() == 0 => {
                let mut s = "[ ".to_string();
                for (i, item) in self.iter_mut().enumerate() {
                    if i > 0 {
                        s += ", ";
                    }
                    s += &item.invoke("to_str", &vec![])?.as_str();
                }
                s += " ]";
                Ok(FamlValue::String(s))
            }
            _ => Err(anyhow!(
                "unknown vec.{func} with args[count: {}]",
                args.len()
            )),
        }
    }
}

impl InvokeExt for HashMap<String, FamlValue> {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        match func {
            "len" if args.len() == 0 => Ok(FamlValue::Int64(self.len() as i64)),
            "to_str" if args.len() == 0 => {
                let mut s = "{ ".to_string();
                for (i, (key, item)) in self.iter_mut().enumerate() {
                    if i > 0 {
                        s += ", ";
                    }
                    s += &key;
                    s += ": ";
                    s += &item.invoke("to_str", &vec![])?.as_str();
                }
                s += " }";
                Ok(FamlValue::String(s))
            }
            _ => Err(anyhow!(
                "unknown map.{func} with args[count: {}]",
                args.len()
            )),
        }
    }
}

impl InvokeExt for Duration {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        const G: f64 = Duration::from_secs(1).as_nanos() as f64;
        const D: f64 = Duration::from_secs(86400).as_secs() as f64;
        if args.len() == 0 {
            Ok(match func {
                "as_nanoseconds" => (self.as_nanos() as f64).into(),
                "as_microseconds" => (self.as_nanos() as f64 * 1_000.0).into(),
                "as_milliseconds" => (self.as_nanos() as f64 * 1_000_000.0).into(),
                "as_seconds" => (self.as_nanos() as f64 * G).into(),
                "as_mins" => (self.as_nanos() as f64 * G * 60.0).into(),
                "as_hours" => (self.as_nanos() as f64 * G * 3600.0).into(),
                "as_days" => (self.as_nanos() as f64 * G * D).into(),
                "as_weeks" => (self.as_nanos() as f64 * G * D * 7.0).into(),
                "as_months" => (self.as_nanos() as f64 * G * D * 30.0).into(),
                "as_years" => (self.as_nanos() as f64 * G * D * 365.0).into(),
                "to_str" => self.to_str().into(),
                _ => Err(anyhow!(
                    "unknown duration.{func} with args[count: {}]",
                    args.len()
                ))?,
            })
        } else {
            Err(anyhow!(
                "unknown duration.{func} with args[count: {}]",
                args.len()
            ))
        }
    }
}

impl InvokeExt for Distance {
    fn invoke(&mut self, func: &str, args: &Vec<FamlValue>) -> anyhow::Result<FamlValue> {
        if args.len() == 0 {
            Ok(match func {
                "to_megameters" => self.to_megameters().into(),
                "to_kilometers" => self.to_kilometers().into(),
                "to_meters" => self.to_meters().into(),
                "to_millimeters" => self.to_millimeters().into(),
                "to_micrometers" => self.to_micrometers().into(),
                "to_nanometers" => self.to_nanometers().into(),
                "to_str" => FamlValue::String(self.to_str()),
                _ => Err(anyhow!(
                    "unknown distance.{func} with args[count: {}]",
                    args.len()
                ))?,
            })
        } else {
            Err(anyhow!(
                "unknown distance.{func} with args[count: {}]",
                args.len()
            ))
        }
    }
}

pub trait F64Ext {
    fn f64_gamma(self) -> f64;
    fn to_quantified(self) -> String;
}

impl F64Ext for f64 {
    fn f64_gamma(self) -> f64 {
        let x = self;
        const G: f64 = 7.0;
        const P: [f64; 9] = [
            0.99999999999980993,
            676.5203681218851,
            -1259.1392167224028,
            771.32342877765313,
            -176.61502916214059,
            12.507343278686905,
            -0.13857109526572012,
            9.9843695780195716e-6,
            1.5056327351493116e-7,
        ];

        if x <= 0.0 {
            std::f64::consts::PI / ((-x * std::f64::consts::PI).sin() * (1.0 - x).f64_gamma())
        } else if x < 0.5 {
            std::f64::consts::PI / ((std::f64::consts::PI * x).sin() * (1.0 - x).f64_gamma())
        } else {
            let x = x - 1.0;
            let mut a = P[0];
            let t = x + G + 0.5;
            for (i, &p) in P.iter().enumerate().skip(1) {
                a += p / (x + i as f64);
            }
            (2.0 * std::f64::consts::PI).sqrt() * t.powf(x + 0.5) * (-t).exp() * a
        }
    }

    fn to_quantified(self) -> String {
        const U: f64 = 1024.0;
        match self {
            f if f <= U => format!("{f} B"),
            f if f <= U * U => format!("{} KB", f / U),
            f if f <= U * U * U => format!("{} MB", f / U / U),
            f if f <= U * U * U * U => format!("{} GB", f / U / U / U),
            f => format!("{} TB", f / U / U / U / U),
        }
    }
}

pub trait DurationExt {
    fn to_str(&self) -> String;
}

impl DurationExt for Duration {
    fn to_str(&self) -> String {
        const G: f64 = Duration::from_secs(1).as_nanos() as f64;
        const D: f64 = Duration::from_secs(86400).as_secs() as f64;
        match self.as_nanos() as f64 {
            v if v < 1_000.0 => format!("{v} nanoseconds").into(),
            v if v < 1_000_000.0 => format!("{} microseconds", v / 1_000.0).into(),
            v if v < G => format!("{} milliseconds", v / 1_000_000.0).into(),
            v if v < G * 60.0 => format!("{} seconds", v / G).into(),
            v if v < G * 3_600.0 => format!("{} mins", v / G / 60.0).into(),
            v if v < G * D => format!("{} hours", v / G / 3_600.0).into(),
            v if v < G * D * 7.0 => format!("{} days", v / G / D).into(),
            v if v < G * D * 30.0 => format!("{} weeks", v / G / D / 7.0).into(),
            v if v < G * D * 365.0 => format!("{} months", v / G / D / 30.0).into(),
            v => format!("{} years", v / G / D / 365.0).into(),
        }
    }
}
