use decimal::d128;
use crate::Number;

#[derive(Clone, Copy, PartialEq, Debug)]
/// An enum of all possible unit types, like [`Length`], [`DigitalStorage`] etc.
/// There is also a [`NoType`] unit type for normal numbers.
pub enum UnitType {
  /// A normal number, for example `5`
  NoType,
  /// A unit of time, for example [`Hour`]
  Time,
  /// A unit of length, for example [`Mile`]
  Length,
  /// A unit of area, for example [`SquareKilometer`]
  Area,
  /// A unit of volume, for example [`Liter`] or [`Tablespoon`]
  Volume,
  /// A unit of mass, for example [`Gram`]
  Mass,
  /// A unit of digital storage, for example [`Kilobyte`]
  DigitalStorage,
  /// A unit of energy, for example [`Joule`] or [`KilowattHour`]
  Energy,
  /// A unit of power, for example [`Watt`]
  Power,
  /// A unit of electrical current, for example [`Ampere`]
  ElectricCurrent,
  /// A unit of electric resistance, for example [`Ohm`]
  Resistance,
  /// A unit of voltage, for example [`Volt`]
  Voltage,
  /// A unit of pressure, for example [`Bar`]
  Pressure,
  /// A unit of frequency, for example [`Hertz`]
  Frequency,
  /// A unit of x, for example [`KilometersPerHour`]
  Speed,
  /// A unit of temperature, for example [`Kelvin`]
  Temperature,
}
use UnitType::*;

// Macro for creating units. Not possible to extend/change the default units
// with this because the default units are imported into the lexer, parser
// and evaluator
macro_rules! create_units {
  ( $( $variant:ident : $properties:expr ),*, ) => {
    #[derive(Clone, Copy, PartialEq, Debug)]
    /// A Unit enum. Note that it can also be [`NoUnit`].
    pub enum Unit {
      $($variant),*
    }
    use Unit::*;

    impl Unit {
      pub fn category(&self) -> UnitType {
        match self {
          $(
            Unit::$variant => $properties.0
          ),*
        }
      }
      pub fn weight(&self) -> d128 {
        match self {
          $(
            Unit::$variant => $properties.1
          ),*
        }
      }
    }
  }
}

