
package Pugs::Runtime::Perl6;

use strict;
use warnings;

use Data::Dumper;
use Data::Bind;
#use Lexical::Alias;
use Sub::Multi;
use PadWalker;
use IO::File ();
use Pugs::Compiler::Regex ();
use List::Util; # 'reduce'

$::_V6_BACKEND = 'BACKEND_PERL5';

# TODO - see Pugs::Runtime::Grammar for metaclass stuff

use constant Inf => 100**100**100;
use constant NaN => Inf - Inf;
    
    sub pad_depth {
        local $@;
        my $idx = 0;
        $idx++ while eval { PadWalker::peek_my($idx) };
        $idx;
    }
    
    sub eval_preprocess {
        my ($string, $lang);
        Data::Bind->arg_bind(\@_);
        $lang ||= 'perl6';
        my $eval_string;
        Data::Bind::bind_op2(\$eval_string, \$string);
        # print "LANG: $lang\n";
        if ($lang eq 'yaml') {
            # print "YAML: $eval_string\n";
            my $code = 
            'do{
                require YAML::Syck;
                # interoperability with other YAML/Syck bindings:
                $YAML::Syck::ImplicitTyping = 1;
                YAML::Syck::Load(\'' . $string . '\' );
            }';
            Data::Bind::bind_op2(\$eval_string, \$code);
            # print "YAML: $eval_string\n";
        }
        elsif ($lang eq 'perl6') {
            require Pugs::Compiler::Perl6;
            my $p6 = Pugs::Compiler::Perl6->compile( $string );
            Data::Bind::bind_op2(\$eval_string, \$p6->{perl5});
        }
        elsif ($lang ne 'perl5') {
            die;
        }
        return $eval_string;
    }
    
    Data::Bind->sub_signature(\&eval_preprocess, { var => '$string' }, { var => '$lang', optional => 1});
    
    sub setup_class {
        my ($class) = caller;
        no strict 'refs';
        my @foo = split /::/, $class;
        my $last = pop @foo;
        no strict 'refs';
        no warnings 'redefine';  # Moose already does this?
        *{'::'.$class} = sub { $class->meta->name };
    }

package Pugs::Runtime::Perl6::IO;
    use base 'IO::Handle';
    
    unless ( defined $::_V6_STDIN ) {
        $::_V6_STDIN = new Pugs::Runtime::Perl6::IO;
        unless ($::_V6_STDIN->fdopen(fileno(STDIN),"r")) {
            warn "Can't open \$*IN";
        }
    }
    
    sub slurp {
        my $self = $_[0];
        my $content;
        local $/; 
        $content = <$self>;
        return bless \$content, 'Pugs::Runtime::Perl6::Scalar';
    }

package Pugs::Runtime::Perl6::Routine;
    use B ();
    use Devel::Caller ();
    
    sub new {
        my ($class, $cv) = @_;
        bless { cv => B::svref_2object($cv) }, $class;
    }
    
    sub code {
        $_[0]->{cv}->object_2svref;
    }
    
    sub name {
        my $self = shift;
        my $cv = $self->{cv};
        return '&'.$cv->GV->STASH->NAME . '::' . $cv->GV->NAME;
    }
    
    sub package {
        $_[0]->{cv}->GV->STASH->NAME;
    }
    
    sub arity {
        my $cv = Data::Bind::_get_cv($_[0]->code);
        use Data::Dumper;
        return *$cv->{sig} ? *$cv->{sig}->arity : 0;
    }

