// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![doc(hidden)]

macro_rules! assert_approx_eq {
    ($a:expr, $b:expr) => ({
        use num::Float;
        let (a, b) = (&$a, &$b);
        assert!((*a - *b).abs() < 1.0e-6,
                "{} is not approximately equal to {}", *a, *b);
    })
}

macro_rules! from_str_radix_float_impl {
    ($T:ty) => {
        fn from_str_radix(src: &str, radix: u32)
                          -> Result<$T, ParseFloatError> {
            use num::dec2flt::{pfe_empty, pfe_invalid};

            // Special values
            match src {
                "inf"   => return Ok(Float::infinity()),
                "-inf"  => return Ok(Float::neg_infinity()),
                "NaN"   => return Ok(Float::nan()),
                _       => {},
            }

            let (is_positive, src) =  match src.slice_shift_char() {
                None             => return Err(pfe_empty()),
                Some(('-', ""))  => return Err(pfe_empty()),
                Some(('-', src)) => (false, src),
                Some((_, _))     => (true,  src),
            };

            // The significand to accumulate
            let mut sig = if is_positive { 0.0 } else { -0.0 };
            // Necessary to detect overflow
            let mut prev_sig = sig;
            let mut cs = src.chars().enumerate();
            // Exponent prefix and exponent index offset
            let mut exp_info = None::<(char, usize)>;

            // Parse the integer part of the significand
            for (i, c) in cs.by_ref() {
                match c.to_digit(radix) {
                    Some(digit) => {
                        // shift significand one digit left
                        sig = sig * (radix as $T);

                        // add/subtract current digit depending on sign
                        if is_positive {
                            sig = sig + ((digit as isize) as $T);
                        } else {
                            sig = sig - ((digit as isize) as $T);
                        }

                        // Detect overflow by comparing to last value, except
                        // if we've not seen any non-zero digits.
                        if prev_sig != 0.0 {
                            if is_positive && sig <= prev_sig
                                { return Ok(Float::infinity()); }
                            if !is_positive && sig >= prev_sig
                                { return Ok(Float::neg_infinity()); }

                            // Detect overflow by reversing the shift-and-add process
                            if is_positive && (prev_sig != (sig - digit as $T) / radix as $T)
                                { return Ok(Float::infinity()); }
                            if !is_positive && (prev_sig != (sig + digit as $T) / radix as $T)
                                { return Ok(Float::neg_infinity()); }
                        }
                        prev_sig = sig;
                    },
                    None => match c {
                        'e' | 'E' | 'p' | 'P' => {
                            exp_info = Some((c, i + 1));
                            break;  // start of exponent
                        },
                        '.' => {
                            break;  // start of fractional part
                        },
                        _ => {
                            return Err(pfe_invalid())
                        },
                    },
                }
            }

            // If we are not yet at the exponent parse the fractional
            // part of the significand
            if exp_info.is_none() {
                let mut power = 1.0;
                for (i, c) in cs.by_ref() {
                    match c.to_digit(radix) {
                        Some(digit) => {
                            // Decrease power one order of magnitude
                            power = power / (radix as $T);
                            // add/subtract current digit depending on sign
                            sig = if is_positive {
                                sig + (digit as $T) * power
                            } else {
                                sig - (digit as $T) * power
                            };
                            // Detect overflow by comparing to last value
                            if is_positive && sig < prev_sig
                                { return Ok(Float::infinity()); }
                            if !is_positive && sig > prev_sig
                                { return Ok(Float::neg_infinity()); }
                            prev_sig = sig;
                        },
                        None => match c {
                            'e' | 'E' | 'p' | 'P' => {
                                exp_info = Some((c, i + 1));
                                break; // start of exponent
                            },
                            _ => {
                                return Err(pfe_invalid())
                            },
                        },
                    }
                }
            }

            // Parse and calculate the exponent
            let exp = match exp_info {
                Some((c, offset)) => {
                    let base = match c {
                        'E' | 'e' if radix == 10 => 10.0,
                        'P' | 'p' if radix == 16 => 2.0,
                        _ => return Err(pfe_invalid()),
                    };

                    // Parse the exponent as decimal integer
                    let src = &src[offset..];
                    let (is_positive, exp) = match src.slice_shift_char() {
                        Some(('-', src)) => (false, src.parse::<usize>()),
                        Some(('+', src)) => (true,  src.parse::<usize>()),
                        Some((_, _))     => (true,  src.parse::<usize>()),
                        None             => return Err(pfe_invalid()),
                    };

                    match (is_positive, exp) {
                        (true,  Ok(exp)) => base.powi(exp as i32),
                        (false, Ok(exp)) => 1.0 / base.powi(exp as i32),
                        (_, Err(_))      => return Err(pfe_invalid()),
                    }
                },
                None => 1.0, // no exponent
            };

            Ok(sig * exp)
        }
    }
}
