// License: see LICENSE file at root directory of `master` branch

//! # Number parser

#![cfg(feature="std")]

use {
    core::fmt::{self, Display, Formatter, Write},
    crate::{Error, Number, Result},
};

#[derive(Debug)]
enum FloatExp {
    None,
    Some,
    Exp {
        x: f64,
        fract: Option<u8>,
    },
}

impl Display for FloatExp {

    fn fmt(&self, f: &mut Formatter) -> core::result::Result<(), fmt::Error> {
        match self {
            FloatExp::None | FloatExp::Some => f.write_str(concat!()),
            FloatExp::Exp { x, .. } => {
                f.write_char('e')?;
                x.fmt(f)
            },
        }
    }

}

#[derive(Debug)]
enum X {
    #[cfg(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))]
    Signed(i64),
    #[cfg(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))]
    Unsigned(u64),

    #[cfg(not(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64")))]
    Signed(isize),
    #[cfg(not(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64")))]
    Unsigned(usize),

    Float {
        x: f64,
        fract: Option<u8>,
        exp: FloatExp,
    },
}

impl Display for X {

    fn fmt(&self, f: &mut Formatter) -> core::result::Result<(), fmt::Error> {
        match self {
            X::Signed(x) => x.fmt(f),
            X::Unsigned(x) => x.fmt(f),
            X::Float { x, fract: _, exp } => write!(f, "{x}{exp}", x=x, exp=exp),
        }
    }

}

/// # Number parser
#[derive(Debug)]
pub struct NumberParser {
    x: Option<X>,
}

impl NumberParser {

    /// # Makes new instance
    pub fn new() -> Self {
        Self {
            x: None,
        }
    }

