# -*- Mode: Python -*-

use strict;
use warnings;

use Test::More 'no_plan';
use Acme::Pythonic debug => 0;

use integer

# ----------------------------------------------------------------------

# $i ** $j (mod $n)
sub exp_mod:
    my ($i, $j, $n) = @_
    my $r = 1
    while $j:
        if $j % 2:
            $r = $i*$r % $n
        $j >>= 1
        $i = $i**2 % $n
    return $r

is exp_mod(3, 2, 7), 2
is exp_mod(7, 2, 43), 6
is exp_mod(9, 4, 6561), 0

# ----------------------------------------------------------------------

sub gcd:
    my ($a, $b) = @_
    my $r
    do:
        ($a, $b) = ($b, $a % $b)
    while $b
    return $a

is gcd(12, 1), 1
is gcd(1, 12), 1
is gcd(21, 12), 3
is gcd(49, 91), 7

# ----------------------------------------------------------------------

sub is_prime:
    my $n = shift
    return 1 if $n <= 3
    return 0 unless $n % 2

    my $a = int(rand($n - 3)) + 2
    return 0 if gcd($a, $n) > 1

    # find the greatest odd divisor of $n - 1
    # $n - 1 = 2^$k*$x is an invariant
    my $x = $n - 1 # even
    my $k = 0
    do:
        $x /= 2
        ++$k
    until $x % 2

    my $b = exp_mod($a, $x, $n)
    return 1 if $b == 1

    my $c = $b
    for 1..$k:
        $b = exp_mod($b, 2, $n)
        if $b == 1:
            last
        else:
            $c = $b

    return 0 if $b != 1

    my $d = gcd($c + 1, $n)
    return $d == 1 || $d == $n ? 1 : 0


is is_prime(2), 1
is is_prime(3), 1
is is_prime(16), 0
is is_prime(99), 0
is is_prime(647), 1
is is_prime(4900), 0
is is_prime(7919), 1


sub eratostenes:
    my $num = shift 
    $num or die 'Dona\'m un enter, si us plau'

    # creo les dues llistes per emmagatzemar els
    # primers i els nombres
    my @primers
    my @nombres
    
    # afegeixo a la llista cadascun dels nombres
    for my $i in 2..$num:
	   push(@nombres, $i)
	
    push(@primers, shift(@nombres))

    # mentres el quadrat del nombre mŽs gran
    # dels primers sigui mŽs petit que el nombre
    # mŽs gran de la llista, esborra els mœltiples
    # del primer mŽs gran a la llista de nombres
    while @nombres && ($primers[-1] ** 2) <= $nombres[-1]:
        @nombres = grep:
	        $_ % $primers[-1]
	    @nombres
	   push(@primers, shift(@nombres))


    return join(',', @primers, @nombres)
    
is eratostenes(2), "2"
is eratostenes(3), "2,3"
is eratostenes(5), "2,3,5"
is eratostenes(6), eratostenes(5)
is eratostenes(49), "2,3,5,7,11,13,17,19,23,29,31,37,41,43,47" 
is eratostenes(50), eratostenes(49)

# ----------------------------------------------------------------------

sub bubblesort:
    my $array = shift
    for my $i = $#$array; $i; $i--:
        for my $j = 1; $j <= $i; $j++:
            if $array->[$j-1] > $array->[$j]:
                @$array[$j, $j-1] = @$array[$j-1, $j]

my @a = 1..10
bubblesort \@a
is_deeply \@a, [sort { $a <=> $b } @a]

@a = (1,2,3,2,-1)
bubblesort \@a
is_deeply \@a, [sort { $a <=> $b } @a]

@a = reverse 50..100
bubblesort \@a
is_deeply \@a, [sort { $a <=> $b } @a]

# ----------------------------------------------------------------------


# These subroutines are ports to Acme::Pythonic from the ones in
# "Mastering Algorithms with Perl" except the last one, which I fixed
# myself. The one in the book is buggy.

sub basic_tree_find:
    my ($tree_link, $target, $cmp) = @_
    my $node

    while $node = $$tree_link:
        no warnings
        my $relation = defined $cmp ? $cmp->($target, $node->{val}) \
                                    : $target <=> $node->{val}
        return ($tree_link, $node) if $relation == 0
        $tree_link = $relation > 0 ? \$node->{left} : \$node->{right}

    return ($tree_link, undef)

sub basic_tree_add:
    my ($tree_link, $target, $cmp) = @_
    my $found

    ($tree_link, $found) = basic_tree_find($tree_link, $target, $cmp)
    unless $found:
        $found = {left  => undef,
                  right => undef,
                  val   => $target}
        $$tree_link = $found

    return $found