package Pugs::Runtime::Perl6::Scalar;
    use Scalar::Util qw(looks_like_number);
    
    sub perl {
        local $Data::Dumper::Terse    = 1;
        local $Data::Dumper::Sortkeys = 1;
        my $dumped = join(', ', Data::Dumper::Dumper(@_));
        $dumped =~ s/\n$//;
        return $dumped;
    }
        
    sub yaml {
        require YAML::Syck;
        # interoperability with other YAML/Syck bindings:
        $YAML::Syck::ImplicitTyping = 1;
        YAML::Syck::Dump( $_[0] );
    }
    
    sub defined { CORE::defined(@_) }
    
    # TODO - rename this to 'WHAT'
    sub ref : method {
        # XXX: should use Data::Bind callconv
        #print "ref: ", Data::Dumper::Dumper( @_ );
        my $self = $_[0];
        return $$self->WHAT
            if UNIVERSAL::can( $$self, 'WHAT' );
        return 'Scalar'
            if ! defined $$self;
        
        my $ref = CORE::ref(@_);
        
        return 'Code'  if ref($self) eq 'CODE';
        return 'Hash'  if ref($self) eq 'HASH';
        return 'Array' if ref($self) eq 'ARRAY';
        unless ($ref) {
            return 'Num' if looks_like_number($self);
            return 'Str';
        }
        if ($self->can('meta')) {
            return $self->meta->name;
        }
        die 'unknown type';
    }
    
    sub isa {
        my $self = $_[0];
        return $self->isa( $_[1] )
            if     Scalar::Util::blessed $self 
                && UNIVERSAL::can( $self, 'isa' );
        return 1 if $_[1] eq 'Hash'  && ref($_[0]) eq 'HASH';
        return 1 if $_[1] eq 'Array' && ref($_[0]) eq 'ARRAY';
        return 1 if $_[1] eq 'Str'   && defined $_[0];
        return 1 if $_[1] eq 'Num'   && defined $_[0];
        return 1 if $_[1] eq 'Code'  && ref($_[0]) eq 'CODE';
        return 0;
    }
    
    sub eval { 
        my $s = ${$_[0]};
        #warn "eval $s\n";
        Pugs::Runtime::Perl6::eval( [ \$s, \'perl6' ], {} )     # '
    }
    
    sub sort { 
        sort @_ 
    }
    
    sub chars { 
        length "@_" 
    }
    
    sub reverse { 
        my $s = reverse $_[0];
        $s;
    }
    
    sub words { 
        # todo - parameter handling
        my $s = $_[0];
        $s =~ s/^\s+//;
        my @tmp = split( /\s+/, $s ); 
    }

package Pugs::Runtime::Perl6::Array;

    sub map {
        my ($code, @array);
        Data::Bind->arg_bind(\@_);
        my $run = ref($code) eq 'Pugs::Runtime::Perl6::Routine' ? $code->code : $code;
        my $arity = Pugs::Runtime::Perl6::Routine->new($run)->arity || 1;
    
        return map $run, @array if $arity == 1;
    
        my @result;
        my $i = 0;
        while ($i <= $#array) {
      my @x = @array[$i..$i+$arity-1];
      push @result, $run->([map { \$_ } @x], {});
      $i += $arity;
        }
        return @result;
    }
    
    Data::Bind->sub_signature(\&map, { var => '$code', type => 'Code' }, { var => '@array'} );

package Pugs::Runtime::Perl6::Hash;
    use overload (
        '""'     => \&str,
        '0+'     => sub { scalar keys %{$_[0]} },
        'bool'   => sub { 1 },
        fallback => 1,
    );
    sub str {
        join( "\n",
            map {
                $_ . "\t" . $_[0]{$_}
            }
            keys %{$_[0]}
        );
    }

package Pugs::Runtime::Perl6::Bool;
    use overload (
        '""'     => sub { ${$_[0]} ? 'Bool::True' : 'Bool::False' },
        '0+'     => sub { ${$_[0]} },
        'bool'   => sub { ${$_[0]} },
        fallback => 1,
    );
    sub WHAT { 
        'Bool';  # XXX box
    }
    sub isa { $_[0]->WHAT eq $_[1] }
package Pugs::Runtime::Perl6::Str;
    use overload (
        '""'     => sub { ${$_[0]} },
        '0+'     => sub { ${$_[0]} },
        'bool'   => sub { ${$_[0]} },
        fallback => 1,
    );
    sub pos { pos( ${$_[0]} ) }
    sub WHAT { 
        'Str';  # XXX box
    }
    sub isa { $_[0]->WHAT eq $_[1] }
package Pugs::Runtime::Perl6::Int;
    use overload (
        '""'     => sub { ${$_[0]} },
        '0+'     => sub { ${$_[0]} },
        'bool'   => sub { ${$_[0]} },
        '++'     => sub { ${$_[0]}++ },
        fallback => 1,
    );
    sub WHAT { 
        'Int';  # XXX box
    }
    sub isa { $_[0]->WHAT eq $_[1] }
package Pugs::Runtime::Perl6::Num;
    use overload (
        '""'     => \&str,
        '0+'     => sub { ${$_[0]} },
        'bool'   => sub { ${$_[0]} },
        '++'     => sub { ${$_[0]}++ },
        fallback => 1,
    );
    sub WHAT { 
        'Num';  # XXX box
    }
    sub isa { $_[0]->WHAT eq $_[1] }
    sub str { 
        my $n = ${$_[0]}; 
        $n == 'Inf' 
        ? 'Inf'
        : $n == 'NaN'
        ? 'NaN'
        : $n
    }

1;

__END__

=pod

=head1 NAME 

Pugs::Runtime::Perl6

=head1 DESCRIPTION

Provides runtime routines for the Perl6-in-Perl5 compiled code

=head1 AUTHORS

The Pugs Team E<lt>perl6-compiler@perl.orgE<gt>.

=head1 COPYRIGHT

Copyright 2006 by Flavio Soibelmann Glock and others.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

See L<http://www.perl.com/perl/misc/Artistic.html>

=cut