    /// # Adds new byte
    pub fn add(&mut self, b: &u8) -> Result<()> {
        match b {
            b'-' => match self.x.as_mut() {
                None => self.x = Some(X::Signed(0)),
                Some(X::Signed(x)) => return Err(Error::from(__!("Invalid integer: {parsed}{c}", parsed=x, c=char::from(*b)))),
                Some(X::Unsigned(x)) => return Err(Error::from(__!("Invalid integer: {parsed}{c}", parsed=x, c=char::from(*b)))),
                Some(X::Float { x, fract: _, exp }) => match exp {
                    FloatExp::Some => *exp = FloatExp::Exp { x: -0.0, fract: None },
                    _ => return Err(Error::from(__!("Invalid float: {x}{c}", x=x, c=char::from(*b)))),
                },
            },
            b'+' => match self.x.as_mut() {
                None => return Err(Error::from(__!("Invalid sign: {c}", c=char::from(*b)))),
                Some(X::Signed(x)) => return Err(Error::from(__!("Invalid integer: {parsed}{c}", parsed=x, c=char::from(*b)))),
                Some(X::Unsigned(x)) => return Err(Error::from(__!("Invalid integer: {parsed}{c}", parsed=x, c=char::from(*b)))),
                Some(X::Float { x, fract: _, exp }) => match exp {
                    FloatExp::Some => *exp = FloatExp::Exp { x: 0.0, fract: None },
                    FloatExp::None => return Err(Error::from(__!("Invalid float: {x}{c}", x=x, c=char::from(*b)))),
                    FloatExp::Exp {..} => return Err(Error::from(__!("Invalid float: {x}{exp}{c}", x=x, exp=exp, c=char::from(*b)))),
                },
            },
            b'e' | b'E' => match self.x.as_mut() {
                None => return Err(Error::from(__!("Invalid sign: {c}", c=char::from(*b)))),
                Some(X::Signed(x)) => self.x = Some(X::Float { x: *x as f64, fract: None, exp: FloatExp::Some }),
                Some(X::Unsigned(x)) => self.x = Some(X::Float { x: *x as f64, fract: None, exp: FloatExp::Some }),
                Some(X::Float { x, fract: _, exp }) => match exp {
                    FloatExp::None => *exp = FloatExp::Some,
                    FloatExp::Some => return Err(Error::from(__!("Invalid float: {x}e{c}", x=x, c=char::from(*b)))),
                    FloatExp::Exp {..} => return Err(Error::from(__!("Invalid float: {x}{exp}{c}", x=x, exp=exp, c=char::from(*b)))),
                },
            },
            b'.' => match self.x.as_mut() {
                None => return Err(Error::from(__!("Invalid sign: {c}", c=char::from(*b)))),
                Some(X::Signed(x)) => self.x = Some(X::Float { x: *x as f64, fract: Some(0), exp: FloatExp::None }),
                Some(X::Unsigned(x)) => self.x = Some(X::Float { x: *x as f64, fract: Some(0), exp: FloatExp::None }),
                Some(X::Float { x, fract, exp }) => match fract {
                    None => *fract = Some(0),
                    Some(_) => match exp {
                        FloatExp::None => return Err(Error::from(__!("Invalid float: {x}{c}", x=x, c=char::from(*b)))),
                        FloatExp::Some => return Err(Error::from(__!("Invalid float: {x}e{c}", x=x, c=char::from(*b)))),
                        FloatExp::Exp { x: _, fract } => match fract {
                            None => *fract = Some(0),
                            Some(_) => return Err(Error::from(__!("Invalid float: {x}{exp}{c}", x=x, exp=exp, c=char::from(*b)))),
                        },
                    },
                },
            },
            b'0'..=b'9' => {
                let b = b - b'0';
                match self.x.as_mut() {
                    None => self.x = Some(X::Unsigned(b.into())),
                    Some(X::Signed(x)) => match x.checked_mul(10).map(|x| x.checked_sub(b.into())) {
                        Some(Some(n)) => *x = n,
                        // Cast into float. We'll handle this in parse(). Basically, we'll need either fract or exp to be some.
                        _ => self.x = Some(X::Float { x: *x as f64 * 10.0 - f64::from(b), fract: None, exp: FloatExp::None }),
                    },
                    Some(X::Unsigned(x)) => match x.checked_mul(10).map(|x| x.checked_add(b.into())) {
                        Some(Some(n)) => *x = n,
                        // Cast into float. We'll handle this in parse(). Basically, we'll need either fract or exp to be some.
                        _ => self.x = Some(X::Float { x: *x as f64 * 10.0 + f64::from(b), fract: None, exp: FloatExp::None }),
                    },
                    Some(X::Float { x, fract, exp }) => match exp {
                        FloatExp::None => if add_digit(x, fract.as_mut(), b) == false {
                            return Err(Error::from(__!("Float fractional part is too long: {x}{b}", x=x, b=b)));
                        },
                        FloatExp::Some => *exp = FloatExp::Exp { x: b.into(), fract: None },
                        FloatExp::Exp { x, fract } => if add_digit(x, fract.as_mut(), b) == false {
                            return Err(Error::from(__!("Float's exponent's fractional part is too long: {x}{b}", x=x, b=b)));
                        },
                    },
                };
            },
            _ => return Err(Error::from(match self.x.as_ref() {
                None => __!("Invalid number: {c}", c=char::from(*b)),
                Some(X::Signed(x)) => __!("Invalid number: {parsed}{c}", parsed=x, c=char::from(*b)),
                Some(X::Unsigned(x)) => __!("Invalid number: {parsed}{c}", parsed=x, c=char::from(*b)),
                Some(x) => __!("Invalid number: {parsed}{c}", parsed=x, c=char::from(*b)),
            })),
        };

        Ok(())
    }

    /// # Parses
    ///
    /// After calling this function, internal state will be reset.
    pub fn parse(&mut self) -> Result<Number> {
        match self.x.take() {
            None => Err(Error::from(__!("Number is empty"))),
            Some(X::Signed(x)) => Ok(x.into()),
            Some(X::Unsigned(x)) => Ok(x.into()),
            Some(X::Float { x, fract, exp }) => match exp {
                FloatExp::None => match fract.is_some() {
                    true => Ok(x.into()),
                    false => {
                        let large_or_small = match x.is_sign_positive() {
                            true => "large",
                            false => "small",
                        };
                        Err(Error::from(__!("Integer too {large_or_small}: {x}", large_or_small=large_or_small, x=x.trunc())))
                    },
                },
                FloatExp::Some => Err(Error::from(__!("Invalid float: {x}e", x=x))),
                FloatExp::Exp { x: exp_x, fract: _ } => Ok((x * 10_f64.powf(exp_x)).into()),
            },
        }
    }

}