create_units!(
  NoUnit:             (NoType, d128!(1)),

  Nanosecond:         (Time, d128!(1)),
  Microsecond:        (Time, d128!(1000)),
  Millisecond:        (Time, d128!(1000000)),
  Second:             (Time, d128!(1000000000)),
  Minute:             (Time, d128!(60000000000)),
  Hour:               (Time, d128!(3600000000000)),
  Day:                (Time, d128!(86400000000000)),
  Week:               (Time, d128!(604800000000000)),
  Month:              (Time, d128!(2629746000000000)),
  Quarter:            (Time, d128!(7889238000000000)),
  Year:               (Time, d128!(31556952000000000)),
  Decade:             (Time, d128!(315569520000000000)),
  Century:            (Time, d128!(3155695200000000000)),
  Millenium:          (Time, d128!(31556952000000000000)),

  Millimeter:         (Length, d128!(1)),
  Centimeter:         (Length, d128!(10)),
  Decimeter:          (Length, d128!(100)),
  Meter:              (Length, d128!(1000)),
  Kilometer:          (Length, d128!(1000000)),
  Inch:               (Length, d128!(25.4)),
  Foot:               (Length, d128!(304.8)),
  Yard:               (Length, d128!(914.4)),
  Mile:               (Length, d128!(1609344)),
  // 1-dimensional only:
  NauticalMile:       (Length, d128!(1852000)),
  LightYear:          (Length, d128!(9460730472580800000)),
  LightSecond:        (Length, d128!(299792458000)),

  SquareMillimeter:   (Area, d128!(1)),
  SquareCentimeter:   (Area, d128!(100)),
  SquareDecimeter:    (Area, d128!(10000)),
  SquareMeter:        (Area, d128!(1000000)),
  SquareKilometer:    (Area, d128!(1000000000000)),
  SquareInch:         (Area, d128!(645.16)),
  SquareFoot:         (Area, d128!(92903.04)),
  SquareYard:         (Area, d128!(836127.36)),
  SquareMile:         (Area, d128!(2589988110336.00)),
  // 2-dimensional only
  Are:                (Area, d128!(100000000)),
  Decare:             (Area, d128!(1000000000)),
  Hectare:            (Area, d128!(10000000000)),
  Acre:               (Area, d128!(4046856422.40)),

  CubicMillimeter:    (Volume, d128!(1)),
  CubicCentimeter:    (Volume, d128!(1000)),
  CubicDecimeter:     (Volume, d128!(1000000)),
  CubicMeter:         (Volume, d128!(1000000000)),
  CubicKilometer:     (Volume, d128!(1000000000000000000)),
  CubicInch:          (Volume, d128!(16387.064)),
  CubicFoot:          (Volume, d128!(28316846.592)),
  CubicYard:          (Volume, d128!(764554857.984)),
  CubicMile:          (Volume, d128!(4168181825440579584)),
  // 3-dimensional only
  Milliliter:         (Volume, d128!(1000)),
  Centiliter:         (Volume, d128!(10000)),
  Deciliter:          (Volume, d128!(100000)),
  Liter:              (Volume, d128!(1000000)),
  Teaspoon:           (Volume, d128!(4928.92159375)),
  Tablespoon:         (Volume, d128!(14786.76478125)),
  FluidOunce:         (Volume, d128!(29573.5295625)),
  Cup:                (Volume, d128!(236588.2365)),
  Pint:               (Volume, d128!(473176.473)),
  Quart:              (Volume, d128!(946352.946)),
  Gallon:             (Volume, d128!(3785411.784)),
  OilBarrel:          (Volume, d128!(158987294.928)),

  Milligram:          (Mass, d128!(0.001)),
  Gram:               (Mass, d128!(1)),
  Hectogram:          (Mass, d128!(100)),
  Kilogram:           (Mass, d128!(1000)),
  MetricTon:          (Mass, d128!(1000000)),
  Ounce:              (Mass, d128!(28.349523125)),
  Pound:              (Mass, d128!(453.59237)),
  Stone:              (Mass, d128!(6350.29318)),
  ShortTon:           (Mass, d128!(907184.74)),
  LongTon:            (Mass, d128!(1016046.9088)),

  Bit:                (DigitalStorage, d128!(1)),
  Kilobit:            (DigitalStorage, d128!(1000)),
  Megabit:            (DigitalStorage, d128!(1000000)),
  Gigabit:            (DigitalStorage, d128!(1000000000)),
  Terabit:            (DigitalStorage, d128!(1000000000000)),
  Petabit:            (DigitalStorage, d128!(1000000000000000)),
  Exabit:             (DigitalStorage, d128!(1000000000000000000)),
  Zettabit:           (DigitalStorage, d128!(1000000000000000000000)),
  Yottabit:           (DigitalStorage, d128!(1000000000000000000000000)),
  Kibibit:            (DigitalStorage, d128!(1024)),
  Mebibit:            (DigitalStorage, d128!(1048576)),
  Gibibit:            (DigitalStorage, d128!(1073741824)),
  Tebibit:            (DigitalStorage, d128!(1099511627776)),
  Pebibit:            (DigitalStorage, d128!(1125899906842624)),
  Exbibit:            (DigitalStorage, d128!(1152921504606846976)),
  Zebibit:            (DigitalStorage, d128!(1180591620717411303424)),
  Yobibit:            (DigitalStorage, d128!(1208925819614629174706176)),
  Byte:               (DigitalStorage, d128!(8)),
  Kilobyte:           (DigitalStorage, d128!(8000)),
  Megabyte:           (DigitalStorage, d128!(8000000)),
  Gigabyte:           (DigitalStorage, d128!(8000000000)),
  Terabyte:           (DigitalStorage, d128!(8000000000000)),
  Petabyte:           (DigitalStorage, d128!(8000000000000000)),
  Exabyte:            (DigitalStorage, d128!(8000000000000000000)),
  Zettabyte:          (DigitalStorage, d128!(8000000000000000000000)),
  Yottabyte:          (DigitalStorage, d128!(8000000000000000000000000)),
  Kibibyte:           (DigitalStorage, d128!(8192)),
  Mebibyte:           (DigitalStorage, d128!(8388608)),
  Gibibyte:           (DigitalStorage, d128!(8589934592)),
  Tebibyte:           (DigitalStorage, d128!(8796093022208)),
  Pebibyte:           (DigitalStorage, d128!(9007199254740992)),
  Exbibyte:           (DigitalStorage, d128!(9223372036854775808)),
  Zebibyte:           (DigitalStorage, d128!(9444732965739290427392)),
  Yobibyte:           (DigitalStorage, d128!(9671406556917033397649408)),

  // ! If updating Millijoule, also update get_inverted_millijoule_weight()
  Millijoule:         (Energy, d128!(0.001)),
  Joule:              (Energy, d128!(1)),
  NewtonMeter:        (Energy, d128!(1)),
  Kilojoule:          (Energy, d128!(1000)),
  Megajoule:          (Energy, d128!(1000000)),
  Gigajoule:          (Energy, d128!(1000000000)),
  Terajoule:          (Energy, d128!(1000000000000)),
  Calorie:            (Energy, d128!(4.1868)),
  KiloCalorie:        (Energy, d128!(4186.8)),
  BritishThermalUnit: (Energy, d128!(1055.05585262)),
  WattHour:           (Energy, d128!(3600)),
  KilowattHour:       (Energy, d128!(3600000)),
  MegawattHour:       (Energy, d128!(3600000000)),
  GigawattHour:       (Energy, d128!(3600000000000)),
  TerawattHour:       (Energy, d128!(3600000000000000)),
  PetawattHour:       (Energy, d128!(3600000000000000000)),

  // ! If updating Milliwatt, also update get_inverted_milliwatt_weight()
  Milliwatt:                    (Power, d128!(0.001)),
  Watt:                         (Power, d128!(1)),
  Kilowatt:                     (Power, d128!(1000)),
  Megawatt:                     (Power, d128!(1000000)),
  Gigawatt:                     (Power, d128!(1000000000)),
  Terawatt:                     (Power, d128!(1000000000000)),
  Petawatt:                     (Power, d128!(1000000000000000)),
  BritishThermalUnitsPerMinute: (Power, d128!(0.0568690272188)), // probably inexact
  BritishThermalUnitsPerHour:   (Power, d128!(3.412141633128)), // probably inexact
  Horsepower:                   (Power, d128!(745.69987158227022)), // exact according to wikipedia
  MetricHorsepower:             (Power, d128!(735.49875)),

  // ! If updating Milliampere, also update get_inverted_milliampere_weight()
  Milliampere:                  (ElectricCurrent, d128!(0.001)),
  Ampere:                       (ElectricCurrent, d128!(1)),
  Kiloampere:                   (ElectricCurrent, d128!(1000)),
  Abampere:                     (ElectricCurrent, d128!(10)),

  // ! If updating Milliohm, also update get_inverted_milliohm_weight()
  Milliohm:                     (Resistance, d128!(0.001)),
  Ohm:                          (Resistance, d128!(1)),
  Kiloohm:                      (Resistance, d128!(1000)),

  // ! If updating Millivolt, also update get_inverted_millivolt_weight()
  Millivolt:                    (Voltage, d128!(0.001)),
  Volt:                         (Voltage, d128!(1)),
  Kilovolt:                     (Voltage, d128!(1000)),

  Pascal:                       (Pressure, d128!(1)),
  Kilopascal:                   (Pressure, d128!(1000)),
  Atmosphere:                   (Pressure, d128!(101325)),
  Millibar:                     (Pressure, d128!(100)),
  Bar:                          (Pressure, d128!(100000)),
  InchOfMercury:                (Pressure, d128!(3386.389)),
  PoundsPerSquareInch:          (Pressure, d128!(6894.757293168361)), // inexact
  Torr:                         (Pressure, d128!(162.12)),

  Hertz:                        (Frequency, d128!(1)),
  Kilohertz:                    (Frequency, d128!(1000)),
  Megahertz:                    (Frequency, d128!(1000000)),
  Gigahertz:                    (Frequency, d128!(1000000000)),
  Terahertz:                    (Frequency, d128!(1000000000000)),
  Petahertz:                    (Frequency, d128!(1000000000000000)),
  RevolutionsPerMinute:         (Frequency, d128!(60)),

  KilometersPerHour:  (Speed, d128!(1)),
  MetersPerSecond:    (Speed, d128!(3.6)),
  MilesPerHour:       (Speed, d128!(1.609344)),
  FeetPerSecond:      (Speed, d128!(1.09728)),
  Knot:               (Speed, d128!(1.852)),

  Kelvin:             (Temperature, d128!(0)),
  Celsius:            (Temperature, d128!(0)),
  Fahrenheit:         (Temperature, d128!(0)),
);

