package JavaScript::Runtime;

use strict;
use warnings;

use Carp qw(croak);

use JavaScript;

sub new {
    my ($pkg, @args) = @_;

    $pkg = ref $pkg || $pkg;

    my $maxbytes = $JavaScript::MAXBYTES;
	$maxbytes = shift @args if (@args && $args[0] =~ /^\d+$/);
	
	my @does;
	for (@args) {
		if (my ($type) = $_ =~ /^-(\w+)$/) {
			my $does = "JavaScript::Runtime::" . $type;
			if (!exists $JavaScript::Runtime::{$type . '::'}) {
				eval "require $does;";
				croak $@ if $@;
			}
			push @does, $does;
		}
	}
	
    my $runtime = jsr_create($maxbytes);
    my $self = bless { _impl => $runtime, _does => \@does }, $pkg;
   
	for (@does) {
		my $init = $_->can('_init');
		$init->($self) if $init;
	}
 
    return $self;
}

sub _destroy {
    my $self = shift;

	for (@{$self->{_does}}) {
		my $destroy = $_->can('_destroy');
		$destroy->($self);
	}
	
	if ($self->{_perl_interrupt_handler}) {
		# Remove the current one
		$self->set_interrupt_handler();
	}

    return unless $self->{'_impl'};
    jsr_destroy($self->{'_impl'});
    delete $self->{'_impl'};
    return 1;
}

sub DESTROY {
    my ($self) = @_;
    $self->_destroy();
}

sub create_context {
    my ($self, $stacksize) = @_;

    $stacksize = $JavaScript::STACKSIZE unless(defined($stacksize));
    my $context = JavaScript::Context->new($self, $stacksize);

    return $context;
}

sub _add_interrupt_handler {
	my ($self, $handler) = @_;
	jsr_add_interrupt_handler($self->{_impl}, $handler);
}

sub _remove_interrupt_handler {
	my ($self, $handler) = @_;
	jsr_remove_interrupt_handler($self->{_impl}, $handler);	
}

sub set_interrupt_handler {
    my ($self, $handler) = @_;

    if ($handler && ref $handler eq '') {
        my $caller_pkg = caller;
        $handler = $caller_pkg->can($handler);
    }
    
	if ($handler) {
		$self->{_perl_interrupt_handler} = jsr_init_perl_interrupt_handler($handler);
		$self->_add_interrupt_handler($self->{_perl_interrupt_handler});
		
	}
	elsif ($self->{_perl_interrupt_handler}) {
		$self->_remove_interrupt_handler($self->{_perl_interrupt_handler});
		jsr_destroy_perl_interrupt_handler($self->{_perl_interrupt_handler});
		delete $self->{_perl_interrupt_handler};
	}
	
	1;
}

our $AUTOLOAD;
sub AUTOLOAD {
	my $self = shift;
	my ($method_name) = $AUTOLOAD =~ /::([A-Za-z0-9_]+)$/;
	
	for my $does (@{$self->{_does}}) {
		if (defined (my $method = $does->can($method_name))) {
			return $method->($self, @_);
		}
	}
	
	my $isa = join(", ", @{$self->{_does}});
	croak "Can't call method '$method_name' because it's not defined in $isa";
}

1;
__END__

=head1 NAME

JavaScript::Runtime -

=head1 DESCRIPTION

=head1 INTERFACE

=head2 CLASS METHODS

=over 4

=item new ( $maxbytes )

Creates a new runtime object. The optional argument I<$maxbytes> specifies the number
of bytes that can be allocated before garbage collection is runned. If ommited it
defaults to 1MB.

=back

=head2 INSTANCE METHODS

=over 4

=item create_context ( $stacksize )

Creates a new C<JavaScript::Context>-object in the runtime. The optional argument
I<$stacksize> specifies number of bytes to allocate for the execution stack for the
script. If omitted it defaults to 32KB.

=item set_interrupt_handler ( $handler )

Attaches an interrupt handler (a function that is called before each op is
executed ) to the runtime. The argument I<$handler> must be either a code-reference
or the name of a subroutine in the calling package.

To remove the handler call this method with an undef as argument.

Note that attaching an interrupt handler to the runtime causes a slowdown in
execution speed since we must execute some Perl code between each op.

In order to abort execution your handler should a false value (such as 0). All true values will continue
execution. Any exceptions thrown by the handler are ignored and $@ is cleared.

=back

=begin PRIVATE

=head1 PRIVATE INTERFACE

=over 4

=item _destroy

Method that deallocates the runtime.

=item DESTORY

Called when the runtime is destroyed by Perl.

=item jsr_create ( int maxbytes )

Creates a runtime and returns a pointer to a C<PJS_Runtime> structure.

=item jsr_destroy ( PJS_Runtime *runtime )

Destorys the runtime and deallocates the memory occupied by it.

=item jsr_add_interrupt_handler ( PJS_Runtime *runtime, PJS_InterruptHandler *handler )

Adds an interrupt handler. 

=item jsr_remove_interrupt_handler ( PJS_Runtime *runtime, PJS_InterruptHandler *handler )

Removes an interrupt handler

=item jsr_init_perl_interrupt_handler ( CV *callback )

Initializes a new Perl level interrupt handler.

=item jsr_destroy_perl_interrupt_handler ( PJS_InterruptHandler *handler )

Destroys a Perl level interrupt handler

=back

=end PRIVATE

=head1 SEE ALSO

L<JavaScript::Context>

=cut

