#!/usr/bin/perl -w

use strict;

package Eseries;
use POSIX;

use Exporter 5.57 'import';
our @EXPORT_OK = qw(%E @E3 @E6 @E12 @E24 @E48 @E96 @E192 @e24 @e12 @e6 @e3 @ERAV @RT);

##########
# component values (e.g. resistance, capacitance, inductance) are usually made with
# values approx. equal to a geometric serie spanning a decade, i.e.
# first value is 10**d and last/first value < 10
# See https://en.wikipedia.org/wiki/E_series_of_preferred_numbers


##########
# A mathimatically correct E-serie rounded to $digits digits
sub Eseries($$) {
    # calculate a geometric series with $num entries spanning a decade
    # starting with 10 ** $digits and where the last + 1 entry should be  10 ** $digits+1
    my $num = shift;
    my $digits = shift;

    my $k = M_LN10 / $num;
    my $scale = 10 ** $digits;
    my @arr = ();

    for (my $ix = 0; $ix < $num; $ix++) {
	push @arr, int(exp($k * $ix) * $scale + 0.5);
    }

    @arr;
}

########## Some decade series
# the "official" E-series E3/6/12/24/48/96/192
sub sub_arr($@);
our @E24 = (
    10, 11, 12, 13,
    15, 16, 18, 20,
    22, 24, 27, 30,
    33, 36, 39, 43,
    47, 51, 56, 62,
    68, 75, 82, 91
);
our @E12 = sub_arr(2,@E24);
our @E6  = sub_arr(4,@E24);
our @E3  = sub_arr(8,@E24);

our @E192 = (
    100, 101, 102, 104, 105, 106, 107, 109,
    110, 111, 113, 114, 115, 117, 118, 120,
    121, 123, 124, 126, 127, 129, 130, 132,
    133, 135, 137, 138, 140, 142, 143, 145,
    147, 149, 150, 152, 154, 156, 158, 160,
    162, 164, 165, 167, 169, 172, 174, 176,
    178, 180, 182, 184, 187, 189, 191, 193,
    196, 198, 200, 203, 205, 208, 210, 213,
    215, 218, 221, 223, 226, 229, 232, 234,
    237, 240, 243, 246, 249, 252, 255, 258,
    261, 264, 267, 271, 274, 277, 280, 284,
    287, 291, 294, 298, 301, 305, 309, 312,
    316, 320, 324, 328, 332, 336, 340, 344,
    348, 352, 357, 361, 365, 370, 374, 379,
    383, 388, 392, 397, 402, 407, 412, 417,
    422, 427, 432, 437, 442, 448, 453, 459,
    464, 470, 475, 481, 487, 493, 499, 505,
    511, 517, 523, 530, 536, 542, 549, 556,
    562, 569, 576, 583, 590, 597, 604, 612,
    619, 626, 634, 642, 649, 657, 665, 673,
    681, 690, 698, 706, 715, 723, 732, 741,
    750, 759, 768, 777, 787, 796, 806, 816,
    825, 835, 845, 856, 866, 876, 887, 898,
    909, 920, 931, 942, 953, 965, 976, 988
);
our @E96 = sub_arr( 2,@E192);
our @E48 = sub_arr( 4,@E192);

# and some possible others
our @e24 = sub_arr( 8,@E192);
our @e12 = sub_arr(16,@E192);
our @e6  = sub_arr(32,@E192);
our @e3  = sub_arr(64,@E192);

########## Some list of values
# Not any E seires
our @ERAV = (
    49.9,
    100, 150, 200, 220, 270, 330, 390, 470, 560,
    1000, 1200, 1430, 1910, 2400, 2490, 2700, 3240, 4020, 4300, 4700, 7500, 9100, 9530,
    11000, 13000, 15000, 20000, 22000, 24000, 24900, 27000, 30000, 31600, 33000, 36000, 43000, 49900, 51000,
    75000, 82000,
    100000, 110000, 120000, 150000, 160000, 200000, 220000
);
our @RT = (
    1000, 1020, 1100, 1200, 1500, 1800, 18200, 2000, 2200, 2490, 2700, 2740, 3000, 3010, 3300, 3320, 3920, 4020, 4640,
    4700, 4990, 5100, 5110, 5600, 5620, 6200, 6490, 6800, 6810, 7500, 8060, 8200, 8250,
    10000, 10200, 11000, 12000, 12100, 16500, 18000, 20000, 20500, 22000, 22100, 24000, 249000, 26700, 27000, 28400,
    30000, 30100, 33000, 33200, 38300, 39000, 40200, 41200, 47000, 47500, 49900, 50000, 62000, 7000, 76800, 82000, 91000,
    100000, 110000, 150000, 180000, 200000, 220000, 300000, 332000, 470000,
);
our %E = (
    E3   => \@E3,
    E6   => \@E6,
    E12  => \@E12,
    E24  => \@E24,
    E48  => \@E48,
    E96  => \@E96,
    E192 => \@E192,
    e24  => \@e24,
    e12  => \@e12,
    e6   => \@e6,
    e3   => \@e3,
    ERAV => \@ERAV,
    RT   => \@RT,
);

##########

sub type(@) {
    # is the serie contained within a decade or
    # is it just some range of values

    # it should be sorted beforehand

    # returns:
    #  number of digits in the serie if it is a decade serie
    #  undef else
    my @E = @_;

    if (@E < 1) { return undef; }

    my $min = $E[0];
    my $max = $E[$#E];

    if ($min <= 0) { return undef; }
    if ($max / $min >= 10) { return undef; }

    my $ee = POSIX::floor(POSIX::log10($min));
    return $ee;
}

sub sort_numbers(@) {
    # a given serie should be sorted numerically
    sort { $a <=> $b } @_;
}

sub sub_arr($@) {
    # given a series @arr, return every $n'th entry
    my $n = shift;
    my @arr = @_;
    my @ix = map {$_ *= $n; } (0 .. @arr/$n);
    pop @ix;
    @arr[@ix];
}

sub print_arr($$@) {
    # print out an array, use $n etnries per line
    my $n = shift;
    my $str = shift;
    my @arr = @_;

    print "$str = (";
    for (my $ix = 0; $ix < @arr; $ix++) {
	if ($ix % $n == 0) {
	    print "\n ";
	}
	print " ", $arr[$ix], ",";
    }
    print "\n);\n";
}

sub show() {
    # print out the "officieal" E-series
    print_arr(6,"E3"   , @E3);
    print_arr(6,"E6"   , @E6);
    print_arr(12,"E12" , @E12);
    print_arr(12,"E24" , @E24);

    print_arr(16,"E48" , @E48);
    print_arr(16,"E96" , @E96);
    print_arr(16,"E192", @E192);

    # and my non-official ones
    print_arr(12,"e3"  , @e3);
    print_arr(12,"e6"  , @e6);
    print_arr(12,"e12" , @e12);
    print_arr(12,"e24" , @e24);
}

sub find(;$) {
    # find a E-serie by name or number
    my $str = shift;
    if (!defined($str) || $str eq "") {
	return @E6;
    }
    my $Eref = $E{$str};
    if (defined($Eref)) { return @$Eref; }
    else { return (); };
}

sub ExpandLimit($$@) {
    # fill out the range $min to $max with numbers from
    # for the given serie @E
    my $min = shift;
    my $max = shift;
    my @E = sort_numbers(@_);

    my $digits = type(@E);
    if (!defined($digits)) { # list of values
	@E = grep { $min <= $_ && $_ <= $max } @E;
	return @E;
    }

    # decade serie
    my $emin = POSIX::floor(POSIX::log10($min));
    my $scale = 10 ** ($emin - $digits);

    my @arr;
    while (@arr < 1 || $arr[$#arr] < $max) {
	push @arr, map { $scale * $_ } @E;
	$scale *= 10;
    }

    while (@arr && $arr[0] < $min) { shift @arr; }
    while (@arr && $max < $arr[$#arr]) {
	pop @arr;
    }

    @arr;
}

sub nearest($@) {
    # find the value in the list of values (@E) that is closest to the
    # given value ($val)
    my $val = shift;
    my @E = @_; # array should be already sorted

    if ($val <= 0 || @E < 1) { return undef; }
    my $min = $E[0];
    my $max = $E[$#E];

    my @e = map { POSIX::log10($_); } @E;
    my $digits = POSIX::floor($e[0]);
    @e = map { $_ - $digits } @e;

    my $vallog = POSIX::log10($val);
    my $valdig = POSIX::floor($vallog);
    $vallog -= $valdig;

    my $ix;
    my $ix_closest = -1;
    my $ix_nxt;
    my $ix_prv;

    if ($vallog < $e[0]) {
	$ix_prv = -1;
	$ix_nxt = 0;
	if ( abs($vallog - $e[0]) <=  abs($vallog - ($e[$#e]-1)) ) {
	    $ix_closest = 0;
	} else {
	    $ix_closest = -1;
	}

    } elsif ($e[$#e] < $vallog) {
	$ix_prv = $#e;
	$ix_nxt = $#e+1;

	if ( abs($vallog - $e[$#e]) <=  abs($vallog - ($e[0]+1)) ) {
	    $ix_closest = $#e;
	} else {
	    $ix_closest = $#e+1;
	}

    } else {
	for ($ix = 0; $ix < @e; $ix++) {
	    if ($vallog == $e[$ix]) {
		$ix_prv = $ix - 1;
		$ix_nxt = $ix + 1;
		$ix_closest = $ix;
		last;
	    } elsif ($vallog < $e[$ix]) {
		$ix_prv = $ix - 1;
		$ix_nxt = $ix;

		if ( abs($vallog - $e[$ix_prv]) <=  abs($vallog - $e[$ix_nxt]) ) {
		    $ix_closest = $ix_prv;
		} else {
		    $ix_closest = $ix_nxt;
		}

		last;
	    }
	}
    }

    my @ix = ($ix_prv, $ix_closest, $ix_nxt);
    my @val;
    my @lg;
    for ($ix = 0; $ix < @ix; $ix++) {
	if ($ix[$ix] < 0) {
	    $val[$ix] = $E[$#E] * 10 ** ($valdig-1-$digits);
	    $lg[$ix] = $e[$#e] - 1;
	} elsif ($ix[$ix] <= $#e) {
	    $val[$ix] = $E[$ix[$ix]] * 10 ** ($valdig-$digits);
	    $lg[$ix] = $e[$ix[$ix]];
	} else {
	    $val[$ix] = $E[0] * 10 ** ($valdig+1-$digits);
	    $lg[$ix] = $e[0] + 1;
	}
    }
    @val;
}

1;