// These functions are here to avoid dividing by small numbers like 0.01,
// because d128 gives numbers in E notation in that case.
fn get_inverted_millijoule_weight() -> d128 {
  return d128!(1000);
}
fn get_inverted_milliwatt_weight() -> d128 {
  return d128!(1000);
}
fn get_inverted_milliohm_weight() -> d128 {
  return d128!(1000);
}
fn get_inverted_milliampere_weight() -> d128 {
  return d128!(1000);
}
fn get_inverted_millivolt_weight() -> d128 {
  return d128!(1000);
}

/// Returns the conversion factor between two units.
/// 
/// The conversion factor is what you need to multiply `unit` with to get
/// `to_unit`. For example, the conversion factor from 1 minute to 1 second
/// is 60.
/// 
/// This is not sufficient for [`Temperature`] units.
pub fn get_conversion_factor(unit: Unit, to_unit: Unit) -> d128 {
  return unit.weight() / to_unit.weight();
}

/// Convert a [`Number`] to a specified [`Unit`].
pub fn convert(number: Number, to_unit: Unit) -> Result<Number, String> {
  if number.unit.category() != to_unit.category() {
    return Err(format!("Cannot convert from {:?} to {:?}", number.unit, to_unit));
  }
  let value = number.value;
  let ok = |new_value| {
    Ok(Number::new(new_value, to_unit))
  };
  if number.unit.category() == UnitType::Temperature {
    match (number.unit, to_unit) {
      (Kelvin, Kelvin)         => ok(value),
      (Kelvin, Celsius)        => ok(value-d128!(273.15)),
      (Kelvin, Fahrenheit)     => ok(value*d128!(1.8)-d128!(459.67)),
      (Celsius, Celsius)       => ok(value),
      (Celsius, Kelvin)        => ok(value+d128!(273.15)),
      (Celsius, Fahrenheit)    => ok(value*d128!(1.8)+d128!(32)),
      (Fahrenheit, Fahrenheit) => ok(value),
      (Fahrenheit, Kelvin)     => ok((value+d128!(459.67))*d128!(5)/d128!(9)),
      (Fahrenheit, Celsius)    => ok((value-d128!(32))/d128!(1.8)),
      _ => Err(format!("Error converting temperature {:?} to {:?}", number.unit, to_unit)),
    }
  } else {
    let conversion_factor = get_conversion_factor(number.unit, to_unit);
    ok(number.value * conversion_factor)
  }
}

/// If one of two provided [`Number`]s has a larger [`Unit`] than the other, convert
/// the large one to the unit of the small one.
pub fn convert_to_lowest(left: Number, right: Number) -> Result<(Number, Number), String> {
  if left.unit.weight() == right.unit.weight() {
    Ok((left, right))
  } else if left.unit.weight() > right.unit.weight() {
    let left_converted = convert(left, right.unit)?;
    Ok((left_converted, right))
  } else {
    let right_converted = convert(right, left.unit)?;
    Ok((left, right_converted))
  }
}

/// Return the sum of two [`Number`]s
pub fn add(left: Number, right: Number) -> Result<Number, String> {
  if left.unit == right.unit {
    Ok(Number::new(left.value + right.value, left.unit))
  } else if left.unit.category() == right.unit.category() && left.unit.category() != Temperature {
    let (left, right) = convert_to_lowest(left, right)?;
    Ok(Number::new(left.value + right.value, left.unit))
  } else {
    return Err(format!("Cannot add {:?} and {:?}", left.unit, right.unit))
  }
}

/// Subtract a [`Number`] from another [`Number`]
pub fn subtract(left: Number, right: Number) -> Result<Number, String> {
  if left.unit == right.unit {
    Ok(Number::new(left.value - right.value, left.unit))
  } else if left.unit.category() == right.unit.category() && left.unit.category() != Temperature {
    let (left, right) = convert_to_lowest(left, right)?;
    Ok(Number::new(left.value - right.value, left.unit))
  } else {
    return Err(format!("Cannot subtract {:?} by {:?}", left.unit, right.unit))
  }
}

/// Convert a [`Number`] to an ideal unit.
/// 
/// If you have 1,000,000 millimeters, this will return 1 kilometer.
/// 
/// This only affects units of `Length`, `Area`, `Volume`, `Energy`, `Power`,
/// `ElectricCurrent`, `Resistance` and `Voltage`. Other units are passed through.
pub fn to_ideal_unit(number: Number) -> Number {
  let value = number.value * number.unit.weight();
  if number.unit.category() == Length {
    if value >= d128!(1000000000000000000) { // ≈ 0.1 light years
      return Number::new(value/Kilometer.weight(), Kilometer)
    } else if value >= d128!(1000000) { // 1 km
      return Number::new(value/Kilometer.weight(), Kilometer)
    } else if value >= d128!(1000) { // 1 m
      return Number::new(value/Meter.weight(), Meter)
    } else if value >= d128!(10) { // 1 cm
      return Number::new(value/Centimeter.weight(), Centimeter)
    } else {
      return Number::new(value, Millimeter)
    }
  } else if number.unit.category() == Area {
    if value >= d128!(1000000000000) { // 1 km2
      return Number::new(value/SquareKilometer.weight(), SquareKilometer)
    } else if value >= d128!(10000000000) { // 1 hectare
      return Number::new(value/Hectare.weight(), Hectare)
    } else if value >= d128!(1000000) { // 1 m2
      return Number::new(value/SquareMeter.weight(), SquareMeter)
    } else if value >= d128!(100) { // 1 cm2
      return Number::new(value/SquareCentimeter.weight(), SquareCentimeter)
    } else {
      return Number::new(value, SquareMillimeter)
    }
  } else if number.unit.category() == Volume {
    if value >= d128!(1000000000000000000) { // 1 km3
      return Number::new(value/CubicKilometer.weight(), CubicKilometer)
    } else if value >= d128!(1000000000) { // 1 m3
      return Number::new(value/CubicMeter.weight(), CubicMeter)
    } else if value >= d128!(1000000) { // 1 l
      return Number::new(value/Liter.weight(), Liter)
    } else if value >= d128!(1000) { // 1 ml
      return Number::new(value/Milliliter.weight(), Milliliter)
    } else {
      return Number::new(value, CubicMillimeter)
    }
  } else if number.unit.category() == Energy {
    if value >= d128!(3600000000000000000) { // 1 petawatthour
      return Number::new(value/PetawattHour.weight(), PetawattHour)
    } else if value >= d128!(3600000000000000) { // 1 terawatthour
      return Number::new(value/TerawattHour.weight(), TerawattHour)
    } else if value >= d128!(3600000000000) { // 1 gigawatthour
      return Number::new(value/GigawattHour.weight(), GigawattHour)
    } else if value >= d128!(3600000000) { // 1 megawatthour
      return Number::new(value/MegawattHour.weight(), MegawattHour)
    } else if value >= d128!(3600000) { // 1 kilowatthour
      return Number::new(value/KilowattHour.weight(), KilowattHour)
    } else if value >= d128!(3600) { // 1 watthour
      return Number::new(value/WattHour.weight(), WattHour)
    } else if value >= d128!(1) { // 1 joule
      return Number::new(value, Joule)
    } else {
      return Number::new(value * get_inverted_millijoule_weight(), Millijoule)
    }
  } else if number.unit.category() == Power {
    if value >= d128!(1000000000000000) { // 1 petawatt
      return Number::new(value/Petawatt.weight(), Petawatt)
    } else if value >= d128!(1000000000000) { // 1 terawatt
      return Number::new(value/Terawatt.weight(), Terawatt)
    } else if value >= d128!(1000000000) { // 1 gigawatt
      return Number::new(value/Gigawatt.weight(), Gigawatt)
    } else if value >= d128!(1000000) { // megawatt
      return Number::new(value/Megawatt.weight(), Megawatt)
    } else if value >= d128!(1000) { // 1 kilowatt
      return Number::new(value/Kilowatt.weight(), Kilowatt)
    } else if value >= d128!(1) { // 1 watt
      return Number::new(value, Watt)
    } else {
      return Number::new(value * get_inverted_milliwatt_weight(), Milliwatt)
    }
  } else if number.unit.category() == ElectricCurrent {
    if value >= d128!(1000) { // 1 kiloampere
      return Number::new(value/Kiloampere.weight(), Kiloampere)
    } else if value >= d128!(1) { // 1 ampere
      return Number::new(value, Ampere)
    } else {
      return Number::new(value * get_inverted_milliampere_weight(), Milliampere)
    }
  } else if number.unit.category() == Resistance {
    if value >= d128!(1000) { // 1 kiloohm
      return Number::new(value/Kiloohm.weight(), Kiloohm)
    } else if value >= d128!(1) { // 1 ohm
      return Number::new(value, Ohm)
    } else {
      return Number::new(value * get_inverted_milliohm_weight(), Milliohm)
    }
  } else if number.unit.category() == Voltage {
    if value >= d128!(1000) { // 1 kilovolt
      return Number::new(value/Kilovolt.weight(), Kilovolt)
    } else if value >= d128!(1) { // 1 volt
      return Number::new(value, Volt)
    } else {
      return Number::new(value * get_inverted_millivolt_weight(), Millivolt)
    }
  }
  number
}

/// Convert a [`Number`] to an ideal [`Joule`] unit, if the number is a unit of [`Energy`].
pub fn to_ideal_joule_unit(number: Number) -> Number {
  let value = number.value * number.unit.weight();
  if number.unit.category() == Energy {
    if value >= d128!(1000000000000) { // 1 terajoule
      return Number::new(value/Terajoule.weight(), Terajoule)
    } else if value >= d128!(1000000000) { // 1 gigajoule
      return Number::new(value/Gigajoule.weight(), Gigajoule)
    } else if value >= d128!(1000000) { // 1 megajoule
      return Number::new(value/Megajoule.weight(), Megajoule)
    } else if value >= d128!(1000) { // 1 kilojoule
      return Number::new(value/Kilojoule.weight(), Kilojoule)
    } else if value >= d128!(1) { // 1 joule
      return Number::new(value/Joule.weight(), Joule)
    } else {
      return Number::new(value * get_inverted_millijoule_weight(), Millijoule)
    }
  }
  number
}

