#!/usr/bin/perl -w

use strict;
package Prefix;

########################################

# prefix handling
my %pfx = (
    y => -24,
    z => -21,
    a => -18,
    f => -15,
    p => -12,
    n => -9,
    u => -6,
    m => -3,
    c => -2,
    d => -1,
    "" => 0,
    da => 1,
    h =>  2,
    k =>  3,
    M =>  6,
    G =>  9,
    T => 12,
    P => 15,
    E => 18,
    Z => 21,
    Y => 24,
);

# cached values:
my %pfx_val; # 10 ** pfx{"y"} etc.
my @pfx; # ordered list of prefixes
my %pfx_eng; # = %pfx, but just thoose prefixes divisible by 3 (skip c, d, da and h)
my @pfx_eng;

sub pfx_sort() { { $pfx{$a} <=> $pfx{$b} } }

INIT {
    my @k = keys %pfx;

    for my $k (@k) { $pfx_val{$k} = 10 ** $pfx{$k}; }

    @pfx = sort pfx_sort @k;

    for my $k (@k) {
	my $v = $pfx{$k};
	if ($v%3 == 0) {
	    $pfx_eng{$k} = $v;
	}
    }

    @pfx_eng = sort pfx_sort keys %pfx_eng;
}
sub pfx_show() {
    print "\@pfx     = ( \"", join("\", \"", @pfx    ), "\" );\n";
    print "\@pfx_eng = ( \"", join("\", \"", @pfx_eng), "\" );\n";
    print "\%pfx_eng = (\n";
    for my $k (sort pfx_sort keys %pfx_eng) {
	my $v = $pfx_eng{$k};
	if ($k eq "") { $k = '""'; }
	else { $k .= " "; }
	print " $k => $v,\n";
    }
    print ");\n";
}

sub pfx_find($) {
    my $val = shift;

    my $str = $val;
    my $neg = 1;
    if (!defined($val) || $val eq "") { return undef; }
    if ($val < 0) {
	$neg = -1;
	$val = - $val;
    }

    my $sz = 1;
    my $pfx = "";
    if ($val == 0) { return [ $neg, $val, $pfx ]; }

    my $ix;
    for ($ix = 0; $ix < @pfx_eng; $ix++) {
	$pfx = $pfx_eng[$ix];
	$sz = $pfx_val{$pfx};
	#print "$val / $pfx / $sz\n";
	if ($val < $sz * 0.9995) {
	    #print "fin\n";
	    last;
	}
    }
    if ($ix > 0) { $ix--; }
    $pfx = $pfx_eng[$ix];
    $sz = $pfx_val{$pfx};

    [ $neg, $val/$sz, $pfx ];
}

sub println($$$@) {
    my $hdr  = shift;
    my $ref = shift;
    my $sep = shift // "";
    my @list = @_;
    my $line = "";

    while (@list > 1) {
	my $k = shift @list;
	my $u = shift @list;
	my $str = "";

	if ($hdr) {
	    $hdr = sprintf(" %-5s = ", $k);
	} else {
	    $hdr = "";
	}


	for my $data (@$ref) {
	    if ($str) {
		$str .= ": ";
	    }
	    if ($u eq '-f') {
		$str .= sprintf("%8.3f$sep    ", $$data{$k});
	    } elsif ($u eq '-e') {
		$str .= sprintf("%12.3e$sep", $$data{$k});
	    } elsif ($u eq '-s') {
		$str .= sprintf("%-8s$sep    ", $$data{$k});
	    } else {
		$str .= pfx_en($$data{$k}, "$u");
	    }
	}
	$line .= "$hdr$str"
    }
    printf "$line\n";
}

sub pfx_de($) {
    my $str = shift;

    return $str unless (defined($str));
    if ( $str =~ m/^([0-9.+-]+([eE][+-]?\d+)?) ?([a-zA-Z]+)?$/ ) {
	my $val = $1;
	my $exp = $pfx{$3 // ""};
	if (!defined($exp)) {
	    warn("illegal unit prefix in \"$str\"");
	    $exp = 0;
	}

	return $val * 10 ** $exp;
    } else {
	return $str;
    }
}
sub decode_arr($) {
    my $data = shift;

    for (my $ix = 0; $ix < @$data; $ix++) {
	my $val = $$data[$ix];
	next unless (defined($val));
	$$data[$ix] = pfx_de($val);
    }
    $data;
}
sub decode_hash($) {
    my $ref = shift;

    my $k;
    my $v;

    if ($$ref{pfx_show}) {
	Prefix::pfx_show();
    };

    for $k (sort keys %$ref) {
	$v = $$ref{$k};
	my $e = Prefix::pfx_de($v);
	if ($$ref{pfx_show}) {
	    if (defined($v)) {
		printf "%10s = %10s %s\n", $k, $v ,$e;
	    } else {
		printf "%10s = %10s\n", $k, "undef";
	    }
	}
	$$ref{$k} = $e;
    }

    if ($$ref{pfx_show}) {
	exit(0);
    }

}

sub pfx_en($;$$) {
    my $val1 = shift;
    my $unit = shift // "";
    my $fmt = shift // " %-4s";

    my $str = $val1;

    my $r = pfx_find($val1);
    if (!defined($r)) {
	$str = sprintf("%8s$fmt", "undef", "");
	return $str;
    }
    my $full_format = "%8.3f$fmt";
    my ( $neg, $val, $pfx ) = @$r;
    my $suf = $pfx . $unit;
    if ($val == 0 || 0.010 < $val && $val < 1000) {
	$str = sprintf($full_format, $neg * $val, $suf);
    } else {
	$str = sprintf($full_format, $val1, " ");
    }
    return $str;
}
sub encode_arr($;$$) {
    my $data = shift;
    my $unit = shift;
    my $fmt = shift;

    for (my $ix = 0; $ix < @$data; $ix++) {
	my $val = $$data[$ix];
	next unless (defined($val));
	$$data[$ix] = pfx_en($val, $unit, $fmt);
    }
}
sub encode_hash($;$$) {
    my $ref = shift;
    my $unit = shift;
    my $fmt = shift;

    my $k;
    my $v;
    for $k (sort keys %$ref) {
	$v = $$ref{$k};
	my $e = pfx_en($v, $unit, $fmt);
	$$ref{$k} = $e;
    }
}

1;
