//! Plural operands in compliance with [CLDR Plural Rules](http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules).
//!
//! See [full operands description](http://unicode.org/reports/tr35/tr35-numbers.html#Operands).
//!
//! # Examples
//!
//! From int
//!
//! ```
//! use std::convert::TryFrom;
//! use intl_pluralrules::operands::*;
//! assert_eq!(Ok(PluralOperands {
//!    n: 2_f64,
//!    i: 2,
//!    v: 0,
//!    w: 0,
//!    f: 0,
//!    t: 0,
//! }), PluralOperands::try_from(2))
//! ```
//!
//! From float
//!
//! ```
//! use std::convert::TryFrom;
//! use intl_pluralrules::operands::*;
//! assert_eq!(Ok(PluralOperands {
//!    n: 1234.567_f64,
//!    i: 1234,
//!    v: 3,
//!    w: 3,
//!    f: 567,
//!    t: 567,
//! }), PluralOperands::try_from("-1234.567"))
//! ```
//!
//! From &str
//!
//! ```
//! use std::convert::TryFrom;
//! use intl_pluralrules::operands::*;
//! assert_eq!(Ok(PluralOperands {
//!    n: 123.45_f64,
//!    i: 123,
//!    v: 2,
//!    w: 2,
//!    f: 45,
//!    t: 45,
//! }), PluralOperands::try_from(123.45))
//! ```
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
use std::convert::TryFrom;
use std::isize;
use std::str::FromStr;

/// A full plural operands representation of a number. See [CLDR Plural Rules](http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules) for complete operands description.
#[derive(Debug, PartialEq)]
pub struct PluralOperands {
    /// Absolute value of input
    pub n: f64,
    /// Integer value of input
    pub i: usize,
    /// Number of visible fraction digits with trailing zeros
    pub v: usize,
    /// Number of visible fraction digits without trailing zeros
    pub w: usize,
    /// Visible fraction digits with trailing zeros
    pub f: usize,
    /// Visible fraction digits without trailing zeros
    pub t: usize,
}

impl<'a> TryFrom<&'a str> for PluralOperands {
    type Error = &'static str;

    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
        let abs_str = if input.starts_with('-') {
            &input[1..]
        } else {
            &input
        };

        let absolute_value = f64::from_str(&abs_str).map_err(|_| "Incorrect number passed!")?;

        let integer_digits;
        let num_fraction_digits0;
        let num_fraction_digits;
        let fraction_digits0;
        let fraction_digits;

        if let Some(dec_pos) = abs_str.find('.') {
            let int_str = &abs_str[..dec_pos];
            let dec_str = &abs_str[(dec_pos + 1)..];

            integer_digits =
                usize::from_str(&int_str).map_err(|_| "Could not convert string to integer!")?;

            let backtrace = dec_str.trim_end_matches('0');

            num_fraction_digits0 = dec_str.len() as usize;
            num_fraction_digits = backtrace.len() as usize;
            fraction_digits0 =
                usize::from_str(dec_str).map_err(|_| "Could not convert string to integer!")?;
            fraction_digits = usize::from_str(backtrace).unwrap_or(0);
        } else {
            integer_digits = absolute_value as usize;
            num_fraction_digits0 = 0;
            num_fraction_digits = 0;
            fraction_digits0 = 0;
            fraction_digits = 0;
        }

        Ok(PluralOperands {
            n: absolute_value,
            i: integer_digits,
            v: num_fraction_digits0,
            w: num_fraction_digits,
            f: fraction_digits0,
            t: fraction_digits,
        })
    }
}

macro_rules! impl_integer_type {
    ($ty:ident) => {
        impl From<$ty> for PluralOperands {
            fn from(input: $ty) -> Self {
                // XXXManishearth converting from u32 or u64 to isize may wrap
                PluralOperands {
                    n: input as f64,
                    i: input as usize,
                    v: 0,
                    w: 0,
                    f: 0,
                    t: 0,
                }
            }
        }
    };
    ($($ty:ident)+) => {
        $(impl_integer_type!($ty);)+
    };
}

macro_rules! impl_signed_integer_type {
    ($ty:ident) => {
        impl TryFrom<$ty> for PluralOperands {
            type Error = &'static str;
            fn try_from(input: $ty) -> Result<Self, Self::Error> {
                // XXXManishearth converting from i64 to isize may wrap
                let x = (input as isize).checked_abs().ok_or("Number too big")?;
                Ok(PluralOperands {
                    n: x as f64,
                    i: x as usize,
                    v: 0,
                    w: 0,
                    f: 0,
                    t: 0,
                })
            }
        }
    };
    ($($ty:ident)+) => {
        $(impl_signed_integer_type!($ty);)+
    };
}

macro_rules! impl_convert_type {
    ($ty:ident) => {
        impl TryFrom<$ty> for PluralOperands {
            type Error = &'static str;
            fn try_from(input: $ty) -> Result<Self, Self::Error> {
                let as_str: &str = &input.to_string();
                PluralOperands::try_from(as_str)
            }
        }
    };
    ($($ty:ident)+) => {
        $(impl_convert_type!($ty);)+
    };
}

impl_integer_type!(u8 u16 u32 u64 usize);
impl_signed_integer_type!(i8 i16 i32 i64 isize);
// XXXManishearth we can likely have dedicated float impls here
impl_convert_type!(f32 f64 String);