sub basic_tree_del:
    my ($tree_link, $target, $cmp) = @_
    my $found

    ($tree_link, $found) = basic_tree_find($tree_link, $target, $cmp)
    return undef unless $found
    if ! defined $found->{left}:
        $$tree_link = $found->{right}
    elsif ! defined $found->{right}:
        $$tree_link = $found->{left}
    else:
        MERGE_SOMEHOW($tree_link, $found)

    return $found->{val}

sub MERGE_SOMEHOW:
    my ($tree_link, $found) = @_
    my $left_of_right = $found->{right}
    my $next_left

    $left_of_right = $next_left \
        while $next_left = $left_of_right->{left}

    $left_of_right->{left} = $found->{left}

    $$tree_link = $found->{right}


# ----------------------------------------------------------------------
#
# Now I will port the next subroutines meticulously from the listings in
# the book, respecting comments, whitespace, etc. Except in one place.
#
# ----------------------------------------------------------------------

# manhattan_intersection( @lines )
#   Find the intersection of strictly horizontal and vertical lines.
#   Requires basic_tree_add(), basic_tree_del(), and basic_tree_find(),
#   all defined in Chapter 3, Advanced Data Structures
sub manhattan_intersection:
    my @op # The coordinates are transformed here as operations.

    while @_:
        my @line = splice @_, 0, 4

        if $line[1] == $line[3]:        # Horizontal.
            push @op, [ @line, \&range_check_tree ]
        else:
            # Swap if upside down.
            @line = @line[0, 3, 2, 1] if $line[1] > $line[3]

            push @op, [ @line[0, 1, 2, 1], \&basic_tree_add ]
            push @op, [ @line[0, 3, 2, 3], \&basic_tree_del ]

    my $x_tree # The range check tree.
    # The x coordinate comparison routine.
    my $compare_x = sub { $_[0]->[0] <=> $_[1]->[0] }
    my @intersect # The intersections.

    # We don't reproduce the multi-line here because parens are not put correctly.
    foreach my $op in sort { $a->[1] <=> $b->[1] || $a->[4] == \&range_check_tree || $a->[0] <=> $b->[0] } @op:
        if $op->[4] == \&range_check_tree:
            push @intersect, $op->[4]->( \$x_tree, $op, $compare_x )
        else: # Add or delete.
            $op->[4]->( \$x_tree, $op, $compare_x )

    return @intersect


#
# The implementation of range_check_tree() in the book is buggy, I
# submitted the bug to the authors.
#

# range_check_tree( $tree_link, $horizontal, $compare )
#
#    Returns the list of tree nodes that are within the limits
#    $horizontal->[0] and $horizontal->[1]. Depends on the binary
#    trees of Chapter 3, Advanced Data Structures.
sub range_check_tree:
    my ( $tree, $horizontal, $compare ) = @_

    my @range         = ()     # The return value.
    my $node          = $$tree
    my $vertical_x    = $node->{val}
    my $horizontal_lo = [ $horizontal->[ 0 ] ]
    my $horizontal_hi = [ $horizontal->[ 2 ] ]

    return unless defined $$tree

    push @range, range_check_tree( \$node->{left}, $horizontal, $compare ) \
        if defined $node->{left}

    push @range, $vertical_x->[ 0 ], $horizontal->[ 1 ] \
         if $compare->( $horizontal_lo, $vertical_x ) <= 0 && \
            $compare->( $horizontal_hi, $vertical_x ) >= 0

    push @range, range_check_tree( \$node->{right}, $horizontal,
                                   $compare ) \
        if defined $node->{right}

    return @range

my @lines = (0, 0, 0, 1)
is_deeply [manhattan_intersection(@lines)], []

@lines = (0, 0, 1, 0)
is_deeply [manhattan_intersection(@lines)], []

@lines = (0, 0, 1, 0, 0, 1, 1, 1, 0, 2, 1, 2)
is_deeply [manhattan_intersection(@lines)], []

@lines = (0, 0, 0, 1, 1, 0, 1, 1, 2, 0, 2, 1)
is_deeply [manhattan_intersection(@lines)], []

@lines = (0, 1, 2, 1, 1, 0, 1, 2)
is_deeply [manhattan_intersection(@lines)], [1, 1]

# This is the example in the book.
@lines = ( 1, 6,  1, 3,  1, 2,  3, 2,  1, 1,  4, 1,
              2, 4,  7, 4,  3, 0,  3, 6,  4, 3,  4, 7,
              5, 7,  5, 4,  5, 2,  7, 2 )
# And this is the correct answer, check Figure 10-10, which is right.
is_deeply [manhattan_intersection(@lines)], [3, 1, 3, 2, 5, 4, 4, 4, 3, 4]
