package Array::AsObject;
# Copyright (c) 2009-2010 Sullivan Beck. All rights reserved.
# This program is free software; you can redistribute it and/or modify it
# under the same terms as Perl itself.

###############################################################################

require 5.004;

use warnings;
use strict;
use Sort::DataTypes qw(sort_by_method sort_valid_method);

use vars qw($VERSION);
$VERSION = "1.02";

###############################################################################
# BASE METHODS
###############################################################################

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

   my $self = {
               "set"  => [],
               "err"  => "",
              };
   bless $self, $class;

   $self->list(@array)  if (@array);
   return $self;
}

sub version {
   my($self) = @_;

   return $VERSION;
}

sub err {
   my($self) = @_;

   return 1  if ($$self{"err"});
   return 0;
}

sub errmsg {
   my($self) = @_;

   return $$self{"err"};
}

###############################################################################
# LIST EXAMINATION METHODS
###############################################################################

sub as_hash {
   my($self,$full) = @_;
   $$self{"err"} = "";

   if ($full) {

      my %count;
      my %vals;
      my %refs;
      my %scal;
      my $undef;
      my $label = 1;
      foreach my $ele (@{ $$self{"set"} }) {
         if (! defined $ele) {
            if ($undef) {
               $count{$undef}++;
            } else {
               $undef         = $label++;
               $vals{$undef}  = undef;
               $count{$undef} = 1;
            }
         } elsif (ref($ele)) {
            my $s = scalar($ele);
            my $l;
            if (exists $refs{$s}) {
               $l         = $refs{$s};
               $count{$l}++;
            } else {
               $l         = $label++;
               $refs{$s}  = $l;
               $vals{$l}  = $ele;
               $count{$l} = 1;
            }
         } else {
            my $l;
            if (exists $scal{$ele}) {
               $l         = $scal{$ele};
               $count{$l}++;
            } else {
               $l          = $label++;
               $scal{$ele} = $l;
               $vals{$l}   = $ele;
               $count{$l}  = 1;
            }
         }
      }

      return (\%count, \%vals);

   } else {

      my %tmp;
      foreach my $ele (@{ $$self{"set"} }) {
         next  if (! defined $ele  ||  ref($ele));
         if (exists $tmp{$ele}) {
            $tmp{$ele}++;
         } else {
            $tmp{$ele} = 1;
         }
      }

      return %tmp;

   }
}

sub at {
   my($self,@n) = @_;
   $$self{"err"} = "";

   if      (! @n) {
      $$self{"err"} = "Index required";
      return undef;
   } elsif ($#n > 0  &&  ! wantarray) {
      $$self{"err"} = "In scalar context, only a single index allowed";
      return undef;
   }

   my @list = @{ $$self{"set"} };
   if (! @list) {
      $$self{"err"} = "Operation (at) invalid with empty list";
      return undef;
   }

   my(@ret);
   my $len = $#list + 1;

   foreach my $n (@n) {
      if ($n =~ /^[+-]?\d+$/) {
         if ($n < -$len  ||  $n > $len-1) {
            $$self{"err"} = "Index out of range";
            return undef;
         }
         CORE::push(@ret,$list[$n]);
      } else {
         $$self{"err"} = "Index must be an integer";
         return undef;
      }
   }

   if (wantarray) {
      return @ret;
   } else {
      return $ret[0];
   }
}

sub count {
   my($self,$val) = @_;
   my @idx = $self->index($val);
   return undef  if ($self->err());

   return $#idx + 1;
}

sub exists {
   my($self,@val) = @_;
   @val = (undef)  if (! @val);

   foreach my $val (@val) {
      my @idx = $self->index($val);
      return undef  if ($self->err());
      return 0  if (! @idx);
   }
   return 1;
}

sub first {
   my($self) = @_;
   $$self{"err"} = "";

   my @list = @{ $$self{"set"} };
   if (! @list) {
      $$self{"err"} = "Operation (first) invalid with empty list";
      return undef;
   }

   return $list[0];
}

