package JE::Object::Proxy;

our $VERSION = '0.027';

use strict;
use warnings; no warnings 'utf8';

# ~~~ delegate overloaded methods?

use JE::Code 'add_line_number';
use Scalar::Util 1.09 qw'refaddr';

require JE::Object;

our @ISA = 'JE::Object';


=head1 NAME

JE::Object::Proxy - JS wrapper for Perl objects

=head1 SYNOPSIS

  $proxy = new JE::Object::Proxy $JE_object, $some_Perl_object;

=cut




sub new {
	my($class, $global, $obj) = @_;

	my $class_info = $$$global{classes}{ref $obj};

	my $self = ($class eq __PACKAGE__ # allow subclassing
	            && ($$class_info{hash} || $$class_info{array})
			? __PACKAGE__."::Array" : $class)
		   ->JE::Object::new($global,
		{ prototype => $$class_info{prototype} });

	@$$self{qw/class_info value/} = ($class_info, $obj);

	while(my($name,$args) = each %{$$class_info{props}}) {
		$self->prop({ name => $name, @$args });
	}

	$self;
}




sub class { $${$_[0]}{class_info}{name} }




sub value { $${$_[0]}{value} }




sub id {
	refaddr $${$_[0]}{value};
}




sub to_primitive { # ~~~ This code should  probably  be  moved  to 
                   #     &JE::bind_class for the sake of efficiency.
	my($self, $hint) = (shift, @_);

	my $guts = $$self;
	my $value = $$guts{value};
	my $class_info = $$guts{class_info};

	if(exists $$class_info{to_primitive}) {
		my $tp = $$class_info{to_primitive};
		if(defined $tp) {
			ref $tp eq 'CODE' and
				return $$guts{global}->upgrade(
					&$tp($value, @_)
				);
			($tp, my $type) = JE::_split_meth($tp);
			return defined $type
			?  $$guts{global}->_cast($value->$tp(@_),$type)
			:  $$guts{global}->upgrade($value->$tp(@_))
		} else {
			die add_line_number
				"The object ($$class_info{name}) cannot "
				. "be converted to a primitive";
		}
	} else {
		if(overload::Method($value,'""') ||
		   overload::Method($value,'0+') ||
		   overload::Method($value,'bool')){
			return $$guts{global}->upgrade("$value");
		}
		return SUPER::to_primitive $self @_;
	}
}



sub to_string {
	my($self, $hint) = (shift, @_);

	my $guts = $$self;
	my $value = $$guts{value};
	my $class_info = $$guts{class_info};

	if(exists $$class_info{to_string}) {
		my $tp = $$class_info{to_string};
		if(defined $tp) {
			ref $tp eq 'CODE' and
				return $$guts{global}->upgrade(
					&$tp($value, @_)
				)->to_string;
			($tp, my $type) = JE::_split_meth $tp;
			return ( defined $type
			  ?  $$guts{global}->upgrade($value->$tp(@_))
			  :  $$guts{global}->_cast($value->$tp(@_),$type)
			)->to_string
		} else {
			die add_line_number
				"The object ($$class_info{name}) cannot "
				. "be converted to a string";
		}
	} else {
		return SUPER::to_string $self @_;
	}
}




sub to_number {
	my($self, $hint) = (shift, @_);

	my $guts = $$self;
	my $value = $$guts{value};
	my $class_info = $$guts{class_info};

	if(exists $$class_info{to_number}) {
		my $tp = $$class_info{to_number};
		if(defined $tp) {
			ref $tp eq 'CODE' and
				return $$guts{global}->upgrade(
					&$tp($value, @_)
				)->to_number;
			($tp, my $type) = JE::_split_meth $tp;
			return ( defined $type
			  ?  $$guts{global}->upgrade($value->$tp(@_))
			  :  $$guts{global}->_cast($value->$tp(@_),$type)
			)->to_number
		} else {
			die add_line_number
				"The object ($$class_info{name}) cannot "
				. "be converted to a number";
		}
	} else {
		return SUPER::to_number $self @_;
	}
}




package JE::Object::Proxy::Array; # so this extra stuff doesn't slow down
our $VERSION = '0.027';           # 'normal' usage
our @ISA = 'JE::Object::Proxy';

sub prop {
	my $self = shift;
	my $wrappee = $self->value;
	my $name = shift;
	my $class_info = $$$self{class_info};

	if ($$class_info{array}) {
		if($name eq 'length') {
			@_ ? ($#$wrappee = $_[0]-1, return shift)
			   : return scalar @$wrappee
		}
		if($name =~ /^(?:0|[1-9]\d*)\z/ and $name < 4294967295){
			@_ ? $$class_info{array}{store}(
				$wrappee,$name,$_[0]) && return shift
		  	 : do {
				my $ret =
				   $$class_info{array}{fetch}(
				      $wrappee,$name);
				defined $ret and return $ret;
			   }
		}
	}
	if ($$class_info{hash}and !exists $$class_info{props}{$name}) {
		if(@_){
			$$class_info{hash}{store}->(
				$wrappee,$name,$_[0]
			) and return shift;
		}else{
			my $ret = $$class_info{hash}{fetch}
				($wrappee,$name);
			defined $ret and return $ret;
		}
	}
	SUPER::prop $self $name, @_;
}

sub keys {
	my $self = shift;
	my $wrappee = $self->value;
	my $class_info = $$$self{class_info};
	my @keys;
	if ($$class_info{array}){
		@keys = grep(exists $wrappee->[$_], 0..$#$wrappee);
	}
	if($$class_info{hash}) {
		push @keys, keys %$wrappee;
	}
	push @keys, SUPER::keys $self;
	my @new_keys; my %seen;
	$seen{$_}++ or push @new_keys, $_ for @keys;
	@new_keys;
}

sub delete {
	my $self = shift;
	my $wrappee = $self->value;
	my($name) = @_;
	my $class_info = $$$self{class_info};
	if ($$class_info{array}){
		if ($name =~ /^(?:0|[1-9]\d*)\z/ and $name < 4294967295 and
		    exists $wrappee->[$name]) {
			delete $wrappee->[$name];
			return !$self->exists($name);
		}
		elsif ($name eq 'length') {
			return !1
		}
	}
	if($$class_info{hash} && !exists $$class_info{props}{$name} and
	   exists $wrappee->{$name}) {
		delete $self->value->{$name};
		return !exists $self->value->{$name};
	}
	SUPER::delete $self @_;
}


1;

