use crate::cents::Cents;
use chrono::prelude::*;

pub struct Formatter<'a> {
    thousands_delimiter: &'a str,
    decimal_delimiter: &'a str,
    currency: &'a str,
    currency_position: CurrencyPosition,
    datetime_format: &'a str,
    date_format: &'a str,
}

enum CurrencyPosition {
    Before,
    After,
}

impl<'a> Formatter<'a> {
    pub fn new(locale: &str, currency: &'a str) -> Self {
        match locale {
            "de" => Formatter {
                thousands_delimiter: ".",
                decimal_delimiter: ",",
                currency,
                currency_position: CurrencyPosition::After,
                datetime_format: "%d.%m.%Y %H:%M:%S",
                date_format: "%d.%m.%Y",
            },
            // en, and all others ...
            _ => Formatter {
                thousands_delimiter: ",",
                decimal_delimiter: ".",
                currency,
                currency_position: CurrencyPosition::Before,
                datetime_format: "%Y-%m-%d %H:%M:%S",
                date_format: "%Y-%m-%d",
            },
        }
    }

    fn format_number(&self, mut s: String) -> String {
        let dec_ix = match s.find('.') {
            Some(ix) => {
                s.remove(ix);
                s.insert_str(ix, self.decimal_delimiter);
                ix
            }
            None => s.len(),
        };

        let mut offset = dec_ix % 3;
        let mut blocks = dec_ix / 3;
        if offset == 0 {
            blocks -= 1;
            offset = 3;
        }
        for i in 0..blocks {
            let ix = offset + (blocks - i - 1) * 3;
            s.insert_str(ix, self.thousands_delimiter);
        }

        s
    }

    pub fn f64(&self, n: f64) -> String {
        let s = n.to_string();
        self.format_number(s)
    }

    pub fn decimal(&self, n: Cents) -> String {
        let s = n.to_string();
        self.format_number(s)
    }

    pub fn currency(&self, n: Cents) -> String {
        let mut s = self.decimal(n);
        match self.currency_position {
            CurrencyPosition::Before => {
                s.insert(0, ' ');
                s.insert_str(0, self.currency);
            }
            CurrencyPosition::After => {
                s = s + " " + self.currency;
            }
        }
        s
    }

    #[allow(unused)]
    pub fn datetime(&self, dt: &DateTime<Utc>) -> String {
        dt.format(self.datetime_format).to_string()
    }

    pub fn date(&self, dt: &DateTime<Utc>) -> String {
        dt.format(self.date_format).to_string()
    }

    pub fn plus_minus(&self, n: f64) -> String {
        let mut s = n.to_string();
        match s.find('.') {
            Some(ix) => match s.len() - ix - 1 {
                0 => s += "00",
                1 => s += "0",
                _ => {}
            },
            None => s += ".00",
        };
        s = self.format_number(s);
        match n {
            n if n == 0f64 => String::from("±") + &s,
            n if n > 0f64 => String::from("+") + &s,
            _ => s,
        }
    }
}

#[cfg(test)]
mod test {
    use super::Formatter;
    use crate::cents::Cents;

    #[test]
    fn unchanged() {
        let fmt = Formatter::new("de", "€");
        assert_eq!(fmt.decimal(Cents::new(123, 0)), "123,00");
    }

    #[test]
    fn decimal_delimiter() {
        let fmt = Formatter::new("de", "€");
        assert_eq!(fmt.decimal(Cents::new(1234, 2)), "12,34");
    }

    #[test]
    fn thousands_delimiter() {
        let fmt = Formatter::new("de", "€");
        assert_eq!(fmt.decimal(Cents::new(12345, 2)), "123,45");
        assert_eq!(fmt.decimal(Cents::new(123456, 2)), "1.234,56");
        assert_eq!(fmt.decimal(Cents::new(1234567, 2)), "12.345,67");
        assert_eq!(fmt.decimal(Cents::new(12345678, 2)), "123.456,78");
        assert_eq!(fmt.decimal(Cents::new(123456789, 2)), "1.234.567,89");
        assert_eq!(fmt.decimal(Cents::new(1234567890, 2)), "12.345.678,90");
        assert_eq!(fmt.decimal(Cents::new(12345678900, 2)), "123.456.789,00");
    }

    #[test]
    fn at_least_two_decimal_places() {
        let fmt = Formatter::new("de", "€");
        assert_eq!(fmt.currency(Cents::new(12, 0)), "12,00 €");
        assert_eq!(fmt.currency(Cents::new(123, 1)), "12,30 €");
        assert_eq!(fmt.currency(Cents::new(1234, 2)), "12,34 €");
        assert_eq!(fmt.currency(Cents::new(12345, 3)), "12,345 €");
    }

    #[test]
    fn currency_after() {
        let fmt = Formatter::new("de", "€");
        assert_eq!(fmt.currency(Cents::new(42, 0)), "42,00 €");
    }

    #[test]
    fn currency_before() {
        let fmt = Formatter::new("en", "€");
        assert_eq!(fmt.currency(Cents::new(42, 0)), "€ 42.00");
    }

    #[test]
    fn plus_minus() {
        let fmt = Formatter::new("de", "€");
        assert_eq!(fmt.plus_minus(0f64), "±0,00");
        assert_eq!(fmt.plus_minus(1f64), "+1,00");
        assert_eq!(fmt.plus_minus(-1f64), "-1,00");
        assert_eq!(fmt.plus_minus(1.2), "+1,20");
        assert_eq!(fmt.plus_minus(-1.2), "-1,20");
        assert_eq!(fmt.plus_minus(1.23), "+1,23");
        assert_eq!(fmt.plus_minus(-1.23), "-1,23");
        assert_eq!(fmt.plus_minus(1.234), "+1,234");
        assert_eq!(fmt.plus_minus(-1.234), "-1,234");
    }
}