sub last {
   my($self) = @_;
   $$self{"err"} = "";

   my @list = @{ $$self{"set"} };
   if (! @list) {
      $$self{"err"} = "Operation (first) invalid with empty list";
      return undef;
   }

   return $list[$#list];
}

sub index {
   my($self,$val) = @_;
   $$self{"err"} = "";

   my @idx  = ();
   my @list = @{ $$self{"set"} };

   for (my $i=0; $i<=$#list; $i++) {
      my $ele = $list[$i];
      CORE::push(@idx,$i)  if (_eq($self,$val,$ele));
   }

   if (wantarray) {
      return @idx;
   } elsif (@idx) {
      return $idx[0];
   } else {
      return -1;
   }
}

sub rindex {
   my($self,$val) = @_;
   my @idx = $self->index($val);

   if (wantarray) {
      return CORE::reverse(@idx);
   } elsif (@idx) {
      return $idx[$#idx];
   } else {
      return -1;
   }
}

sub is_empty {
   my($self,$undef) = @_;
   $$self{"err"} = "";

   my @list = @{ $$self{"set"} };
   return 1  if ($#list == -1);

   foreach my $ele (@list) {
      next  if ($undef  &&  ! defined $ele);
      return 0;
   }

   return 1;
}

sub length {
   my($self) = @_;
   $$self{"err"} = "";

   return $#{ $$self{"set"} } + 1;
}

sub list {
   my($self,@list) = @_;
   $$self{"err"}   = "";

   if (@list) {
      $$self{"set"} = [@list];
      return;
   } else {
      return @{ $$self{"set"} };
   }
}

sub _eq {
   my($self,$val1,$val2) = @_;

   if (! defined $val1  &&  ! defined $val2) {
      return 1;
   } elsif (! defined $val1  ||  ! defined $val2) {
      return 0;

   } elsif (ref($val1)  &&  ref($val2)  &&  scalar($val1) eq scalar($val2)) {
      return 1;
   } elsif (ref($val1)  ||  ref($val2)) {
      return 0;

   } elsif ($val1 eq $val2) {
      return 1;
   } else {
      return 0;
   }
}

###############################################################################
# SIMPLE LIST MODIFICATION METHODS
###############################################################################

sub clear {
   my($self,$undef) = @_;
   $$self{"err"}   = "";

   if ($undef) {
      foreach my $ele (@{ $$self{"set"} }) {
         $ele = undef;
      }

   } else {
      $$self{"set"} = [];
   }

   return;
}

sub compact {
   my($self) = @_;
   $$self{"err"} = "";

   my @list = ();
   foreach my $ele (@{ $$self{"set"} }) {
      CORE::push(@list,$ele)  if (defined $ele);
   }
   $$self{"set"} = [@list];
   return;
}

sub delete {
   my($self,$all,$undef,@val) = @_;
   $$self{"err"} = "";

   foreach my $val (@val) {
      my(@idx);
      if ($all) {
         @idx = $self->rindex($val);
         next  if (! @idx);
      } else {
         my $idx = $self->index($val);
         next  if ($idx == -1);
         @idx = ($idx);
      }

      if ($undef) {
         foreach my $idx (@idx) {
            $$self{"set"}[$idx] = undef;
         }
      } else {
         foreach my $idx (@idx) {
            CORE::splice(@{ $$self{"set"} },$idx,1);
         }
      }
   }
   return;
}

sub delete_at {
   my($self,$undef,@idx) = @_;
   $$self{"err"} = "";

   my @list = @{ $$self{"set"} };
   foreach my $idx (@idx) {
      if ($idx !~ /^[+-]?\d+$/) {
         $$self{"err"} = "Index must be an integer";
         return undef;
      }
      if ($idx < -($#list + 1)  ||
          $idx > $#list) {
         $$self{"err"} = "Index out of bounds";
         return undef;
      }
      if ($idx < 0) {
         $idx = $#list + 1 + $idx;
      }
   }
   @idx = sort { $b <=> $a } @idx;

   if ($undef) {
      foreach my $idx (@idx) {
         $$self{"set"}[$idx] = undef;
      }
   } else {
      foreach my $idx (@idx) {
         CORE::splice(@{ $$self{"set"} },$idx,1);
      }
   }
   return;
}

sub fill {
   my($self,$val,$start,$length) = @_;
   $$self{"err"} = "";

   my @list = @{ $$self{"set"} };

   $start = 0  if (! $start);
   if ($start !~ /^[+-]?\d+$/) {
      $$self{"err"} = "Start must be an integer";
      return undef;
   }
   if ($start < -($#list + 1)  ||
       $start > $#list + 1) {
      $$self{"err"} = "Start out of bounds";
      return undef;
   }
   if ($start < 0) {
      $start = $#list + 1 + $start;
   }

   if (! defined $length) {
      if ($start > $#list) {
         $length = 1;
      } else {
         $length = ($#list + 1 - $start);
      }
   }

   if ($length !~ /^\d+$/) {
      $$self{"err"} = "Length must be an unsigned integer";
      return undef;
   }
   my $end = $start + $length - 1;

   foreach my $i ($start..$end) {
      $list[$i] = $val;
   }

   $$self{"set"} = [@list];
   return;
}

sub min {
   my($self,$method,@args) = @_;

   if (! defined $method) {
      $method = "numerical";
   }

   my(@list) = _sort($self,$method,@args);
   return undef  if ($self->err());

   return $list[0];
}

sub max {
   my($self,$method,@args) = @_;

   if (! defined $method) {
      $method = "numerical";
   }

   my(@list) = _sort($self,$method,@args);
   return undef  if ($self->err());

   return $list[$#list];
}

sub pop {
   my($self) = @_;
   $$self{"err"}   = "";

   my $val = CORE::pop @{ $$self{"set"} };
   return $val;
}

sub shift {
   my($self) = @_;
   $$self{"err"}   = "";

   my $val = CORE::shift @{ $$self{"set"} };
   return $val;
}

sub push {
   my($self,@list) = @_;
   $$self{"err"}   = "";

   CORE::push(@{ $$self{"set"} },@list);
   return;
}

sub unshift {
   my($self,@list) = @_;
   $$self{"err"}   = "";

   CORE::unshift(@{ $$self{"set"} },@list);
   return;
}

sub randomize {
   my($self) = @_;
   $self->sort("random");
}

sub reverse {
   my($self) = @_;
   $$self{"err"}   = "";

   my @list = @{ $$self{"set"} };
   $$self{"set"} = [ CORE::reverse(@list) ];
   return;
}

sub rotate {
   my($self,$n) = @_;
   $n = 1  if (! defined $n);
   $$self{"err"} = "";

   if ($n !~ /^[+-]?\d+$/) {
      $$self{"err"} = "Rotation number must be an integer";
      return undef;
   }

   my @list = @{ $$self{"set"} };
   if ($n > 0) {
      for (my $i=1; $i<=$n; $i++) {
         CORE::push(@list,CORE::shift(@list));
      }
   } elsif ($n < 0) {
      $n *= -1;
      for (my $i=1; $i<=$n; $i++) {
         CORE::unshift(@list,CORE::pop(@list));
      }
   }

   $$self{"set"} = [@list];
   return;
}

sub set {
   my($self,$index,$val) = @_;
   $$self{"err"} = "";

   if (! defined $index) {
      $$self{"err"} = "Index required";
      return undef;
   }

   my @list = @{ $$self{"set"} };

   if ($index !~ /^[+-]?\d+$/) {
      $$self{"err"} = "Index must be an integer";
      return undef;
   }
   if ($index < -($#list + 1)  ||
       $index > $#list) {
      $$self{"err"} = "Index out of bounds";
      return undef;
   }

   $$self{"set"}[$index] = $val;
   return;
}

sub sort {
   my($self,$method,@args) = @_;

   if (! defined $method) {
      $method = "alphabetic";
   }

   my(@list) = _sort($self,$method,@args);
   return undef  if ($self->err());

   $$self{"set"} = [@list];
   return;
}

sub _sort {
   my($self,$method,@args) = @_;
   $$self{"err"} = "";

   if (! sort_valid_method($method)) {
      $$self{"err"} = "Invalid sort method";
      return undef;
   }

   my @list = @{ $$self{"set"} };
   sort_by_method($method,\@list,@args);
   return @list;
}

sub splice {
   my($self,$start,$length,@vals) = @_;
   $$self{"err"} = "";

   my @list = @{ $$self{"set"} };

   $start = 0  if (! $start);
   if ($start !~ /^[+-]?\d+$/) {
      $$self{"err"} = "Start must be an integer";
      return undef;
   }
   if ($start < -($#list + 1)  ||
       $start > $#list) {
      $$self{"err"} = "Start out of bounds";
      return undef;
   }
   if ($start < 0) {
      $start = $#list + 1 + $start;
   }

   if (! defined $length) {
      if ($start > $#list) {
         $length = 1;
      } else {
         $length = ($#list + 1 - $start);
      }
   }

   if ($length !~ /^\d+$/) {
      $$self{"err"} = "Length must be an unsigned integer";
      return undef;
   }
   my $end = $start + $length - 1;

   my @ret = CORE::splice(@list,$start,$length,@vals);

   $$self{"set"} = [@list];
   return @ret;
}

sub unique {
   my($self) = @_;
   $$self{"err"} = "";

   my @list = ();
   my %list = ();
   my $undef = 0;

   foreach my $ele (@{ $$self{"set"} }) {
      if (! defined($ele)) {
         if (! $undef) {
            CORE::push(@list,$ele);
            $undef = 1;
         }
      } elsif (! CORE::exists $list{$ele}) {
         CORE::push(@list,$ele);
         $list{$ele} = 1;
      }
   }
   $$self{"set"} = [@list];
   return;
}

###############################################################################
# SET METHODS
###############################################################################

sub difference {
   my($obj1,$obj2,$unique) = @_;

   my @list  = @{ $$obj1{"set"} };
   my $class = ref($obj1);
   my $ret   = new $class;

   if (ref($obj2) ne $class) {
      $$ret{"err"} = "Obj2 not of the right class";
      return $ret;
   }

   # $ret starts as identical to $obj1
   # remove every element in $obj2 from $ret

   $ret->list(@list);
   my $all = ($unique ? 1 : 0);
   foreach my $ele (@{ $$obj2{"set"} }) {
      $ret->delete($all,0,$ele);
   }

   return $ret;
}

sub intersection {
   my($obj1,$obj2,$unique) = @_;

   my $class = ref($obj1);
   my $ret   = new $class;

   if (ref($obj2) ne $class) {
      $$ret{"err"} = "Obj2 not of the right class";
      return $ret;
   }

   # $tmp is identical to $obj2
   # foreach element in $obj1
   #    if it's in $tmp
   #       add it to $ret
   #       remove it from $tmp

   my $tmp   = new $class;
   $tmp->list(@{ $$obj2{"set"} });
   my $all   = ($unique ? 1 : 0);

   my @list  = @{ $$obj1{"set"} };
   foreach my $ele (@list) {
      if ($tmp->exists($ele)) {
         $ret->push($ele);
         $tmp->delete($all,0,$ele);
      }
   }

   return $ret;
}

sub is_equal {
   my($obj1,$obj2,$unique) = @_;

   my $class = ref($obj1);

   if (ref($obj2) ne $class) {
      return undef;
   }

   my @list1  = @{ $$obj1{"set"} };
   my @list2  = @{ $$obj2{"set"} };

   if ($unique) {
      foreach my $ele (@list1) {
         return 0  if (! $obj2->exists($ele));
      }
      foreach my $ele (@list2) {
         return 0  if (! $obj1->exists($ele));
      }
      return 1;
   }

   foreach my $ele (@list1,@list2) {
      return 0  if ($obj1->count($ele) != $obj2->count($ele));
   }
   return 1;
}

sub not_equal {
   return 1 - is_equal(@_);
}

sub is_subset {
   my($obj1,$obj2,$unique) = @_;

   my $class = ref($obj1);

   if (ref($obj2) ne $class) {
      return undef;
   }

   my @list  = @{ $$obj2{"set"} };

   if ($unique) {
      foreach my $ele (@list) {
         return 0  if (! $obj1->exists($ele));
      }
      return 1;
   }

   foreach my $ele (@list) {
      return 0  if ($obj2->count($ele) > $obj1->count($ele));
   }
   return 1;
}

sub not_subset {
   return 1 - is_subset(@_);
}

sub symmetric_difference {
   my($obj1,$obj2,$unique) = @_;

   my $class = ref($obj1);
   my $ret   = new $class;

   if (ref($obj2) ne $class) {
      $$ret{"err"} = "Obj2 not of the right class";
      return $ret;
   }

   my $tmp1  = new $class;
   my @list1 = @{ $$obj1{"set"} };
   $tmp1->list(@list1);

   my $tmp2  = new $class;
   my @list2 = @{ $$obj2{"set"} };
   $tmp2->list(@list2);

   my $all   = ($unique ? 1 : 0);

   foreach my $ele (@list1,@list2) {
      if ($tmp1->exists($ele)  &&  $tmp2->exists($ele)) {
         $tmp1->delete($all,0,$ele);
         $tmp2->delete($all,0,$ele);
      } elsif ($tmp1->exists($ele)) {
         $ret->push($ele);
         $tmp1->delete(0,0,$ele);
      } elsif ($tmp2->exists($ele)) {
         $ret->push($ele);
         $tmp2->delete(0,0,$ele);
      }
   }

   return $ret;
}

sub union {
   my($obj1,$obj2,$unique) = @_;

   my $class = ref($obj1);
   my $ret   = new $class;

   if (ref($obj2) ne $class) {
      $$ret{"err"} = "Obj2 not of the right class";
      return $ret;
   }

   my @list1 = @{ $$obj1{"set"} };
   my @list2 = @{ $$obj2{"set"} };

   $ret->list(@list1,@list2);
   if ($unique) {
      $ret->unique();
   }

   return $ret;
}

1;
# Local Variables:
# mode: cperl
# indent-tabs-mode: nil
# cperl-indent-level: 3
# cperl-continued-statement-offset: 2
# cperl-continued-brace-offset: 0
# cperl-brace-offset: 0
# cperl-brace-imaginary-offset: 0
# cperl-label-offset: -2
# End: