#!/usr/bin/perl

# $wynik = dzielenie_modulo( $n, $d, $p ) wykorzystuje dzielenie modulo
# do uzyskania wyniku ($wyniku * $d) == $n (mod $p), gdzie $p jest liczba pierwsza.
sub dzielenie_modulo {
    use integer;
    my ( $n, $d, $p ) = @_;
    my @nwd = nwd_liniowy( $d, $p );
    my $odwrotnosc = ($n * $nwd[1]) % $p;
    $odwrotnosc += $p if $odwrotnosc < 0;  # uzwglednienie liczby ujemnej
    return $odwrotnosc;
}

print dzielenie_modulo( 1, 3, 5 ), "\n";  # wyswietla 2

my @bases;
my @inverses;
my $prod;

# 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);
    }
}

