package EmbeddedArray;

use strict;
use warnings;
use Exporter;
use Storable qw(dclone);
use Data::Dumper;
use Carp;
use overload '.' => 'concatenate',
	    '+' => 'add',
	    '*' => 'multiply',
	    '/'=> 'divide',
	    '-'=> 'subtract',
	    fallback => 1,
	    q{""} => 'to_string',
	    '0+'  => 'to_num',
	    bool => 'to_bool',
	    '<=>' => 'numeric_comparison',
	    nomethod => sub { 
			croak "illegal operator $_[3]" 
	    };
our @ISA = qw(Exporter);

sub new {
  my ($class, $ref) = @_;
  my $self = {};
  if (defined $ref and ref($ref) eq 'ARRAY') {
    #$self->{array} =  dclone($ref)  ;
    $self->{array} = $ref;
    $self->{forarray} = [  map{ [$_] } @$ref ];
  }
  else {
    $self->{array} =  [];
    $self->{for_count} = 0;
    $self->{forarray} = [];
  }
  bless $self, $class;
  return $self;
}
sub i {
  $_[0]->{array}->[$_[1]];
}
sub mi {
  $#{$_[0]->{array}};
}
sub forvalues {
  $_[0]->{forarray}->[$_[1]]
}
sub eapush {
  push @{$_[0]->{array}}, $_[1];
}
sub eapop {
  pop @{$_[0]->{array}};
}
sub eaunshift {
  unshift @{$_[0]->{array}}, $_[1];
}
sub eashift {
  shift @{$_[0]->{array}};
}
sub ealist {
  @{$_[0]->{array}}
}
sub combine_for_arrays {
    my ($self, $second) = @_;
    my @newforarray =   map { my $l = $_;	    map {[@$l, @$_] } @{$second->{forarray}}	 } @{$self->{forarray}};
    return \@newforarray;
    
}
sub merge {
   my ($self, $second) = @_;
   if (UNIVERSAL::isa( $second, "EmbeddedArray") ) {
       $self->{array} = [(@{$self->{array}} , @{$second->{array}} )];
       $self->{forarray} = [(@{$self->{forarray}} , @{$second->{forarray}} )];
   }
   elsif (ref( $second) eq "ARRAY" ) {
       $self->{array} = [(@{$self->{array}} , @$second )];
       $self->{forarray} = [({@$self->{forarray}} , @$second )];
   }
   else {
      push @{$self->{array}}, $second;
      push @{$self->{forarray}}, $second;
   }
   return $self;
}
sub concatenate {
    my ($self, $other, $swap) = @_;
    
    return EmbeddedArray->new([]) unless (scalar @{$self->{array}});
    my $new = EmbeddedArray->new;
    if (UNIVERSAL::isa( $other, "EmbeddedArray") ) {
		return EmbeddedArray->new([]) unless (scalar @{$other->{array}});
		my ($l, $r) = ($self,$other);
		($r, $l) = ($self,$other) if $swap;
		foreach my $lo ($l->ealist) {
			foreach ($r->ealist) {
			  $new->eapush($lo."$_"); 
		  }
		}
      $new->{forarray} = combine_for_arrays($l, $r);
    }
    elsif ($swap) {
		foreach ($self->ealist) {
			$new->eapush($other.$_); 
		}
		$new->{forarray} = $self->{forarray};
    }
    else {
		foreach ($self->ealist) {
			$new->eapush($_.$other); 
			$new->{forarray} = $self->{forarray};
		}
    }
    return $new;
}
sub multiply {
    my ($self, $other, $swap) = @_;
    return EmbeddedArray->new([]) unless (scalar @{$self->{array}});
    my $new = EmbeddedArray->new;
    if (UNIVERSAL::isa( $other, "EmbeddedArray") ) {
		return EmbeddedArray->new([]) unless (scalar @{$other->{array}});
		my ($l, $r) = ($self,$other);
		($r, $l) = ($self,$other) if $swap;
		foreach my $lo ($l->ealist) {
			foreach ($r->ealist) {
			  $new->eapush($lo * $_); 
		  }
		}
      $new->{forarray} = combine_for_arrays($l, $r);

    }
    
    else {
		foreach ($self->ealist) {
			$new->eapush($_ * $other); 
			$new->{forarray} = $self->{forarray};
		}
    }

    
    return $new;
}
sub divide {
    my ($self, $other, $swap) = @_;
    return EmbeddedArray->new([]) unless (scalar @{$self->{array}});
    my $new = EmbeddedArray->new;
    if (UNIVERSAL::isa( $other, "EmbeddedArray") ) {
		return EmbeddedArray->new([]) unless (scalar @{$other->{array}});
		my ($l, $r) = ($self,$other);
		($r, $l) = ($self,$other) if $swap;
		foreach my $lo ($l->ealist) {
			foreach ($r->ealist) {
			  $new->eapush($lo / $_); 
		  }
		}
		  $new->{forarray} = combine_for_arrays($l, $r);
    }
    elsif ($swap) {
		foreach ($self->ealist) {
			$new->eapush($other / $_); 
		}
		$new->{forarray} = $self->{forarray};
    }
    else {
		foreach ($self->ealist) {
			$new->eapush( ($_) / $other); 
			$new->{forarray} = $self->{forarray};
		}
    }
    return $new;
}
sub subtract {
    my ($self, $other, $swap) = @_;
    return EmbeddedArray->new([]) unless (scalar @{$self->{array}});
    my $new = EmbeddedArray->new;
    if (UNIVERSAL::isa( $other, "EmbeddedArray") ) {
		return EmbeddedArray->new([]) unless (scalar @{$other->{array}});
		my ($l, $r) = ($self,$other);
		($r, $l) = ($self,$other) if $swap;
		foreach my $lo ($l->ealist) {
			foreach ($r->ealist) {
			  $new->eapush($lo - $_); 
		  }
		}
      $new->{forarray} = combine_for_arrays($l, $r);
    }
    elsif ($swap) {
		foreach ($self->ealist) {
			$new->eapush($other - $_); 
		}
		$new->{forarray} = $self->{forarray};
		}
    else {
		foreach ($self->ealist) {
			$new->eapush($_ - $other); 
			$new->{forarray} = $self->{forarray};
		}
    }
    return $new;
}
sub add {
    my ($self, $other, $swap) = @_;
    return EmbeddedArray->new([]) unless (scalar @{$self->{array}});
    my $new = EmbeddedArray->new;
    if (UNIVERSAL::isa( $other, "EmbeddedArray") ) {
		return EmbeddedArray->new([]) unless (scalar @{$other->{array}});
		my ($l, $r) = ($self,$other);
		($r, $l) = ($self,$other) if $swap;
		foreach my $lo ($l->ealist) {
			foreach ($r->ealist) {
			  $new->eapush($lo + "$_"); 
		  }
		}
		$new->{forarray} = combine_for_arrays($l, $r);	  
    }
    else {
		foreach ($self->ealist) {
			$new->eapush($_ + $other); 
			$new->{forarray} = $self->{forarray};
		}
    }
    
    return $new;
    
}
sub to_string {
    ref($_[0]);
}

sub to_num {
    return @{$_[0]->{array}};
}
sub to_bool {
    scalar @{$_[0]->{array}};
}
sub numeric_comparison {
	my ($self, $other, $swap) = @_;	
	if (UNIVERSAL::isa( $other, "EmbeddedArray") ) {
		my ($l, $r) = ($self,$other);
		($r, $l) = ($self,$other) if $swap;
        return scalar @{$l->{array}} <=> scalar @{$r->{array}};
    }
	elsif ($swap) {
	  return $other <=>  scalar @{$self->{array}};
	}
	else {
	  return   scalar @{$self->{array}} <=> $other ; 
	}
}
1;
