#!/usr/bin/perl

use integer;

# Utworzenie pewnych prywatnych, trwaych zmiennych.
my @liczby_pierwsze     = ( 2, 3, 5 );
my @wielokrotnosci      = ( 4, 9, 25 );
my $ostatnia_liczba     = 5;

# @sterta jest sterta indeksow dla tablic @liczby_pierwsze i @wielokrotnosci.
# Zindeksowana wartosc @wielokrotnosci jest uzywana do sortowania sterty.
# W czasie kolejnych testow zawsze pomijane sa liczby parzyste i wielokrotnosci 3,
# przez co tablica @sterta rozpoczyna sie od 2  indeksu dla liczby pierwszej 5.
my @sterta = ( 2 );

# $ostatni_przyrost pozwala na pominiecie liczb parzystych i wielokrotnosci 3.
# Ta zmienna jest zawsze zwiekszana o 2 lub 4. Poniewaz pierwsza wartosc 
# $ostatni_przyrost jest nieparzysta(5), to i kolejne wartosci sa nieparzyste.
# Nigdy nie  sa uzywane takze wielokrotnosci 3 w dowolnej grupie 6.  Pierwsze
# wartosci to:  5+2 -> 7, 7+4 -> 11
my $ostatni_przyrost = 4;

# Binarne przeszukanie tablicy @liczby_pierwsze w celu odnalezienia indeksu
# najwiekszej liczby pierwszej, ktora jest <= argumentowi.
  sub _bin_pierwsza {
my ($n, $dolna, $gorna) = (shift, 0, $#liczby_pierwsze);
while ( $dolna < $gorna ) {
my $srodkowa = ($dolna+$gorna+1) / 2;
                if ( $liczby_pierwsze[$srodkowa] > $n ) {
                    $gorna = $srodkowa -1
                } else {
                    $dolna = $srodkowa;
                }
         }
         return $dolna;
}

# @sterta jest sterta indeksow dla @liczby_pierwsze i @wielokrotnosci.
# @sterta jest posortowana wedlug zindeksowanej wartosci @wielokrotnosci.
# Pierwszy element zostal wlasnie zwiekszony i moze zajmowac niewlasciwe miejsce.
sub _sterta_dol {
my $is = 0;                     # indeks sterty
my $il = $sterta[0];            # indeks liczby pierwszej
my $wp = $wielokrotnosci[$ip];  # wartosc liczby pierwszej
my $ip;                         # indeks potomka
while ( ($ip = $is*2 + 1) < @sterta ) {
++$ip
             if ($ip < $#liczby_pierwsze)
             && ($wielokrotnosci[$sterta[$ci+1]] < $wielokrotnosci[$sterta[$ci]]);
last unless $wielokrotnosci[$sterta[$ip]] < $wp;
$sterta[$is] = $sterta[$ip];
$is = $ip;
    }
    $sterta[$is] = $il;
}

# Zwrocenie wszystkich liczb pierwszych az do $n
#   oraz indeksu najwiekszej liczby pierwszej, ktora jest <= $n.
sub liczby_pierwsze {
    my $n = shift;

    # Male liczby nie sa liczbami pierwszymi ani zlozonymi.
    return (\@liczby_pierwsze, undef) if $n < 2;

    # Jesli wczesniej wygenerowano wystarczajaco duzo liczb pierwszych,
    # to nalezy zwrocic aktualna tablice i wlasciwy indeks.
    return (\@liczby_pierwsze, _bin_pierwsza($n) ) if $n < $ostatnia_liczba;

    # W przeciwnym wypadku nalezy rozszerzyc liste liczb pierwszych.
    while ( $n > $ostatnia_liczba ) {
        # Wygenerowanie kolejnej liczby pierwszej, przeskok o 2 lub 4 oraz pominiecie
        # wszystkich liczb parzystych oraz nieparzystych wielokrotnosci 3.
        $ostatnia_liczba += ($ostatni_przyrost = 6-$ostatni_przyrost);
        my $sterta;
        while ( $ostatnia_liczba > $wielokrotnosci[$sterta=$sterta[0]] ) {
            # Przemieszczenie wszystkich liczb pierwszych, ktorych kolejna
            # wielokrotnosc zostala przekazana.
            $wielokrotnosci[$sterta] += 2 * $liczby_pierwsze[$sterta];
            _sterta_dol();
        }
        # Pominiecie tego kandydata, jesli jest wielokrotnoscia.
        next if $ostatnia_liczba == $wielokrotnosci[$sterta];
        # $ostatnia_liczba jest kolejna liczba pierwsza.
        push ( @liczby_pierwsze, $ostatnia_liczba );
        # Pierwsza sprawdzana wielokrotnosc to pierwiastek kwadratowy 
        # (wszystkie mniejsze liczby sa rowniez wielokrotnosciami innych mniejszych 
        # liczb pierwszych).
        push ( @wielokrotnosci, $ostatnia_liczba*$ostatnia_liczba );
        # Dodanie liczby do sterty. Ta liczba musi byc wieksza od wszystkich
        # innych elementow sterty, przez co jest potrzebna funkcja 
        # _sterta_gora.

        push ( @sterta, $#liczby_pierwsze );
    }

    # Dalsze wyszukiwanie nie jest potrzebne. Zwracana wartosc moze byc
    # indeksem ostatniego elementu (jesli odpowiada on elementowi docelowemu,
    # ktory jest liczba pierwsza) lub indeksem przedostatniego elementu
    # (jesli ostatni element jest wiekszy niz element docelowy; zdarza sie
    # to w przypadku, gdy element docelowy jest liczba zlozona). 
    # Z tego powodu nalezy ustalic, czy konieczne jest odjecie 1
    # od indeksu ostatniego elementu:
    return (\@liczby_pierwsze, $#liczby_pierwsze - ($n != $ostatnia_liczba));
}


# $wartosc = liczba_pierwsza($n) zwraca $n-ta liczbe pierwsza.
# liczba_pierwsza(0) to 2, liczba_pierwsza(1) to 3, 
# liczba_pierwsza(2) to 5 itd.
sub liczba_pierwsza {
    my $n = shift;
    # Nalezy upewnic sie, czy odnaleziono juz $n+1 liczb pierwszych.
    while ( $n >= @liczby_pierwsze ) {
        # $n - $#liczby_pierwsze to liczba zadanych liczb pierwszych,
        # dzieki czemu najwieksza odnaleziona liczba bedzie co najmniej
        # dwa razy wieksza niz dotychczasowa najwieksza liczba.
        liczby_pierwsze( $ostatnia_liczba + 2*($n - $#liczby_pierwsze) );
    }
    return $liczby_pierwsze[$n];
}


# $num = koduj( $n1, $n2, ... )
sub koduj {
    my $indeks  = 0;
    my $wynik = 1;
    while ( @_ ) {
        $wynik *= liczba_pierwsza($indeks++) ** (shift);
    }
}

# ($n1,$n2, ...) = dekoduj( $num );
sub dekoduj {
    my ($num, $indeks, @wynik) = (shift, 0);

    while ( $num > 1 ) {
        use integer;
        my $liczba_pierwsza = liczba_pierwsza($indeks++);
        my $dzielnik = 0;
        until ( $num % $liczba_pierwsza ) {
            $num /= $liczba_pierwsza;
            ++$dzielnik;
        }
        push @wynik, $dzielnik;
        $num     -= $dzielnik * $liczba_pierwsza;
    }
}

# Sprawdzenie czy $n jest liczba pierwsza poprzez przeprowadzenie
# pieciu losowych testow.
sub czy_pierwsza {
    use integer;
    my $n  = shift;
    my $n1 = $n - 1;
    my $jeden = $n - $n1;     # 1, ale zapewnia to wlasciwy typ liczby.

    my $swiadek = $jeden * 100;

    # obliczenie potegi 2 dla najwyzszego bitu $n1.
    my $p2 = $jeden;
    my $p2indeks = -1;
    ++$p2indeks, $p2 *= 2
        while $p2 <= $n1;
    $p2 /= 2;

    # liczba iteracji: 5 dla 260-bitowej liczby,
    # 25 dla znacznie mniejszych liczb.
    my $ostatni_swiadek = 5;
    $ostatni_swiadek += (260 - $p2indeks)/13 if $p2indeks < 260;

    for $licznik_swiadkow ( 1..$ostatni_swiadek) {
        $swiadek *= 1024;
        $swiadek += rand(1024);
        $swiadek = $swiadek % $n if $swiadek > $n;
        $swiadek = $one * 100, redo if $swiadek == 0;

        my $iloczyn = $jeden;
        my $n1bity = $n1;
        my $p2nastepny = $p2;

        # obliczenie $swiadek ** ($n - 1).
        while (1) {
            # Czy $iloczyn, aktualna potega, jest pierwiastkiem kwadratowym 1?
            # (plus lub minus 1)
            my $pierwiastek = $iloczyn == 1 || $iloczyn == $n1;

            $iloczyn = ($iloczyn * $iloczyn) % $n;

            # Dodatkowy pierwiastek 1 potwierdza brak liczby pierwszej.
            return 0 if $iloczyn == 1 && !$pierwiastek;

            if ( $n1bity >= $p2nastepny ) {
                $iloczyn = ($iloczyn * $swiadek) % $n;
                $n1bity -= $p2nastepny;
            }
            last if $p2nastepny == 1;
            $p2nastepny /= 2;
        }
        return 0 unless $iloczyn == 1;
    }
    return 1;
}