/// Multiply two [`Number`]s
/// 
/// - Temperatures don't work
/// - If you multiply [`NoType`] with any other unit, the result gets that other unit
/// - If you multiply [`Length`] with [`Length`], the result has a unit of [`Area`], etc.
/// - If you multiply [`Speed`] with [`Time`], the result has a unit of [`Length`]
/// - If you multiply [`Voltage`] with [`ElectricCurrent`], the result has a unit of [`Power`]
/// - If you multiply [`ElectricCurrent`] with [`Resistance`], the result has a unit of [`Voltage`]
/// - If you multiply [`Power`] with [`Time`], the result has a unit of [`Energy`]
pub fn multiply(left: Number, right: Number) -> Result<Number, String> {
  Ok(actual_multiply(left, right, false)?)
}

fn actual_multiply(left: Number, right: Number, swapped: bool) -> Result<Number, String> {
  let lcat = left.unit.category();
  let rcat = right.unit.category();
  if left.unit == NoUnit && right.unit == NoUnit {
    // 3 * 2
    Ok(Number::new(left.value * right.value, left.unit))
  } else if left.unit.category() == Temperature || right.unit.category() == Temperature {
    // if temperature
    Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit))
  } else if left.unit == NoUnit && right.unit != NoUnit {
    // 3 * 2 anyunit
    Ok(Number::new(left.value * right.value, right.unit))
  } else if lcat == Length && rcat == Length {
    // length * length
    let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, SquareMillimeter)))
  } else if lcat == Length && rcat == Area {
    // length * area
    let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, CubicMillimeter)))
  } else if lcat == Speed && rcat == Time {
    // 1 km/h * 1h
    let kph_value = left.value * left.unit.weight();
    let hours = convert(right, Hour)?;
    let result = kph_value * hours.value;
    let final_unit = match left.unit {
      KilometersPerHour => Kilometer,
      MetersPerSecond => Meter,
      MilesPerHour => Mile,
      FeetPerSecond => Foot,
      Knot => NauticalMile,
      _ => Meter,
    };
    let kilometers = Number::new(result, Kilometer);
    Ok(convert(kilometers, final_unit)?)
  } else if lcat == Voltage && rcat == ElectricCurrent {
    // 1 volt * 1 ampere = 1 watt
    let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, Watt)))
  } else if lcat == ElectricCurrent && rcat == Resistance {
    // 1 amp * 1 ohm = 1 volt
    let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, Watt)))
  } else if lcat == Power && rcat == Time {
    // 1 watt * 1 second = 1 joule
    let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight() / Unit::Second.weight());
    match right.unit {
      Second => return Ok(to_ideal_joule_unit(Number::new(result, Joule))),
      _ => return Ok(to_ideal_unit(Number::new(result, Joule))),
    };
  } else {
    if swapped == true {
      Err(format!("Cannot multiply {:?} and {:?}", right.unit, left.unit))
    } else {
      actual_multiply(right, left, true)
    }
  }
}

/// Divide a [`Number`] by another [`Number`]
/// 
/// - Temperatures don't work
/// - If you divide a unit by that same unit, the result has a unit of [`NoType`]
/// - If you divide [`Volume`] by [`Length`], the result has a unit of [`Area`], etc.
/// - If you divide [`Length`] by [`Time`], the result has a unit of [`Speed`]
/// - If you divide [`Power`] by [`ElectricCurrent`], the result has a unit of [`Volt`]
/// - If you divide [`Voltage`] by [`ElectricCurrent`], the result has a unit of [`Ohm`]
/// - If you divide [`Voltage`] by [`Resistance`], the result has a unit of [`Ampere`]
/// - If you divide [`Power`] by [`Voltage`], the result has a unit of [`Ampere`]
/// - If you divide [`Energy`] by [`Time`], the result has a unit of [`Power`]
pub fn divide(left: Number, right: Number) -> Result<Number, String> {
  let lcat = left.unit.category();
  let rcat = right.unit.category();
  if left.unit == NoUnit && right.unit == NoUnit {
    // 3 / 2
    Ok(Number::new(left.value / right.value, left.unit))
  } else if lcat == Temperature || rcat == Temperature {
    // if temperature
    Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit))
  } else if left.unit != NoUnit && right.unit == NoUnit {
    // 1 km / 2
    Ok(Number::new(left.value / right.value, left.unit))
  } else if lcat == rcat {
    // 4 km / 2 km
    let (left, right) = convert_to_lowest(left, right)?;
    Ok(Number::new(left.value / right.value, NoUnit))
  } else if (lcat == Area && rcat == Length) || (lcat == Volume && rcat == Area) {
    // 1 km2 / 1 km, 1 km3 / 1 km2
    let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, Millimeter)))
  } else if lcat == Volume && rcat == Length {
    // 1 km3 / 1 km
    let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, SquareMillimeter)))
  } else if lcat == Length && rcat == Time {
    // 1 km / 2s
    let final_unit = match (left.unit, right.unit) {
      (Kilometer, Hour) => KilometersPerHour,
      (Meter, Second) => MetersPerSecond,
      (Mile, Hour) => MilesPerHour,
      (Foot, Second) => FeetPerSecond,
      (NauticalMile, Hour) => Knot,
      _ => KilometersPerHour,
    };
    let kilometers = convert(left, Kilometer)?;
    let hours = convert(right, Hour)?;
    let kph = Number::new(kilometers.value / hours.value, KilometersPerHour);
    Ok(convert(kph, final_unit)?)
  } else if lcat == Power && rcat == ElectricCurrent {
    // 1 watt / 1 ampere = 1 volt
    let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, Volt)))
  } else if lcat == Voltage && rcat == ElectricCurrent {
    // 1 volt / 1 ampere = 1 ohm
    let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, Ohm)))
  } else if lcat == Voltage && rcat == Resistance {
    // 1 volt / 1 ohm = 1 amp
    let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, Ampere)))
  } else if lcat == Power && rcat == Voltage {
    // 1 watt / 1 volt = 1 amp
    let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight());
    Ok(to_ideal_unit(Number::new(result, Ampere)))
  } else if lcat == Energy && rcat == Time {
    // 1 joule / 1 second = 1 watt
    let result = (left.value * left.unit.weight()) / ((right.value * right.unit.weight() / Unit::Second.weight()));
    Ok(to_ideal_unit(Number::new(result, Watt)))
  } else {
    Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit))
  }
}
/// Modulo a [`Number`] by another [`Number`].
/// 
/// `left` and `right` need to have the same [`UnitType`], and the result will have that same [`UnitType`].
///
/// Temperatures don't work.
pub fn modulo(left: Number, right: Number) -> Result<Number, String> {
  if left.unit.category() == Temperature || right.unit.category() == Temperature {
    // if temperature
    Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit))
  } else if left.unit.category() == right.unit.category() {
    // 5 km % 3 m
    let (left, right) = convert_to_lowest(left, right)?;
    Ok(Number::new(left.value % right.value, left.unit))
  } else {
    Err(format!("Cannot modulo {:?} by {:?}", left.unit, right.unit))
  }
}

