#!/usr/bin/env perl -l

# This is a little test program to see if we could implement
# http://candygram.sourceforge.net/node6.html on MooseX::POE
# using MX::Poe objects (aka POE::Sessions) to replace threads

# Here is the relevant code from Canygram
#
#
# >>> import candygram as cg
# >>> import time
# >>> def proc_func():
# ...     r = cg.Receiver()
# ...     r.addHandler('land shark', shut_door, cg.Message)
# ...     r.addHandler('candygram', open_door, cg.Message)
# ...     for message in r:
# ...         print message
# ...
# >>> def shut_door(name):
# ...     return 'Go Away ' + name
# ...
# >>> def open_door(name):
# ...     return 'Hello ' + name
# ...
# >>> proc = cg.spawn(proc_func)
# >>> proc.send('land shark')
# >>> proc.send('candygram')
# >>> # Give the proc a chance to print its messages before termination:
# ... time.sleep(1)

#
# here is our version
#
sub main {

    sub proc_func {
        my $r = $_[0]->receiver;
        $r->add_handler( 'land_shark', \&shut_door );
        $r->add_handler( 'candygram',  \&open_door );
        while (<$r>) {
            print;
        }
    }

    sub shut_door {
        return 'Go away ' . $_[1];
    }

    sub open_door {
        return 'Hello ' . $_[1];
    }

    my $proc = Candygram->spawn( \&proc_func );
    $proc->send('land_shark');
    $proc->send('candygram');
    POE::Kernel->run;    # we have to run the kernel manually
    
}

# 
# Implementation
# 

{

    package Candygram;

    sub spawn {
        my ( $self, $func ) = splice @_, 0, 2;
        return Proc->new( func => $func, args => \@_ );
    }

}

#
# the Receiver object does all the real work
#

{

    package Receiver;
    use Moose;
    use overload '<>' => \&receive;

    has mailbox => (
        isa        => 'ArrayRef',
        is         => 'ro',
        auto_deref => 1,
        default    => sub { [] },
    );

    has handlers => (
        isa     => 'HashRef',
        is      => 'ro',
        default => sub { {} },
    );

    sub add_handler {
        my ( $self, $state, $code ) = @_;
        return if exists $self->handlers->{$state};
        $self->handlers->{$state} = $code;
    }

#   This could be cleaned up a bunch by MooseX::AttributeHelpers on the 
#   attributes above, but I didn't wan't to have a dependency
#   for a example script

    sub receive {
        my $self = shift;
        my ( $state, $args );
        return unless scalar @{ $self->mailbox };
        for ( 0 .. $#{ $self->mailbox } ) {
            my $state = $self->mailbox->[$_]->[0];
            next unless $state;
            next unless exists $self->handlers->{$state};
            ( $state, $args ) = @{ splice @{ $self->mailbox }, $_, 1 };
            if ( $state && $args ) {
                my $res = $self->handlers->{$state}->(@$args);
                return $res;
            }
        }
        return;
    }
}

{

    package Proc;
    use MooseX::POE;

    has func => (
        isa     => 'CodeRef',
        is      => 'ro',
        default => sub {
            sub { }
        },
    );
    has args => (
        isa        => 'ArrayRef',
        is         => 'ro',
        auto_deref => 1,
        default    => sub { [] },
    );

    has receiver => (
        is      => 'ro',
        default => sub { Receiver->new() },
    );

    sub START {
        my ($self) = @_;
        $self->yield('loop');
    }

    sub on_loop {
        my ($self) = @_;
        my $func = $self->func;
        $self->$func( $self->args );
    }

    sub send {
        my ( $self, $message ) = @_;
        push @{ $self->receiver->mailbox }, [ $message, \@_ ];
        $self->yield('loop');
    }

}

main();
