# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
################################
# Converter Module             #
################################
# Originally by GluffiS <gluffis@mean.net>

package BotModules::Converter;
use vars qw(@ISA);
@ISA = qw(BotModules);
1;

# XXX support the suffixes "to <n> sf" or "to <n> dp"
# XXX support speed, volume, twips
# XXX support light year, parsec, furlong; fm, pm, µm, Mm, Gm, Tm, Pm
# XXX support 1x10^1 notation as well as the already-supported 1e1 notation

sub Help {
    my $self = shift;
    my ($event) = @_;
    return {
        '' => 'A generic converter. Currently supports converting between positive integers in binary, octal, decimal and hexidecimal forms, and converting temperatures, lengths, times and masses.',
        'syntax' => 'To convert a number, simply give the number with units or appropriate prefixes, for example to convert from hexadecimal: \'0x2F\'',
        'integers' => 'Decimal: Simply give the number.  Hexadecimal: Prefix with 0x.  Octal: Prefix with 0.  Binary: Prefix with 0b.',
        'temperature' => 'Kelvin: Suffix with K.  Celsius: Suffix with C.  Fahrenheit: Suffix with F.',
        'length' => 'Imperial: in, ft, yd, mi.  Metric: A, nm, mm, cm, m, km.', # XXX should also support light year, parsec, furlong; fm, pm, µm, Mm, Gm, Tm, Pm
        'time' => 'ISO time units: year, month, week, day, hour, minute, second.  Exotic time units: millifortnight.',
        'mass' => 'Imperial: lbs, oz, stone.  Metric: kg, g.',
        # XXX should support speed, volume, twips
  };
}


