/*
 * unit_converter length_units.rs
 * - definitions of length units
 *
 * 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::unit_defs::{BestUnit, ConvertTo, FromUnitName, MetricPrefix, UnitName};
use astro_float::{BigFloat, RoundingMode};
use once_cell::sync::Lazy;
use std::collections::BTreeMap;

pub const PRECISION: usize = 2048;

// length unit -> value in meters
pub static LENGTH_STORE: Lazy<BTreeMap<LengthUnit, BigFloat>> = Lazy::new(|| {
    let mut store = BTreeMap::new();

    store.insert(
        LengthUnit::Meter(MetricPrefix::Pico),
        BigFloat::from_f64(0.000000000001, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Nano),
        BigFloat::from_f64(0.000000001, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Micro),
        BigFloat::from_f64(0.000001, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Milli),
        BigFloat::from_f64(0.001, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Centi),
        BigFloat::from_f64(0.01, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Deci),
        BigFloat::from_f64(0.1, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::None),
        BigFloat::from_f64(1.0, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Deca),
        BigFloat::from_f64(10.0, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Hecto),
        BigFloat::from_f64(100.0, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Kilo),
        BigFloat::from_f64(1000.0, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Mega),
        BigFloat::from_f64(1_000_000.0, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Giga),
        BigFloat::from_f64(1_000_000_000.0, PRECISION),
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Tera),
        BigFloat::from_f64(1_000_000_000_000.0, PRECISION),
    );
    // inch is 2.54 cm
    store.insert(
        LengthUnit::Inch,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Centi))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(2.54, PRECISION)),
    );
    // thou is 1/1000 inches
    store.insert(
        LengthUnit::Thou,
        store
            .get(&LengthUnit::Inch)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(1.0 / 1000.0, PRECISION)),
    );
    // foot is 12 inches
    store.insert(
        LengthUnit::Foot,
        store
            .get(&LengthUnit::Inch)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(12.0, PRECISION)),
    );
    // yard is 3 feet
    store.insert(
        LengthUnit::Yard,
        store
            .get(&LengthUnit::Foot)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(3.0, PRECISION)),
    );
    // mile is 5280 feet
    store.insert(
        LengthUnit::Mile,
        store
            .get(&LengthUnit::Foot)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(5280.0, PRECISION)),
    );
    // league is 3 miles
    store.insert(
        LengthUnit::League,
        store
            .get(&LengthUnit::Mile)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(3.0, PRECISION)),
    );
    // fathom is 2 yards
    store.insert(
        LengthUnit::Fathom,
        store
            .get(&LengthUnit::Yard)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(2.0, PRECISION)),
    );
    // nautical mile is 1852 m
    store.insert(
        LengthUnit::NauticalMile,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(1852.0, PRECISION)),
    );
    // chain is 22 yards
    store.insert(
        LengthUnit::Chain,
        store
            .get(&LengthUnit::Yard)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(22.0, PRECISION)),
    );
    // rod is 5 / 12 yards
    store.insert(
        LengthUnit::Rod,
        store
            .get(&LengthUnit::Yard)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(5.5, PRECISION)),
    );
    // earth radius is 6371 km
    store.insert(
        LengthUnit::EarthRadius,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Kilo))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(6371.0, PRECISION)),
    );
    // lunar distance is 384402 km
    store.insert(
        LengthUnit::LunarDistance,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Kilo))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(384402.0, PRECISION)),
    );
    // astronomical unit is 149597870700 m
    store.insert(
        LengthUnit::AstronomicalUnit,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(149597870700.0, PRECISION)),
    );
    // lightyear is 9460730472580.8 km
    store.insert(
        LengthUnit::LightYear,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Kilo))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(9460730472580.8, PRECISION)),
    );
    // parsec is 30856775814671.9 km
    store.insert(
        LengthUnit::Parsec,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Kilo))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(30856775814671.9, PRECISION)),
    );
    // hubble length is 14400000000 light years
    store.insert(
        LengthUnit::HubbleLength,
        store
            .get(&LengthUnit::LightYear)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(14400000000.0, PRECISION)),
    );
    // planck length is 0.000000000000000000000000000000000016163 meters
    store.insert(
        LengthUnit::PlanckLength,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(
                0.000000000000000000000000000000000016163,
                PRECISION,
            )),
    );
    // cana is 1.57 meters
    store.insert(
        LengthUnit::Cana,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(1.57, PRECISION)),
    );
    // cubit is 52.92 cm
    store.insert(
        LengthUnit::Cubit,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Centi))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(52.92, PRECISION)),
    );
    // rope is 6.096 meters
    store.insert(
        LengthUnit::Rope,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(6.096, PRECISION)),
    );
    // li is 500 meters
    store.insert(
        LengthUnit::Li,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(500.0, PRECISION)),
    );
    // pace is 0.75 meters
    store.insert(
        LengthUnit::Pace,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(0.75, PRECISION)),
    );
    // verst is 1.0668 km
    store.insert(
        LengthUnit::Verst,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Kilo))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(1.0668, PRECISION)),
    );
    // double decker bus is 10 meters
    store.insert(
        LengthUnit::DoubleDeckerBus,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(10.0, PRECISION)),
    );
    // football field is 100 yards
    store.insert(
        LengthUnit::FootballField,
        store
            .get(&LengthUnit::Yard)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(100.0, PRECISION)),
    );
    // human hair thickness is 0.08 mm
    store.insert(
        LengthUnit::HumanHairThickness,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Milli))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(0.08, PRECISION)),
    );
    // furlong is 1/8 mile
    store.insert(
        LengthUnit::Furlong,
        store
            .get(&LengthUnit::Mile)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(1.0 / 8.0, PRECISION)),
    );
    // horse is 2.4 meters
    store.insert(
        LengthUnit::HorseLength,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(2.4, PRECISION)),
    );
    // horizontal pitch is 5.08 mm
    store.insert(
        LengthUnit::HorizontalPitch,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Milli))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(5.08, PRECISION)),
    );
    // hammer unit is 19.05 mm
    store.insert(
        LengthUnit::HammerUnit,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Milli))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(19.05, PRECISION)),
    );
    // rack unit is 1.75 inches
    store.insert(
        LengthUnit::RackUnit,
        store
            .get(&LengthUnit::Inch)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(1.75, PRECISION)),
    );
    // hand is 4 inches
    store.insert(
        LengthUnit::Hand,
        store
            .get(&LengthUnit::Inch)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(4.0, PRECISION)),
    );
    // light nanosecond is 29.9792458 cm
    store.insert(
        LengthUnit::LightNanosecond,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Centi))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(29.9792458, PRECISION)),
    );
    // metric foot is 300 mm
    store.insert(
        LengthUnit::MetricFoot,
        store
            .get(&LengthUnit::Meter(MetricPrefix::Milli))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(300.0, PRECISION)),
    );
    // boat length is 19 meters
    store.insert(
        LengthUnit::BoatLength,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(19.0, PRECISION)),
    );
    // block is 150 meters
    store.insert(
        LengthUnit::Block,
        store
            .get(&LengthUnit::Meter(MetricPrefix::None))
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(150.0, PRECISION)),
    );
    // siriometer is 15.8 light years
    store.insert(
        LengthUnit::Siriometer,
        store
            .get(&LengthUnit::LightYear)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(15.8, PRECISION)),
    );
    // banana is 8 inches
    store.insert(
        LengthUnit::Banana,
        store
            .get(&LengthUnit::Inch)
            .unwrap()
            .mul_full_prec(&BigFloat::from_f64(8.0, PRECISION)),
    );

    store
});

// length unit -> Vec<(name, plural)>
pub static LENGTH_NAMES: Lazy<BTreeMap<LengthUnit, Vec<(String, String)>>> = Lazy::new(|| {
    let mut store = BTreeMap::new();

    store.insert(
        LengthUnit::Meter(MetricPrefix::Pico),
        vec![("picometer", "picometers")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Nano),
        vec![("nanometer", "nanometers")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Micro),
        vec![("micrometer", "micrometers")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Milli),
        vec![("millimeter", "millimeters"), ("mm", "mm")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Centi),
        vec![("centimeter", "centimeters"), ("cm", "cm")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Deci),
        vec![("decimeter", "decimeters")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::None),
        vec![
            ("meter", "meters"),
            ("m", "m"),
            ("minecraft block", "minecraft blocks"),
        ],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Deca),
        vec![("decameter", "decameters")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Hecto),
        vec![("hectometer", "hectometers")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Kilo),
        vec![("kilometer", "kilometers"), ("km", "km")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Mega),
        vec![("megameter", "megameters")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Giga),
        vec![("gigameter", "gigameters")],
    );
    store.insert(
        LengthUnit::Meter(MetricPrefix::Tera),
        vec![("terameter", "terameters")],
    );
    // inch is 25.4 m
    store.insert(
        LengthUnit::Inch,
        vec![("inch", "inches"), ("in", "in"), ("\"", "\"")],
    );
    // thou is 1/1000 inches
    store.insert(LengthUnit::Thou, vec![("thou", "thous")]);
    // foot is 12 inches
    store.insert(
        LengthUnit::Foot,
        vec![("foot", "feet"), ("ft", "ft"), ("'", "'ft'")],
    );
    // yard is 3 feet
    store.insert(LengthUnit::Yard, vec![("yard", "yards"), ("yd", "yd")]);
    // mile is 5280 feet
    store.insert(LengthUnit::Mile, vec![("mile", "miles")]);
    // league is 3 miles
    store.insert(LengthUnit::League, vec![("league", "leagues")]);
    // fathom is 2 yards
    store.insert(LengthUnit::Fathom, vec![("fathom", "fathoms")]);
    // nautical mile is 1852 m
    store.insert(
        LengthUnit::NauticalMile,
        vec![("nautical mile", "nautical miles")],
    );
    // chain is 22 yards
    store.insert(LengthUnit::Chain, vec![("chain", "chains")]);
    // rod is 5 / 12 yards
    store.insert(LengthUnit::Rod, vec![("rod", "rods")]);
    // earth radius is 6371 km
    store.insert(
        LengthUnit::EarthRadius,
        vec![("earth radius", "earth radii")],
    );
    // lunar distance is 384402 km
    store.insert(
        LengthUnit::LunarDistance,
        vec![("lunar distance", "lunar distances")],
    );
    // astronomical unit is 149597870700 m
    store.insert(
        LengthUnit::AstronomicalUnit,
        vec![("astronomical unit", "astronomical units")],
    );
    // lightyear is 9460730472580.8 km
    store.insert(LengthUnit::LightYear, vec![("lightyear", "lightyears")]);
    // parsec is 30856775814671.9 km
    store.insert(LengthUnit::Parsec, vec![("parsec", "parsecs")]);
    // hubble length is 14400000000 light years
    store.insert(
        LengthUnit::HubbleLength,
        vec![("hubble length", "hubble lengths")],
    );
    // planck length is 0.000000000000000000000000000000000016163 meters
    store.insert(
        LengthUnit::PlanckLength,
        vec![("planck length", "planck lengths")],
    );
    // cana is 1.57 meters
    store.insert(LengthUnit::Cana, vec![("cana", "canas")]);
    // cubit is 52.92 cm
    store.insert(LengthUnit::Cubit, vec![("cubit", "cubits")]);
    // rope is 6.096 meters
    store.insert(LengthUnit::Rope, vec![("rope", "ropes")]);
    // li is 500 meters
    store.insert(LengthUnit::Li, vec![("li", "lis")]);
    // pace is 0.75 meters
    store.insert(LengthUnit::Pace, vec![("pace", "paces")]);
    // verst is 1.0668 km
    store.insert(LengthUnit::Verst, vec![("verst", "versts")]);
    // double decker bus is 10 meters
    store.insert(
        LengthUnit::DoubleDeckerBus,
        vec![("double decker bus", "double decker busses")],
    );
    // football field is 100 yards
    store.insert(
        LengthUnit::FootballField,
        vec![("football field", "football fields")],
    );
    // human hair thickness is 0.08 mm
    store.insert(
        LengthUnit::HumanHairThickness,
        vec![("human hair thickness", "human hair thicknesses")],
    );
    // furlong is 1/8 mile
    store.insert(LengthUnit::Furlong, vec![("furlong", "furlongs")]);
    // horse is 2.4 meters
    store.insert(
        LengthUnit::HorseLength,
        vec![("horse", "horses"), ("horse length", "horse lengths")],
    );
    // horizontal pitch is 5.08 mm
    store.insert(
        LengthUnit::HorizontalPitch,
        vec![("horizontal pitch", "horizontal pitches")],
    );
    // hammer unit is 19.05 mm
    store.insert(
        LengthUnit::HammerUnit,
        vec![("hammer unit", "hammer units")],
    );
    // rack unit is 1.75 inches
    store.insert(LengthUnit::RackUnit, vec![("rack unit", "rack units")]);
    // hand is 4 inches
    store.insert(LengthUnit::Hand, vec![("hand", "hands")]);
    // light nanosecond is 29.9792458 cm
    store.insert(
        LengthUnit::LightNanosecond,
        vec![("light nanosecond", "light nanoseconds")],
    );
    // metric foot is 300 mm
    store.insert(LengthUnit::MetricFoot, vec![("metric foot", "metric feet")]);
    // boat length is 19 meters
    store.insert(
        LengthUnit::BoatLength,
        vec![("boat length", "boat lengths")],
    );
    // block is 150 meters
    store.insert(LengthUnit::Block, vec![("block", "blocks")]);
    // siriometer is 15.8 light years
    store.insert(LengthUnit::Siriometer, vec![("siriometer", "siriometers")]);
    // banana is 8 inches
    store.insert(LengthUnit::Banana, vec![("banana", "bananas")]);

    let mut store_string = BTreeMap::new();
    for (k, v) in store {
        store_string.insert(
            k,
            v.into_iter()
                .map(|(s1, s2)| (s1.to_string(), s2.to_string()))
                .collect(),
        );
    }
    store_string
});

#[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Debug)]
pub enum LengthUnit {
    Meter(MetricPrefix),
    // 1/1000 inch
    Thou,

    // 25.4 mm
    Inch,

    // 12 inches
    Foot,

    // 3 feet
    Yard,

    // 5280 feet
    Mile,

    // 3 miles
    League,

    // 2 yards
    Fathom,

    // 1852 m
    NauticalMile,

    // 22 yards
    Chain,

    // 5 1/2 yards
    Rod,

    // 6371 km
    EarthRadius,

    // 384402 km
    LunarDistance,

    // 149597870700 m
    AstronomicalUnit,

    // 9460730472580.8 km
    LightYear,

    // 30856775814671.9 km
    Parsec,

    // 14400000000 light years
    HubbleLength,

    // 0.000000000000000000000000000000000016163 meters
    PlanckLength,

    // 1.57 meters
    Cana,

    // 52.92 cm
    Cubit,

    // 6.096 meters
    Rope,

    // 500 meters
    Li,

    // 0.75 meters
    Pace,

    // 1.0668 kilometers
    Verst,

    // 10 meters
    DoubleDeckerBus,

    // 100 yards
    FootballField,

    // 0.08 mm
    HumanHairThickness,

    // 1/8 mile
    Furlong,

    // 2.4 meters
    HorseLength,

    // 5.08 mm
    HorizontalPitch,

    // 19.05 mm
    HammerUnit,

    // 1.75 inches
    RackUnit,

    // 4 inches
    Hand,

    // 29.9792458 cm
    LightNanosecond,

    // 300 mm
    MetricFoot,

    // 19 meters
    BoatLength,

    // 150 meters
    Block,

    // 15.8 light years
    Siriometer,

    // 8 inches
    Banana,
}

impl ConvertTo<LengthUnit> for LengthUnit {
    // convert to meters:
    // 1. find the desired type in the length store
    // 2. multiply value by the type's meter value
    fn convert_to(self, value: BigFloat, into: LengthUnit) -> BigFloat {
        match self {
            LengthUnit::Meter(prefix) => {
                // unprefix
                let denom = LENGTH_STORE.get(&LengthUnit::Meter(prefix)).unwrap();
                let store_value = LENGTH_STORE.get(&into).unwrap();
                value
                    .mul_full_prec(denom)
                    .div(store_value, PRECISION, RoundingMode::None)
            }
            _ => {
                match into {
                    LengthUnit::Meter(prefix) => {
                        // unprefix
                        let denom = LENGTH_STORE.get(&LengthUnit::Meter(prefix)).unwrap();
                        let store_value = LENGTH_STORE.get(&self).unwrap();
                        value
                            .mul_full_prec(store_value)
                            .div(denom, PRECISION, RoundingMode::None)
                    }
                    _ => {
                        // convert to meters, then to desired value
                        let meters = self.convert_to(value, LengthUnit::Meter(MetricPrefix::None));
                        LengthUnit::Meter(MetricPrefix::None).convert_to(meters, into)
                    }
                }
            }
        }
    }
}

impl BestUnit for LengthUnit {
    fn best_unit(self, value: BigFloat) -> Self {
        let mut lowest_remainder = BigFloat::from_f64(f64::MAX, PRECISION);
        let mut lowest_value = BigFloat::from_f64(f64::MAX, PRECISION);
        let mut best_unit = LengthUnit::Meter(MetricPrefix::None);

        for unit in LENGTH_STORE.keys() {
            if *unit == self {
                continue;
            }
            match self {
                LengthUnit::Meter(prefix) => {
                    // convert to meters, then to desired value
                    let meters = self
                        .convert_to(value.clone(), LengthUnit::Meter(MetricPrefix::None));
                    let store_value = LENGTH_STORE.get(unit).unwrap();
                    let rem = meters.rem(store_value);
                    let div = meters.div(store_value, PRECISION, RoundingMode::None);
                    if rem.le(&lowest_remainder) && div.le(&lowest_value) && div.ge(&BigFloat::from_f64(1.0, PRECISION)) {
                        lowest_remainder = rem;
                        lowest_value = div;
                        best_unit = *unit;
                    }
                }
                _ => {
                    match unit {
                        LengthUnit::Meter(prefix) => {
                            // unprefix
                            let denom = LENGTH_STORE.get(&LengthUnit::Meter(*prefix)).unwrap();
                            let store_value = LENGTH_STORE.get(&self).unwrap();
                            let rem = value.mul_full_prec(store_value).rem(denom);
                            let div = value.mul_full_prec(store_value).div(
                                denom,
                                PRECISION,
                                RoundingMode::None,
                            );
                            if rem.le(&lowest_remainder) && div.le(&lowest_value) && div.ge(&BigFloat::from_f64(1.0, PRECISION)) {
                                lowest_remainder = rem;
                                lowest_value = div;
                                best_unit = *unit;
                            }
                        }
                        _ => {
                            // convert to meters, then to desired value
                            let meters = self
                                .convert_to(value.clone(), LengthUnit::Meter(MetricPrefix::None));
                            let store_value = LENGTH_STORE.get(unit).unwrap();
                            let rem = meters.rem(store_value);
                            let div = meters.div(store_value, PRECISION, RoundingMode::None);
                            if rem.le(&lowest_remainder) && div.le(&lowest_value) && div.ge(&BigFloat::from_f64(1.0, PRECISION)) {
                                lowest_remainder = rem;
                                lowest_value = div;
                                best_unit = *unit;
                            }
                        }
                    }
                }
            }
        }

        best_unit
    }
}

impl FromUnitName<LengthUnit> for LengthUnit {
    fn parse(value: String) -> Option<(LengthUnit, String)> {
        let mut sorted_checklist: Vec<(String, String, LengthUnit)> = vec![];

        for (unit, names) in LENGTH_NAMES.iter() {
            for (single, plural) in names {
                sorted_checklist.push((single.clone(), plural.clone(), *unit));
            }
        }

        sorted_checklist.sort_by(|b, a| a.1.len().cmp(&b.1.len()));
        let input = value.trim_start();
        let diff = value.len() - input.len();

        for (name, plural, unit) in sorted_checklist {
            if input.to_lowercase().starts_with(&name) || input.to_lowercase().starts_with(&plural)
            {
                let len = if plural.len() > name.len() {
                    if input.to_lowercase().starts_with(&plural) {
                        plural.len()
                    } else {
                        name.len()
                    }
                } else if input.to_lowercase().starts_with(&name) {
                    name.len()
                } else {
                    plural.len()
                };
                return Some((unit, value[diff + len..].to_string()));
            }
        }

        None
    }
}

impl UnitName for LengthUnit {
    fn unit_name(self, value: BigFloat) -> String {
        if value == BigFloat::from_f64(1.0, PRECISION) {
            // singular
            LENGTH_NAMES.get(&self).unwrap().first().unwrap().0.clone()
        } else {
            // plural
            LENGTH_NAMES.get(&self).unwrap().first().unwrap().1.clone()
        }
    }
}