const FRACTS: [[f64; 10]; 20] = [
    [0.0, 1e-1, 2e-1, 3e-1, 4e-1, 5e-1, 6e-1, 7e-1, 8e-1, 9e-1],
    [0.0, 1e-2, 2e-2, 3e-2, 4e-2, 5e-2, 6e-2, 7e-2, 8e-2, 9e-2],
    [0.0, 1e-3, 2e-3, 3e-3, 4e-3, 5e-3, 6e-3, 7e-3, 8e-3, 9e-3],
    [0.0, 1e-4, 2e-4, 3e-4, 4e-4, 5e-4, 6e-4, 7e-4, 8e-4, 9e-4],
    [0.0, 1e-5, 2e-5, 3e-5, 4e-5, 5e-5, 6e-5, 7e-5, 8e-5, 9e-5],
    [0.0, 1e-6, 2e-6, 3e-6, 4e-6, 5e-6, 6e-6, 7e-6, 8e-6, 9e-6],
    [0.0, 1e-7, 2e-7, 3e-7, 4e-7, 5e-7, 6e-7, 7e-7, 8e-7, 9e-7],
    [0.0, 1e-8, 2e-8, 3e-8, 4e-8, 5e-8, 6e-8, 7e-8, 8e-8, 9e-8],
    [0.0, 1e-9, 2e-9, 3e-9, 4e-9, 5e-9, 6e-9, 7e-9, 8e-9, 9e-9],

    [0.0, 1e-10, 2e-10, 3e-10, 4e-10, 5e-10, 6e-10, 7e-10, 8e-10, 9e-10],
    [0.0, 1e-11, 2e-11, 3e-11, 4e-11, 5e-11, 6e-11, 7e-11, 8e-11, 9e-11],
    [0.0, 1e-12, 2e-12, 3e-12, 4e-12, 5e-12, 6e-12, 7e-12, 8e-12, 9e-12],
    [0.0, 1e-13, 2e-13, 3e-13, 4e-13, 5e-13, 6e-13, 7e-13, 8e-13, 9e-13],
    [0.0, 1e-14, 2e-14, 3e-14, 4e-14, 5e-14, 6e-14, 7e-14, 8e-14, 9e-14],
    [0.0, 1e-15, 2e-15, 3e-15, 4e-15, 5e-15, 6e-15, 7e-15, 8e-15, 9e-15],
    [0.0, 1e-16, 2e-16, 3e-16, 4e-16, 5e-16, 6e-16, 7e-16, 8e-16, 9e-16],
    [0.0, 1e-17, 2e-17, 3e-17, 4e-17, 5e-17, 6e-17, 7e-17, 8e-17, 9e-17],
    [0.0, 1e-18, 2e-18, 3e-18, 4e-18, 5e-18, 6e-18, 7e-18, 8e-18, 9e-18],
    [0.0, 1e-19, 2e-19, 3e-19, 4e-19, 5e-19, 6e-19, 7e-19, 8e-19, 9e-19],
    [0.0, 1e-20, 2e-20, 3e-20, 4e-20, 5e-20, 6e-20, 7e-20, 8e-20, 9e-20],
];

#[test]
fn tests() {
    FRACTS.iter().enumerate().for_each(|(i, fracts)| {
        assert_eq!(fracts.len(), 10);
        assert_eq!(fracts[0], 0.0);
        fracts[1..].iter().enumerate().for_each(|(k, f)| assert!((f - 10_f64.powf(-((i + 1) as f64)) * (k + 1) as f64).abs() < 1e-10));
    });
}

/// # Adds a digit
#[must_use]
fn add_digit(x: &mut f64, fract: Option<&mut u8>, digit: u8) -> bool {
    if digit > 9 {
        return false;
    }

    match fract {
        None => {
            *x = *x * 10.0;
            if digit > 0 {
                let digit = f64::from(digit);
                match x.is_sign_positive() {
                    true => *x += digit,
                    false => *x -= digit,
                };
            }
            true
        },
        Some(fract) => match fract.checked_add(1) {
            Some(new_fract) => {
                if digit > 0 {
                    let digit = match FRACTS.get(usize::from(*fract)).map(|fracts| fracts.get(usize::from(digit))) {
                        Some(Some(digit)) => *digit,
                        _ => 10_f64.powf(-f64::from(new_fract)) * f64::from(digit),
                    };
                    match x.is_sign_positive() {
                        true => *x += digit,
                        false => *x -= digit,
                    };
                }
                *fract = new_fract;
                true
            },
            None => false,
        },
    }
}