/// Returns a [`Number`] to the power of another [`Number`]
/// 
/// - If you take [`Length`] to the power of [`NoType`], the result has a unit of [`Area`].
/// - If you take [`Length`] to the power of [`Length`], the result has a unit of [`Area`]
/// - If you take [`Length`] to the power of [`Area`], the result has a unit of [`Volume`]
/// - etc.
pub fn pow(left: Number, right: Number) -> Result<Number, String> {
  let lcat = left.unit.category();
  let rcat = left.unit.category();
  if left.unit == NoUnit && right.unit == NoUnit {
    // 3 ^ 2
    Ok(Number::new(left.value.pow(right.value), left.unit))
  } else if right.value == d128!(1) && right.unit == NoUnit {
    Ok(left)
  } else if lcat == Length && right.unit == NoUnit && right.value == d128!(2) {
    // x km ^ 2
    let result = (left.value * left.unit.weight()).pow(right.value);
    Ok(to_ideal_unit(Number::new(result, SquareMillimeter)))
  } else if lcat == Length && right.unit == NoUnit && right.value == d128!(3) {
    // x km ^ 3
    let result = (left.value * left.unit.weight()).pow(right.value);
    Ok(to_ideal_unit(Number::new(result, CubicMillimeter)))
  } else if lcat == Length && rcat == Length && right.value == d128!(1) {
    // x km ^ 1 km
    Ok(multiply(left, right)?)
  } else if lcat == Length && rcat == Length && right.value == d128!(2) {
    // x km ^ 2 km
    let pow2 = multiply(left, Number::new(d128!(1), right.unit))?;
    let pow3 = multiply(pow2, Number::new(d128!(1), right.unit))?;
    Ok(pow3)
  } else if lcat == Length && rcat == Area && right.value == d128!(1) {
    // x km ^ km2
    Ok(multiply(left, Number::new(d128!(1), right.unit))?)
  } else {
    Err(format!("Cannot multiply {:?} and {:?}", left.unit, right.unit))
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_convert() {
    pub fn convert_test(value: f64, unit: Unit, to_unit: Unit) -> f64 {
      use std::str::FromStr;

      let value_string = &value.to_string();
      let value_d128 = d128::from_str(value_string).unwrap();
      let number = Number::new(value_d128, unit);
      
      let result = convert(number, to_unit);
      let string_result = &result.unwrap().value.to_string();
      let float_result = f64::from_str(string_result).unwrap();

      return float_result;
    }
    assert_eq!(convert_test(1000.0, Nanosecond, Microsecond), 1.0);
    assert_eq!(convert_test(1000.0, Nanosecond, Microsecond), 1.0);
    assert_eq!(convert_test(1000.0, Microsecond, Millisecond), 1.0);
    assert_eq!(convert_test(1000.0, Millisecond, Second), 1.0);
    assert_eq!(convert_test(60.0, Second, Minute), 1.0);
    assert_eq!(convert_test(60.0, Minute, Hour), 1.0);
    assert_eq!(convert_test(24.0, Hour, Day), 1.0);
    assert_eq!(convert_test(7.0, Day, Week), 1.0);
    assert_eq!(convert_test(30.436875, Day, Month), 1.0);
    assert_eq!(convert_test(3.0, Month, Quarter), 1.0);
    assert_eq!(convert_test(4.0, Quarter, Year), 1.0);
    assert_eq!(convert_test(10.0, Year, Decade), 1.0);
    assert_eq!(convert_test(10.0, Decade, Century), 1.0);
    assert_eq!(convert_test(10.0, Century, Millenium), 1.0);

    assert_eq!(convert_test(10.0, Millimeter, Centimeter), 1.0);
    assert_eq!(convert_test(10.0, Centimeter, Decimeter), 1.0);
    assert_eq!(convert_test(10.0, Decimeter, Meter), 1.0);
    assert_eq!(convert_test(1000.0, Meter, Kilometer), 1.0);
    assert_eq!(convert_test(2.54, Centimeter, Inch), 1.0);
    assert_eq!(convert_test(12.0, Inch, Foot), 1.0);
    assert_eq!(convert_test(3.0, Foot, Yard), 1.0);
    assert_eq!(convert_test(1760.0, Yard, Mile), 1.0);
    assert_eq!(convert_test(1852.0, Meter, NauticalMile), 1.0);
    assert_eq!(convert_test(9460730472580800.0, Meter, LightYear), 1.0);
    assert_eq!(convert_test(299792458.0, Meter, LightSecond), 1.0);
    
    assert_eq!(convert_test(100.0, SquareMillimeter, SquareCentimeter), 1.0);
    assert_eq!(convert_test(100.0, SquareCentimeter, SquareDecimeter), 1.0);
    assert_eq!(convert_test(100.0, SquareDecimeter, SquareMeter), 1.0);
    assert_eq!(convert_test(1000000.0, SquareMeter, SquareKilometer), 1.0);
    assert_eq!(convert_test(645.16, SquareMillimeter, SquareInch), 1.0);
    assert_eq!(convert_test(144.0, SquareInch, SquareFoot), 1.0);
    assert_eq!(convert_test(9.0, SquareFoot, SquareYard), 1.0);
    assert_eq!(convert_test(3097600.0, SquareYard, SquareMile), 1.0);
    assert_eq!(convert_test(100.0, SquareMeter, Are), 1.0);
    assert_eq!(convert_test(10.0, Are, Decare), 1.0);
    assert_eq!(convert_test(10.0, Decare, Hectare), 1.0);
    assert_eq!(convert_test(640.0, Acre, SquareMile), 1.0);

    assert_eq!(convert_test(1000.0, CubicMillimeter, CubicCentimeter), 1.0);
    assert_eq!(convert_test(1000.0, CubicCentimeter, CubicDecimeter), 1.0);
    assert_eq!(convert_test(1000.0, CubicDecimeter, CubicMeter), 1.0);
    assert_eq!(convert_test(1000000000.0, CubicMeter, CubicKilometer), 1.0);
    assert_eq!(convert_test(1728.0, CubicInch, CubicFoot), 1.0);
    assert_eq!(convert_test(27.0, CubicFoot, CubicYard), 1.0);
    assert_eq!(convert_test(5451776000.0, CubicYard, CubicMile), 1.0);
    assert_eq!(convert_test(1.0, Milliliter, CubicCentimeter), 1.0);
    assert_eq!(convert_test(10.0, Milliliter, Centiliter), 1.0);
    assert_eq!(convert_test(10.0, Centiliter, Deciliter), 1.0);
    assert_eq!(convert_test(10.0, Deciliter, Liter), 1.0);
    assert_eq!(convert_test(4.92892159375, Milliliter, Teaspoon), 1.0);
    assert_eq!(convert_test(3.0, Teaspoon, Tablespoon), 1.0);
    assert_eq!(convert_test(2.0, Tablespoon, FluidOunce), 1.0);
    assert_eq!(convert_test(8.0, FluidOunce, Cup), 1.0);
    assert_eq!(convert_test(2.0, Cup, Pint), 1.0);
    assert_eq!(convert_test(2.0, Pint, Quart), 1.0);
    assert_eq!(convert_test(4.0, Quart, Gallon), 1.0);
    assert_eq!(convert_test(42.0, Gallon, OilBarrel), 1.0);

    assert_eq!(convert_test(1000.0, Milligram, Gram), 1.0);
    assert_eq!(convert_test(100.0, Gram, Hectogram), 1.0);
    assert_eq!(convert_test(1000.0, Gram, Kilogram), 1.0);
    assert_eq!(convert_test(1000.0, Kilogram, MetricTon), 1.0);
    assert_eq!(convert_test(0.45359237, Kilogram, Pound), 1.0);
    assert_eq!(convert_test(16.0, Ounce, Pound), 1.0);
    assert_eq!(convert_test(14.0, Pound, Stone), 1.0);
    assert_eq!(convert_test(2000.0, Pound, ShortTon), 1.0);
    assert_eq!(convert_test(2240.0, Pound, LongTon), 1.0);

    assert_eq!(convert_test(1000.0, Bit, Kilobit), 1.0);
    assert_eq!(convert_test(1000.0, Kilobit, Megabit), 1.0);
    assert_eq!(convert_test(1000.0, Megabit, Gigabit), 1.0);
    assert_eq!(convert_test(1000.0, Gigabit, Terabit), 1.0);
    assert_eq!(convert_test(1000.0, Terabit, Petabit), 1.0);
    assert_eq!(convert_test(1000.0, Petabit, Exabit), 1.0);
    assert_eq!(convert_test(1000.0, Exabit, Zettabit), 1.0);
    assert_eq!(convert_test(1000.0, Zettabit, Yottabit), 1.0);
    assert_eq!(convert_test(1024.0, Bit, Kibibit), 1.0);
    assert_eq!(convert_test(1024.0, Kibibit, Mebibit), 1.0);
    assert_eq!(convert_test(1024.0, Mebibit, Gibibit), 1.0);
    assert_eq!(convert_test(1024.0, Gibibit, Tebibit), 1.0);
    assert_eq!(convert_test(1024.0, Tebibit, Pebibit), 1.0);
    assert_eq!(convert_test(1024.0, Pebibit, Exbibit), 1.0);
    assert_eq!(convert_test(1024.0, Exbibit, Zebibit), 1.0);
    assert_eq!(convert_test(1024.0, Zebibit, Yobibit), 1.0);
    assert_eq!(convert_test(8.0, Bit, Byte), 1.0);
    assert_eq!(convert_test(1000.0, Byte, Kilobyte), 1.0);
    assert_eq!(convert_test(1000.0, Kilobyte, Megabyte), 1.0);
    assert_eq!(convert_test(1000.0, Megabyte, Gigabyte), 1.0);
    assert_eq!(convert_test(1000.0, Gigabyte, Terabyte), 1.0);
    assert_eq!(convert_test(1000.0, Terabyte, Petabyte), 1.0);
    assert_eq!(convert_test(1000.0, Petabyte, Exabyte), 1.0);
    assert_eq!(convert_test(1000.0, Exabyte, Zettabyte), 1.0);
    assert_eq!(convert_test(1000.0, Zettabyte, Yottabyte), 1.0);
    assert_eq!(convert_test(1024.0, Kibibyte, Mebibyte), 1.0);
    assert_eq!(convert_test(1024.0, Mebibyte, Gibibyte), 1.0);
    assert_eq!(convert_test(1024.0, Gibibyte, Tebibyte), 1.0);
    assert_eq!(convert_test(1024.0, Tebibyte, Pebibyte), 1.0);
    assert_eq!(convert_test(1024.0, Pebibyte, Exbibyte), 1.0);
    assert_eq!(convert_test(1024.0, Exbibyte, Zebibyte), 1.0);
    assert_eq!(convert_test(1024.0, Zebibyte, Yobibyte), 1.0);


    assert_eq!(convert_test(1000.0, Millijoule, Joule), 1.0);
    assert_eq!(convert_test(1000.0, Joule, Kilojoule), 1.0);
    assert_eq!(convert_test(1.0, NewtonMeter, Joule), 1.0);
    assert_eq!(convert_test(1000.0, Kilojoule, Megajoule), 1.0);
    assert_eq!(convert_test(1000.0, Megajoule, Gigajoule), 1.0);
    assert_eq!(convert_test(1000.0, Gigajoule, Terajoule), 1.0);
    assert_eq!(convert_test(4.1868, Joule, Calorie), 1.0);
    assert_eq!(convert_test(1000.0, Calorie, KiloCalorie), 1.0);
    assert_eq!(convert_test(1055.05585262, Joule, BritishThermalUnit), 1.0);
    assert_eq!(convert_test(3600.0, Joule, WattHour), 1.0);
    assert_eq!(convert_test(1000.0, WattHour, KilowattHour), 1.0);
    assert_eq!(convert_test(1000.0, KilowattHour, MegawattHour), 1.0);
    assert_eq!(convert_test(1000.0, MegawattHour, GigawattHour), 1.0);
    assert_eq!(convert_test(1000.0, GigawattHour, TerawattHour), 1.0);
    assert_eq!(convert_test(1000.0, TerawattHour, PetawattHour), 1.0);

    assert_eq!(convert_test(1000.0, Milliwatt, Watt), 1.0);
    assert_eq!(convert_test(1000.0, Watt, Kilowatt), 1.0);
    assert_eq!(convert_test(1000.0, Kilowatt, Megawatt), 1.0);
    assert_eq!(convert_test(1000.0, Megawatt, Gigawatt), 1.0);
    assert_eq!(convert_test(1000.0, Gigawatt, Terawatt), 1.0);
    assert_eq!(convert_test(1000.0, Terawatt, Petawatt), 1.0);
    assert_eq!(convert_test(0.0568690272188, Watt, BritishThermalUnitsPerMinute), 1.0);
    assert_eq!(convert_test(60.0, BritishThermalUnitsPerMinute, BritishThermalUnitsPerHour), 1.0);
    assert_eq!(convert_test(745.6998715822702, Watt, Horsepower), 1.0);
    assert_eq!(convert_test(735.49875, Watt, MetricHorsepower), 1.0);

    assert_eq!(convert_test(1000.0, Milliampere, Ampere), 1.0);
    assert_eq!(convert_test(1000.0, Ampere, Kiloampere), 1.0);
    assert_eq!(convert_test(10.0, Ampere, Abampere), 1.0);

    assert_eq!(convert_test(1000.0, Milliohm, Ohm), 1.0);
    assert_eq!(convert_test(1000.0, Ohm, Kiloohm), 1.0);

    assert_eq!(convert_test(1000.0, Millivolt, Volt), 1.0);
    assert_eq!(convert_test(1000.0, Volt, Kilovolt), 1.0);

    assert_eq!(convert_test(1000.0, Pascal, Kilopascal), 1.0);
    assert_eq!(convert_test(101325.0, Pascal, Atmosphere), 1.0);
    assert_eq!(convert_test(100.0, Pascal, Millibar), 1.0);
    assert_eq!(convert_test(1000.0, Millibar, Bar), 1.0);
    assert_eq!(convert_test(3386.389, Pascal, InchOfMercury), 1.0);
    assert_eq!(convert_test(6894.757293168361, Pascal, PoundsPerSquareInch), 1.0);
    assert_eq!(convert_test(162.12, Pascal, Torr), 1.0);

    assert_eq!(convert_test(1000.0, Hertz, Kilohertz), 1.0);
    assert_eq!(convert_test(1000.0, Kilohertz, Megahertz), 1.0);
    assert_eq!(convert_test(1000.0, Megahertz, Gigahertz), 1.0);
    assert_eq!(convert_test(1000.0, Gigahertz, Terahertz), 1.0);
    assert_eq!(convert_test(1000.0, Terahertz, Petahertz), 1.0);
    assert_eq!(convert_test(60.0, Hertz, RevolutionsPerMinute), 1.0);

    assert_eq!(convert_test(3.6, KilometersPerHour, MetersPerSecond), 1.0);
    assert_eq!(convert_test(0.3048, MetersPerSecond, FeetPerSecond), 1.0);
    assert_eq!(convert_test(1.609344, KilometersPerHour, MilesPerHour), 1.0);
    assert_eq!(convert_test(1.852, KilometersPerHour, Knot), 1.0);

    assert_eq!(convert_test(274.15, Kelvin, Celsius), 1.0);
    assert_eq!(convert_test(300.0, Kelvin, Fahrenheit), 80.33);
    assert_eq!(convert_test(-272.15, Celsius, Kelvin), 1.0);
    assert_eq!(convert_test(-15.0, Celsius, Fahrenheit), 5.0);
    assert_eq!(convert_test(80.33, Fahrenheit, Kelvin), 300.0);
    assert_eq!(convert_test(5.0, Fahrenheit, Celsius), -15.0);
  }
}
