/*
 * unit_converter lib.rs
 * - main library file for the asklyphe unit converter
 *
 * Copyright (C) 2025 Real Microsoft, LLC
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use crate::length_units::{LengthUnit, LENGTH_NAMES, LENGTH_STORE, PRECISION};
use crate::unit_defs::{ConvertTo, MetricPrefix};
use astro_float::{BigFloat, Consts, Radix, RoundingMode};

pub const MAX_PRECISION: usize = 2048;

pub mod length_units;
pub mod unit_defs;

/// removes a leading conversion word
pub fn remove_leading_conversion_word(input_str: &str) -> String {
    const CONVERSION_WORDS: &[&str] = &["to", "into", "as", "in", "using"];

    let input_str = input_str.trim_start();

    for word in CONVERSION_WORDS {
        if input_str.starts_with(word) {
            return input_str.strip_prefix(word).unwrap().to_string();
        }
    }
    input_str.to_string()
}

/// takes an input starting with a number value, and returns it as a bigfloat, plus whatevers left
pub fn parse_number(input_str: &str) -> (BigFloat, String) {
    let mut cc = Consts::new().expect("constants cache not initialized!");
    // parse digits, scientific notation, inf, and nan
    let mut buf = "".to_string();
    let mut input = input_str.chars();
    let mut first = true;
    let mut exp = false;
    let mut nan = 0;
    let mut inf = 0;
    let mut cursor = 0;
    while let Some(c) = input.next() {
        cursor += 1;
        if c == ',' || c.is_whitespace() {
            continue;
        }
        if exp && c == '+' && first {
            buf.push('+');
            first = false;
            continue;
        }
        if c.is_numeric() || (c == '-' && first) || c == '.' {
            buf.push(c);
            first = false;
        } else {
            let lower = c.to_ascii_lowercase();
            if inf > 0 {
                if inf == 1 && lower == 'n' {
                    buf.push('n');
                    inf += 1;
                } else if inf == 2 && lower == 'f' {
                    buf.push('f');
                    break;
                } else if inf == 1 {
                    buf.pop(); // pop i
                    cursor -= 1;
                    cursor -= 1;
                    break;
                } else if inf == 2 {
                    buf.pop(); // pop n
                    buf.pop(); // pop i
                    cursor -= 1;
                    cursor -= 2;
                    break;
                }
            } else if nan > 0 {
                if nan == 1 && lower == 'a' {
                    buf.push('a');
                    nan += 1;
                } else if nan == 2 && lower == 'n' {
                    buf.push('n');
                    break;
                } else if nan == 1 {
                    buf.pop(); // pop n
                    cursor -= 1;
                    cursor -= 1;
                    break;
                } else if nan == 2 {
                    buf.pop(); // pop a
                    buf.pop(); // pop n
                    cursor -= 1;
                    cursor -= 2;
                    break;
                }
            } else if !exp {
                if lower == 'e' {
                    exp = true;
                    first = true;
                    buf.push('e');
                } else if lower == 'i' {
                    inf = 1;
                    buf.push('i');
                } else if lower == 'n' {
                    nan = 1;
                    buf.push('n');
                } else {
                    cursor -= 1;
                    break;
                }
            } else {
                cursor -= 1;
                break;
            }
        }
    }
    let mut output = "".to_string();
    let input = input_str.chars();
    for c in input {
        if cursor > 0 {
            cursor -= 1;
        } else {
            output.push(c);
        }
    }

    let value = BigFloat::parse(&buf, Radix::Dec, MAX_PRECISION, RoundingMode::None, &mut cc);

    (value, output)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::unit_defs::{BestUnit, ConvertTo, FromUnitName};
    use std::str::FromStr;

    #[test]
    fn it_works() {
        let (number, remainder) = parse_number("  2 meter to foot");
        println!("number:{} remainder:{}", number, remainder);
        let (unit_a, remainder) = LengthUnit::parse(remainder).unwrap();
        println!("unit_a: {:?}, remainer: {}", unit_a, remainder);
        let remainder = remove_leading_conversion_word(&remainder);
        println!("stripped: {}", remainder);
        let (unit_b, _) = LengthUnit::parse(remainder).unwrap();
        println!("unit_b: {:?}", unit_b);
        let conversion = unit_a.convert_to(number, unit_b);
        let approx = f64::from_str(&format!("{}", conversion)).unwrap();
        //conversion.set_exponent(Exponent::from(1));
        println!("conversion: {}", conversion);
        println!(
            "approx: {} {}",
            approx,
            LENGTH_NAMES.get(&unit_b).unwrap().first().unwrap().1
        );
    }
    #[test]
    fn best_unit() {
        let (number, remainder) = parse_number("  25 ft");
        println!("number:{} remainder:{}", number, remainder);
        let (unit_a, remainder) = LengthUnit::parse(remainder).unwrap();
        println!("unit_a: {:?}, remainer: {}", unit_a, remainder);
        let best_unit = unit_a.best_unit(number.clone());
        println!("best unit: {:?}", best_unit);
        let conversion = unit_a.convert_to(number, best_unit);
        let approx = f64::from_str(&format!("{}", conversion)).unwrap();
        //conversion.set_exponent(Exponent::from(1));
        println!("conversion: {}", conversion);
        println!(
            "approx: {} {}",
            approx,
            LENGTH_NAMES.get(&best_unit).unwrap().first().unwrap().1
        );
    }
}