sub Told {
    my $self = shift;
    my ($event, $message) = @_;

    # integers
    if ($message =~ m/^\s*([1-9][0-9]*|0)\s*\??\s*$/osi) {
        $self->convertDecimal($event, $1);
    } elsif ($message =~ m/^\s*0x([a-f0-9]+)\s*\??\s*$/osi) {
        $self->convertHex($event, $1);
    } elsif ($message =~ m/^\s*0([0-9]+)\s*\??\s*$/osi) {
        $self->convertOctal($event, $1);
    } elsif ($message =~ m/^\s*0b([0-9]+)\s*\??\s*$/osi) {
        $self->convertBinary($event, $1);

    # temperatures
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:kelvin|K)\s*\??\s*$/osi) {
        $self->convertKelvin($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:deg(?:rees?)|[\`°])?\s*(?:cel[sc]ius|centigrade|c)\s*\??\s*$/osi) {
        $self->convertCelsius($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:deg(?:rees?)|[\`°])?\s*(?:fahrenheit|f)\s*\??\s*$/osi) {
        $self->convertFahrenheit($event, $1);

    # imperial lengths
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:ins?|inch(?:es)?)\s*\??\s*$/osi) {
        $self->convertInches($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:ft|feet|foot)\s*\??\s*$/osi) {
        $self->convertFeet($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:yds?|yards?)\s*\??\s*$/osi) {
        $self->convertYards($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:mi|miles?)\s*\??\s*$/osi) {
        $self->convertMiles($event, $1);

    # metric lengths
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:Å|a|angstroms?)\s*\??\s*$/osi) {
        $self->convertAngstroms($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:nms?|nanometers?|nanometres?)\s*\??\s*$/osi) {
        $self->convertNanometers($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:mms?|millimeters?|millimetres?)\s*\??\s*$/osi) {
        $self->convertMillimeters($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:cms?|centimeters?|centimetres?)\s*\??\s*$/osi) {
        $self->convertCentimeters($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:m|meters?|metres?)\s*\??\s*$/osi) {
        $self->convertMeters($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:kms?|kilometers?|kilometres?|klic?ks?)\s*\??\s*$/osi) {
        $self->convertKilometers($event, $1);
        
    # times
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:years|year|yr)\s*\??\s*$/osi) {
        $self->convertYears($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:months|month|mo)\s*\??\s*$/osi) {
        $self->convertMonths($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:weeks|week|wk)\s*\??\s*$/osi) {
        $self->convertWeeks($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:fortnights|fortnight|mf)\s*\??\s*$/osi) {
        $self->convertMillifortnights($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:days|day|d)\s*\??\s*$/osi) {
        $self->convertDays($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:hours|hour|hr|h)\s*\??\s*$/osi) {  
        $self->convertHours($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:minutes|minute|min)\s*\??\s*$/osi) {
       $self->convertMinutes($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:seconds|second|sec|s)\s*\??\s*$/osi) {
       $self->convertSeconds($event, $1);

    # masses
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:grams|gram|g)\s*\??\s*$/osi) {
        $self->convertGrams($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:kilograms|kilogram|kilos|kilo|kg)\s*\??\s*$/osi) {
        $self->convertKilograms($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:pounds|pound|lbs)\s*\??\s*$/osi) {
        $self->convertPounds($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:ounces|ounce|oz)\s*\??\s*$/osi) {
        $self->convertOunces($event, $1);
    } elsif ($message =~ m/^\s*(-?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+)(?:e-?[0-9]+)?)\s*(?:stones|stone)\s*\??\s*$/osi) {
        $self->convertStones($event, $1);
    
    # oh well
    } else {
        return $self->SUPER::Told(@_);
    }
    return 0;
}


# Integers

sub convertDecimal {
    my $self = shift;
    my($event, $decimal) = @_;
    my $hex = sprintf('%X', $decimal);
    my $octal = sprintf('%o', $decimal);
    my $binary = sprintf('%b', $decimal);
    $self->say($event, "$event->{'from'}: $decimal = 0x$hex, 0$octal, 0b$binary");
}

sub convertHex {
    my $self = shift;
    my($event, $hex) = @_;
    my $decimal = hex($hex);
    my $hex = sprintf('%X', $decimal); # normalise
    my $octal = sprintf('%o', $decimal);
    my $binary = sprintf('%b', $decimal);
    $self->say($event, "$event->{'from'}: 0x$hex = $decimal, 0$octal, 0b$binary");
}

sub convertOctal {
    my $self = shift;
    my($event, $octal) = @_;
    my $decimal = oct("0$octal");
    my $hex = sprintf('%X', $decimal);
    my $binary = sprintf('%b', $decimal);
    $self->say($event, "$event->{'from'}: 0$octal = $decimal, 0x$hex, 0b$binary");
}

sub convertBinary {
    my $self = shift;
    my($event, $binary) = @_;
    my $decimal = oct("0b$binary");
    my $hex = sprintf('%X', $decimal);
    my $octal = sprintf('%o', $decimal);
    $self->say($event, "$event->{'from'}: 0b$binary = $decimal, 0x$hex, 0$octal");
}


# Temperature

sub convertKelvin {
    my $self = shift;
    my($event, $kelvin) = @_;
    my $celsius = round(1, $kelvin - 273.14);
    my $fahrenheit = round(1, ($kelvin - 273.14) * 9 / 5 + 32);
    my $kelvin = round(1, $kelvin); # normalise
    my $prognosis = diagnoseTemperature($kelvin, $celsius, $fahrenheit);
    $self->say($event, "$event->{'from'}: ${kelvin}K = $celsius°C, $fahrenheit°F, $prognosis");
}

sub convertCelsius {
    my $self = shift;
    my($event, $celsius) = @_;
    my $kelvin = round(1, $celsius + 273.14);
    my $fahrenheit = round(1, $celsius * 9 / 5 + 32);
    my $celsius = round(1, $celsius); # normalise
    my $prognosis = diagnoseTemperature($kelvin, $celsius, $fahrenheit);
    $self->say($event, "$event->{'from'}: $celsius°C = ${kelvin}K, $fahrenheit°F, $prognosis");
}

sub convertFahrenheit {
    my $self = shift;
    my($event, $fahrenheit) = @_;
    my $celsius = round(1, ($fahrenheit - 32) * 5 / 9);
    my $kelvin = round(1, ($fahrenheit - 32) * 5 / 9 + 273.14);
    my $fahrenheit = round(1, $fahrenheit); # normalise
    my $prognosis = diagnoseTemperature($kelvin, $celsius, $fahrenheit);
    $self->say($event, "$event->{'from'}: $fahrenheit°F = ${kelvin}K, $celsius°C, $prognosis");
}

sub diagnoseTemperature($$$) {
    my($kelvin, $celsius, $fahrenheit) = @_;
    return
      $kelvin < 0 ? 'an impossible temperature' :
        $kelvin == 0 ? 'absolute zero' :
          $fahrenheit < 0 ? 'extremely cold' :
            $celsius < 0 ? 'freezing cold' :
              $celsius == 0 ? 'freezing point of water' :
                $celsius < 18 ? 'cold' :
                  $celsius == 20 ? 'standard room temperature' :
                    $celsius < 25 ? 'warm' :
                      $celsius < 35 ? 'hot' :
                        $celsius <= 37 ? 'body temperature' :
                          $celsius < 65 ? 'very hot' :
                            $celsius < 95 ? 'scorching hot' :
                              $celsius == 100 ? 'boiling point of water' :
                                $celsius < 105 ? 'boiling hot' :
                                  'ridiculously hot';
}


# Imperial Lengths

sub convertInches {
    my $self = shift;
    my($event, $inches) = @_;
    # imperial
    # (inches)
    my $feet = sigfig(3, $inches / 12.0);
    my $yards = sigfig(3, $inches / 36.0);
    my $miles = sigfig(3, $inches / 63360.0);
    # metric
    my $kilometers = sigfig(3, $inches * 0.0000254);
    my $meters = sigfig(3, $inches * 0.0254);
    my $centimeters = sigfig(3, $inches * 2.54);
    my $millimeters = sigfig(3, $inches * 25.4);
    my $nanometers = sigfig(3, $inches * 25400000.0);
    my $angstroms = sigfig(3, $inches * 254000000.0);
    # normalise
    my $inches = sigfig(3, $inches);
    $self->say($event, "$event->{'from'}: ${inches}in = ${feet}ft, ${yards}yd, ${miles}mi; ${kilometers}Km, ${meters}m, ${centimeters}cm, ${millimeters}mm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}

sub convertFeet {
    my $self = shift;
    my($event, $feet) = @_;
    my $inches = $feet * 12.0;
    # imperial
    # (inches)
    my $feet = sigfig(3, $inches / 12.0);
    my $yards = sigfig(3, $inches / 36.0);
    my $miles = sigfig(3, $inches / 63360.0);
    # metric
    my $kilometers = sigfig(3, $inches * 0.0000254);
    my $meters = sigfig(3, $inches * 0.0254);
    my $centimeters = sigfig(3, $inches * 2.54);
    my $millimeters = sigfig(3, $inches * 25.4);
    my $nanometers = sigfig(3, $inches * 25400000.0);
    my $angstroms = sigfig(3, $inches * 254000000.0);
    # normalise
    my $inches = sigfig(3, $inches);
    $self->say($event, "$event->{'from'}: ${feet}ft = ${inches}in, ${yards}yd, ${miles}mi, ${kilometers}Km, ${meters}m, ${centimeters}cm, ${millimeters}mm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}

sub convertYards {
    my $self = shift;
    my($event, $yards) = @_;
    my $inches = $yards * 36.0;
    # imperial
    # (inches)
    my $feet = sigfig(3, $inches / 12.0);
    my $yards = sigfig(3, $inches / 36.0);
    my $miles = sigfig(3, $inches / 63360.0);
    # metric
    my $kilometers = sigfig(3, $inches * 0.0000254);
    my $meters = sigfig(3, $inches * 0.0254);
    my $centimeters = sigfig(3, $inches * 2.54);
    my $millimeters = sigfig(3, $inches * 25.4);
    my $nanometers = sigfig(3, $inches * 25400000.0);
    my $angstroms = sigfig(3, $inches * 254000000.0);
    # normalise
    my $inches = sigfig(3, $inches);
    $self->say($event, "$event->{'from'}: ${yards}yd = ${inches}in, ${feet}ft, ${miles}mi, ${kilometers}Km, ${meters}m, ${centimeters}cm, ${millimeters}mm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}

sub convertMiles {
    my $self = shift;
    my($event, $miles) = @_;
    my $inches = $miles * 190080.0;
    # imperial
    # (inches)
    my $feet = sigfig(3, $inches / 12.0);
    my $yards = sigfig(3, $inches / 36.0);
    my $miles = sigfig(3, $inches / 63360.0);
    # metric
    my $kilometers = sigfig(3, $inches * 0.0000254);
    my $meters = sigfig(3, $inches * 0.0254);
    my $centimeters = sigfig(3, $inches * 2.54);
    my $millimeters = sigfig(3, $inches * 25.4);
    my $nanometers = sigfig(3, $inches * 25400000.0);
    my $angstroms = sigfig(3, $inches * 254000000.0);
    # normalise
    my $inches = sigfig(3, $inches);
    $self->say($event, "$event->{'from'}: ${miles}mi = ${inches}in, ${feet}ft, ${yards}yd, ${kilometers}Km, ${meters}m, ${centimeters}cm, ${millimeters}mm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}


# Metric Lengths

sub convertAngstroms {
    my $self = shift;
    my($event, $input) = @_;
    # get the number
    my $accurateMeters = $input / 10000000000.0;
    $self->debug("Accurate KiloMeters: ".$accurateMeters/1000);
    # imperial
    my $inches = sigfig(3, $accurateMeters / (0.0254 * 1.0));
    my $feet = sigfig(3, $accurateMeters / (0.0254 * 12.0));
    my $yards = sigfig(3, $accurateMeters / (0.0254 * 36.0));
    my $miles = sigfig(3, $accurateMeters / (0.0254 * 63360.0));
    # metric
    my $kilometers = sigfig(3, $accurateMeters / 1000.0);
    my $meters = sigfig(3, $accurateMeters);
    my $centimeters = sigfig(3, $accurateMeters * 100.0);
    my $millimeters = sigfig(3, $accurateMeters * 1000.0);
    my $nanometers = sigfig(3, $accurateMeters * 1000000000.0);
    my $angstroms = sigfig(3, $accurateMeters * 10000000000.0);
    $self->say($event, "$event->{'from'}: ${angstroms}Å = ${inches}in, ${feet}ft, ${yards}yd, ${miles}mi; ${kilometers}Km, ${meters}m, ${centimeters}cm, ${millimeters}mm, ${nanometers}nm (to 3sf)");
}

sub convertNanometers {
    my $self = shift;
    my($event, $input) = @_;
    # get the number
    my $accurateMeters = $input / 1000000000.0;
    # imperial
    my $inches = sigfig(3, $accurateMeters / (0.0254 * 1.0));
    my $feet = sigfig(3, $accurateMeters / (0.0254 * 12.0));
    my $yards = sigfig(3, $accurateMeters / (0.0254 * 36.0));
    my $miles = sigfig(3, $accurateMeters / (0.0254 * 63360.0));
    # metric
    my $kilometers = sigfig(3, $accurateMeters / 1000.0);
    my $meters = sigfig(3, $accurateMeters);
    my $centimeters = sigfig(3, $accurateMeters * 100.0);
    my $millimeters = sigfig(3, $accurateMeters * 1000.0);
    my $nanometers = sigfig(3, $accurateMeters * 1000000000.0);
    my $angstroms = sigfig(3, $accurateMeters * 10000000000.0);
    $self->say($event, "$event->{'from'}: ${nanometers}nm = ${inches}in, ${feet}ft, ${yards}yd, ${miles}mi; ${kilometers}Km, ${meters}m, ${centimeters}cm, ${millimeters}mm, ${angstroms}Å (to 3sf)");
}

sub convertMillimeters {
    my $self = shift;
    my($event, $input) = @_;
    # get the number
    my $accurateMeters = $input / 1000.0;
    # imperial
    my $inches = sigfig(3, $accurateMeters / (0.0254 * 1.0));
    my $feet = sigfig(3, $accurateMeters / (0.0254 * 12.0));
    my $yards = sigfig(3, $accurateMeters / (0.0254 * 36.0));
    my $miles = sigfig(3, $accurateMeters / (0.0254 * 63360.0));
    # metric
    my $kilometers = sigfig(3, $accurateMeters / 1000.0);
    my $meters = sigfig(3, $accurateMeters);
    my $centimeters = sigfig(3, $accurateMeters * 100.0);
    my $millimeters = sigfig(3, $accurateMeters * 1000.0);
    my $nanometers = sigfig(3, $accurateMeters * 1000000000.0);
    my $angstroms = sigfig(3, $accurateMeters * 10000000000.0);
    $self->say($event, "$event->{'from'}: ${millimeters}mm = ${inches}in, ${feet}ft, ${yards}yd, ${miles}mi; ${kilometers}Km, ${meters}m, ${centimeters}cm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}

sub convertCentimeters {
    my $self = shift;
    my($event, $input) = @_;
    # get the number
    my $accurateMeters = $input / 100.0;
    # imperial
    my $inches = sigfig(3, $accurateMeters / (0.0254 * 1.0));
    my $feet = sigfig(3, $accurateMeters / (0.0254 * 12.0));
    my $yards = sigfig(3, $accurateMeters / (0.0254 * 36.0));
    my $miles = sigfig(3, $accurateMeters / (0.0254 * 63360.0));
    # metric
    my $kilometers = sigfig(3, $accurateMeters / 1000.0);
    my $meters = sigfig(3, $accurateMeters);
    my $centimeters = sigfig(3, $accurateMeters * 100.0);
    my $millimeters = sigfig(3, $accurateMeters * 1000.0);
    my $nanometers = sigfig(3, $accurateMeters * 1000000000.0);
    my $angstroms = sigfig(3, $accurateMeters * 10000000000.0);
    $self->say($event, "$event->{'from'}: ${centimeters}cm = ${inches}in, ${feet}ft, ${yards}yd, ${miles}mi; ${kilometers}Km, ${meters}m, ${millimeters}mm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}

sub convertMeters {
    my $self = shift;
    my($event, $input) = @_;
    # get the number
    my $accurateMeters = $input * 1.0;
    # imperial
    my $inches = sigfig(3, $accurateMeters / (0.0254 * 1.0));
    my $feet = sigfig(3, $accurateMeters / (0.0254 * 12.0));
    my $yards = sigfig(3, $accurateMeters / (0.0254 * 36.0));
    my $miles = sigfig(3, $accurateMeters / (0.0254 * 63360.0));
    # metric
    my $kilometers = sigfig(3, $accurateMeters / 1000.0);
    my $meters = sigfig(3, $accurateMeters);
    my $centimeters = sigfig(3, $accurateMeters * 100.0);
    my $millimeters = sigfig(3, $accurateMeters * 1000.0);
    my $nanometers = sigfig(3, $accurateMeters * 1000000000.0);
    my $angstroms = sigfig(3, $accurateMeters * 10000000000.0);
    $self->say($event, "$event->{'from'}: ${meters}m = ${inches}in, ${feet}ft, ${yards}yd, ${miles}mi; ${kilometers}Km, ${centimeters}cm, ${millimeters}mm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}

sub convertKilometers {
    my $self = shift;
    my($event, $input) = @_;
    # get the number
    my $accurateMeters = $input * 1000.0;
    # imperial
    my $inches = sigfig(3, $accurateMeters / (0.0254 * 1.0));
    my $feet = sigfig(3, $accurateMeters / (0.0254 * 12.0));
    my $yards = sigfig(3, $accurateMeters / (0.0254 * 36.0));
    my $miles = sigfig(3, $accurateMeters / (0.0254 * 63360.0));
    # metric
    my $kilometers = sigfig(3, $accurateMeters / 1000.0);
    my $meters = sigfig(3, $accurateMeters);
    my $centimeters = sigfig(3, $accurateMeters * 100.0);
    my $millimeters = sigfig(3, $accurateMeters * 1000.0);
    my $nanometers = sigfig(3, $accurateMeters * 1000000000.0);
    my $angstroms = sigfig(3, $accurateMeters * 10000000000.0);
    $self->say($event, "$event->{'from'}: ${kilometers}km = ${inches}in, ${feet}ft, ${yards}yd, ${miles}mi; ${meters}m, ${centimeters}cm, ${millimeters}mm, ${nanometers}nm, ${angstroms}Å (to 3sf)");
}


# Time

sub convertYears {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input * 60.0 * 60.0 * 24.0 * 365.25;
    my $years = sigfig(3, $input);
    my $months = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / 12)));
    my $weeks = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0))));
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${years}yr = ${months}mo, ${weeks}wk, ${days}d, ${hours}hr, ${minutes}min, ${seconds}s, ${millifortnights}mf");
}
     
sub convertMonths {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input * 60.0 * 60.0 * 24.0 * (365.25 / 12);
    my $years = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * 365.25));
    my $months = sigfig(3, $input);
    my $weeks = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0))));
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${months}mo = ${years}yr, ${weeks}wk, ${days}d, ${hours}hr, ${minutes}min, ${seconds}s, ${millifortnights}mf");
}

sub convertWeeks {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input * 60.0 * 60.0 * 24.0 * 7.0; 
    my $years = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * 365.25));
    my $months = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / 12)));
    my $weeks = sigfig(3, $input);
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${weeks}wk = ${years}yr, ${months}mo, ${days}d, ${hours}hr, ${minutes}min, ${seconds}s, ${millifortnights}mf");
}

sub convertMillifortnights {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input * 60.0 * 60.0 * 24.0 * 7.0 * 2.0 / 1000.0;
    my $years = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * 365.25));
    my $months = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / 12)));
    my $weeks = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0))));
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${millifortnights}mf = ${years}yr, ${months}mo, ${weeks}wk, ${days}d, ${hours}hr, ${minutes}min, ${seconds}s");
}

sub convertDays {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input * 60.0 * 60.0 * 24.0;
    my $years = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * 365.25));
    my $months = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / 12)));
    my $weeks = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0))));
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${days}d = ${years}yr, ${months}mo, ${weeks}wk, ${hours}hr, ${minutes}min, ${seconds}s, ${millifortnights}mf");
}

sub convertHours {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input * 60.0 * 60.0;
    my $years = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * 365.25));
    my $months = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / 12)));
    my $weeks = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0))));
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${hours}hr = ${years}yr, ${months}mo, ${weeks}wk, ${days}d, ${minutes}min, ${seconds}s, ${millifortnights}mf");
}

sub convertMinutes {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input * 60.0;
    my $years = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * 365.25));
    my $months = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / 12)));
    my $weeks = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0))));
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${minutes}min = ${years}yr, ${months}mo, ${weeks}wk, ${days}d, ${hours}hr, ${seconds}s, ${millifortnights}mf");
}

sub convertSeconds {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateSeconds = $input;
    my $years = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * 365.25));
    my $months = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / 12)));
    my $weeks = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0))));
    my $millifortnights = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0 * (365.25 / (365.25 / 7.0)) * 2.0 / 1000.0));
    my $days = sigfig(3, $accurateSeconds / (60.0 * 60.0 * 24.0));
    my $hours = sigfig(3, $accurateSeconds / (60.0 * 60.0));
    my $minutes = sigfig(3, $accurateSeconds / 60.0);
    my $seconds = sigfig(3, $accurateSeconds);
    $self->say($event, "$event->{'from'}: ${seconds}s = ${years}yr, ${months}mo, ${weeks}wk, ${days}d, ${hours}hr, ${minutes}min, ${millifortnights}mf");
}
     

# Mass

sub convertGrams {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateGrams = $input;
    my $grams = sigfig(3, $accurateGrams);
    my $kgs = sigfig(3, $accurateGrams / 1000.0);
    my $ounces = sigfig(3, $accurateGrams * 0.03527);
    my $pounds = sigfig(3, $accurateGrams * 0.002205);
    my $stones = sigfig(3, $accurateGrams * 0.00016);
    $self->say($event, "$event->{'from'}: ${grams}g = ${kgs}kg, ${ounces}oz, ${pounds}lbs, ${stones}stone");
}

sub convertKilograms {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateGrams = $input * 1000.0;
    my $grams = sigfig(3, $accurateGrams);
    my $kgs = sigfig(3, $input);
    my $ounces = sigfig(3, $accurateGrams * 0.03527);
    my $pounds = sigfig(3, $accurateGrams * 0.002205);
    my $stones = sigfig(3, $accurateGrams * 0.00016);
    $self->say($event, "$event->{'from'}: ${kgs}kg = ${grams}g, ${ounces}oz, ${pounds}lbs, ${stones}stone");
}
     
sub convertPounds {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateGrams = $input * 453.6;
    my $grams = sigfig(3, $accurateGrams);
    my $kgs = sigfig(3, $accurateGrams / 1000.0);
    my $ounces = sigfig(3, $accurateGrams * 0.03527);
    my $pounds = sigfig(3, $input);
    my $stones = sigfig(3, $accurateGrams * 0.00016);
    $self->say($event, "$event->{'from'}: ${pounds}lbs = ${grams}g, ${kgs}kg, ${ounces}oz, ${stones}stone");
}
     
sub convertOunces {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateGrams = $input * 28.35;
    my $grams = sigfig(3, $accurateGrams);
    my $kgs = sigfig(3, $accurateGrams / 1000.0);
    my $ounces = sigfig(3, $input);
    my $pounds = sigfig(3, $accurateGrams * 0.002205);
    my $stones = sigfig(3, $accurateGrams * 0.00016);
    $self->say($event, "$event->{'from'}: ${ounces}oz = ${grams}g, ${kgs}kg, ${pounds}lbs, ${stones}stone");
}
     
sub convertStones {
    my $self = shift;
    my($event, $input) = @_;
    my $accurateGrams = $input * 6350.3;
    my $grams = sigfig(3, $accurateGrams);
    my $kgs = sigfig(3, $accurateGrams / 1000.0);
    my $ounces = sigfig(3, $accurateGrams * 0.03527);
    my $pounds = sigfig(3, $accurateGrams * 0.002205);
    my $stones = sigfig(3, $accurateGrams * 0.00016);
    $self->say($event, "$event->{'from'}: ${stones}stone = ${grams}g, ${kgs}kg, ${ounces}oz, ${pounds}lbs");
}

     
# Utility Functions

sub round($$) {
    return sprintf("%.*f", @_);
}

sub sigfig($$) {
    my($sf, $float) = @_;
    my $length = length(int($float));
    if ($length == $sf) {
        $float = int($float);
    } elsif ($length > $sf) {
        my $factor = (10 ** ($length - $sf));
        $float = int($float / $factor) * $factor;
    } else {
        my $factor = 0;
        while (length(int($float * 10 ** $factor)) < $sf) {
            $factor++;
        }
        $float = int($float * 10 ** $factor) / (10 ** $factor);
    }
    $float = sprintf("%g", $float);
    $float =~ s/e(?:\+|(-))0*/x10^$1/os;
    return $float;
}