#!/usr/bin/perl

sub chinska_teoria {
    use integer;
    my ($dzielniki, $reszty) = @_;
    my ($i, $j);
  DZIELNIK: for ($i = 0; $i < nww(@$dzielniki); $i++) {
          for ($j = 0; $j < @$dzielniki; $j++) {
               next DZIELNIK unless $i % $dzielniki->[$j] == $reszty->[$j];
          }
          return $i;
    }
    return;
}

print "W koszyku jest ", chinska_teoria( [2..7], [1,1,1,1,1,0] ), " jaj.\n";

# $nww = nww( @liczby ) oblicza najmniejsza wspolna wielokrotnosc dla @liczby.
#
sub nww {
    use integer;
    my $nww = shift;
    foreach (@_) { $nww *= $_ / nwd($_, $nww) }
    return $nww;
}

sub nwd {
    use integer;
    my $nwd = shift || 1;
    while (@_) {
        my $kolejny = shift;
        # ($nwd, $kolejny) = ($kolejny, $nwd % $kolejny) while $kolejny;
        while( $kolejny ) {
            my $r = $nwd % $kolejny;
            $r += $kolejny if $r < 0;   # poprawka dla % w trybie integer 
            $nwd = $kolejny;
            $kolejny = $r;
        }
    }
    return $nwd;
}

# Zachowanie listy modulo.
sub ustaw_dzielniki {
    use integer;
    @dzielniki = @_;
    $iloczyn  = 1;

    foreach (@_) {

        # Nalezy upewnic sie, czy wszystkie podstawy sa wobec siebie liczbami
        # pierwszymi, co jest wymagane przez teorie:
        die 'to nie sa liczby pierwsze' unless nwd($_, $iloczyn) == 1;

        # wszystko w porzadku, mozna dodac do iloczynu
        $iloczyn *= $_;
    }
    @odwrotnosci = map {
                          my $k = $iloczyn / $_;
                          $k * odwrotnosc_modulo( $k, $_ );
                    } @_;
}

# Przeksztalcenie listy reszt do liczby calkowitej.
sub do_calkowitych {
    use integer;
    my $v = shift;
    my $t = 0;
    for (0..$#dzielniki) {
        $t += $odwrotnosci[$_] * $v->[$_];
    }
    return $t % $iloczyn;
}

# Przeksztalcenie liczby calkowitej do listy reszt.
sub do_reszt {
    use integer;
    my $v = shift;
    my @v = map { $v%$_ } @dzielniki;
    return \@v;
}

ustaw_dzielniki(3, 4, 5, 7);
print "W koszyku jest ", do_calkowitych( [1,1,1,0] ), " jaj.\n";     # wyswietla 301

# Dodaje dwie listy reszt.
sub dodaj_reszty {
    use integer;
    my ($v1, $v2) = @_;

    my @v = map
            { ($v1->[$_] + $v2->[$_]) % $dzielniki[$_] }
        0 .. $#dzielniki;

    return \@v;
}

# Odnalezienie $wynik = odwrotnosc_modulo( $k, $n ) takiego ze:
#    ($k * $wynik) to 1 (mod $n)
sub odwrotnosc_modulo {
    use integer;
    my ( $k, $n ) = @_;
    my ( $d, $kf, $nf ) = nwd_linowy( $k, $n );

    # $d == $kf*$k + $nf*$n == ($kf*$k mod $n)
    return 0 unless $d == 1;
    $kf %= $n;
    $kf += $n if $kf < 0;

    return $kf;
}


# ( $nwd, $dzielnika, $dzielnikb ) = nwd_liniowy( $a, $b )
# Oblicza najwiekszy wspolny dzielnik $a i $b,
# a takze $dzielnika i $dzielnikb takie ze
#           $nwd == $a * $dzielnika + $b * $dzielnikb

sub nwd_liniowy {
    use integer;

    my ( $a, $b ) = @_;

    # Jesli jedna ze zmiennych jest zerem, to druga jest NWD.
    return ( $a, 1, 0 ) unless $b;
    return ( $b, 0, 1 ) unless $a;

    my ( $x1, $x2, $y1, $y2 ) = ( 0, 1, 1, 0 );

    # Jesli oryginalne wartosci $a i $b zostana nazwane A i B, 
    # to nastepujace relacje zostana zachowane dla kazdej iteracji:
    #                               $a == A * $x2 + B * $y2
    #                               $b == A * $x1 + B * $y1

    while ( 1 ) {
        # Nalezy obliczyc iloraz i reszte.
        my ( $q, $r );
        $r = $a % $b;
        # int % moze spowodowac bledy; nalezy to naprawic.
        $r += $b if $r < 0;
        $q = ($a - $r)/$b;

        # Jesli reszta ma wartosc zero, to $b zawiera NWD.
        # Zgodnie z wczesniej przedstawionymi relacjami:
        # $b == A * $x1 + B * $y1.
        return ( $b, $x1, $y1 ) unless $r;

        # Jesli reszta nie ma jeszcze wartosci zero, to nalezy
        # wykonac kolejna iteracje z zachowaniem relacji.

        ($a, $b)   = ($b, $r);
        ($x1, $x2) = ($x2 - $q*$x1, $x1);
        ($y1, $y2) = ($y2 - $q*$y1, $y1);
    }
}

