The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl

use strict;
use warnings;

use Test::More;

use mop;

class LinkedList {
    has $!head  is ro;
    has $!tail  is ro;
    has $!count is ro = 0;

    method append ($node) {
        unless($!tail) {
            $!tail = $node;
            $!head = $node;
            $!count++;
            return;
        }
        $!tail->set_next($node);
        $node->set_previous($!tail);
        $!tail = $node;
        $!count++;
    }

    method insert ($index, $node) {
        die "Index ($index) out of bounds"
            if $index < 0 or $index > $!count - 1;

        my $tmp = $!head;
        $tmp = $tmp->get_next while($index--);
        $node->set_previous($tmp->get_previous);
        $node->set_next($tmp);
        $tmp->get_previous->set_next($node);
        $tmp->set_previous($node);
        $!count++;
    }

    method remove ($index) {
        die "Index ($index) out of bounds"
            if $index < 0 or $index > $!count - 1;

        my $tmp = $!head;
        $tmp = $tmp->get_next while($index--);
        $tmp->get_previous->set_next($tmp->get_next);
        $tmp->get_next->set_previous($tmp->get_previous);
        $!count--;
        $tmp->detach();
    }

    method prepend ($node) {
        unless($!head) {
            $!tail = $node;
            $!head = $node;
            $!count++;
            return;
        }
        $!head->set_previous($node);
        $node->set_next($!head);
        $!head = $node;
        $!count++;
    }

    method sum {
        my $sum = 0;
        my $tmp = $!head;
        do { $sum += $tmp->get_value } while($tmp = $tmp->get_next);
        return $sum;
    }
}

class LinkedListNode {
    has $!previous;
    has $!next;
    has $!value;

    method get_previous { $!previous }
    method get_next { $!next }
    method get_value { $!value }
    method set_previous($x) { $!previous = $x; }
    method set_next($x) { $!next = $x; }
    method set_value($x) { $!value = $x; }

    method detach { ($!previous, $!next) = (undef) x 2; $self }
}

{
    my $ll = LinkedList->new();

    for(0..9) {
        $ll->append(
            LinkedListNode->new(value => $_)
        );
    }

    is($ll->head->get_value, 0, '... head is 0');
    is($ll->tail->get_value, 9, '... tail is 9');
    is($ll->count, 10, '... count is 10');

    $ll->prepend(LinkedListNode->new(value => -1));
    is($ll->count, 11, '... count is now 11');

    $ll->insert(5, LinkedListNode->new(value => 11));
    is($ll->count, 12, '... count is now 12');

    my $node = $ll->remove(8);
    is($ll->count, 11, '... count is 11 again');

    ok(!$node->get_next, '... detached node does not have a next');
    ok(!$node->get_previous, '... detached node does not have a previous');
    is($node->get_value, 6, '... detached node has the right value');
    ok($node->isa('LinkedListNode'), '... node is a LinkedListNode');

    eval { $ll->remove(99) };
    like($@, qr/^Index \(99\) out of bounds/, '... removing out of range produced error');
    eval { $ll->insert(-1, LinkedListNode->new(value => 2)) };
    like($@, qr/^Index \(-1\) out of bounds/, '... inserting out of range produced error');

    is($ll->sum, 49, '... things sum correctly');
}

done_testing;