package POOF::Properties::Array;

use 5.007;
use strict;
use warnings;

use Carp qw(croak confess);
use base qw(POOF::Properties);

use POOF::DataType;

our $VERSION = '1.0';

my %DEFINITION;

use constant ACCESSLEVEL =>
{
    'Private'        => 0,
    'Protected'      => 1,
    'Public'         => 2,
};

use constant PUBLIC => '@@__POOF::Properties::Public__@@';
use constant DUMMY => '@@__POOF::Properties::DUMMY__@@';

my $GROUPS;
my $REFOBJ;

our $DEBUG = 0;

# CONSTRUCTOR
sub TIEARRAY
{
    my $class = shift;
    my $obj = {};
    bless $obj, $class;
    $obj->_init(@_);
    return $obj;
}

#-------------------------------------------------------------------------------
# Protected Methods go here

sub _init
{
    my ($obj,$def,$self,$exceptionHandlerRef,$propertiesRef,$propBackDoor) = @_;
    
    $obj->{'self'} =
        $self
            ? $self
            : ref($obj);
            
    $obj->{'exceptionHandler'} = $exceptionHandlerRef
        if $exceptionHandlerRef;
        
    $$propertiesRef->{ $obj->{'self'} } = $obj;

    # make sure all keys are lower case
    %{$obj->{'def'}} = map { lc($_) => $def->{ $_ } } keys %{$def};
    
    my $access = $obj->{'def'}->{'access'};
    
    $obj->{'def'}->{'access'} =
        $access
            ? exists +ACCESSLEVEL->{ $access }  
                ? +ACCESSLEVEL->{ $access }   
                : confess "Unkown access type: $access" 
            : $obj->{'def'}->{'name'} eq DUMMY
                ? +ACCESSLEVEL->{'Private'} 
                : +ACCESSLEVEL->{'Public'};
    

    $obj->CLEAR;
    
    return @_;
}

sub RefObj
{
    my ($obj,$ref) = @_;
    $obj->{'___refobj___'} = $ref;
}

#-------------------------------------------------------------------------------
# property definitions

sub Definition
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return $obj->{'def'};
}

#-------------------------------------------------------------------------------
# hash functionality bindings

sub CLEAR
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return $obj->{'ARRAY'} = [ ]; 
}

sub EXISTS
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return exists $obj->{'ARRAY'}->[ +shift ]; 
}

sub FETCH
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    my ($i) = @_;
    $obj->STORE
    (
        $i,$obj->{'def'}->{'otype'}->new
        (
            %{$obj->{'def'}->{'args'}}
        )
    ) unless(exists $obj->{'ARRAY'}->[$i]);
    return $obj->{'ARRAY'}->[$i];
}

sub FETCHSIZE
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return scalar @{$obj->{'ARRAY'}};
}

sub DELETE
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return delete $obj->{'ARRAY'}->[ +shift ]; 
}

sub STORE
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    my ($i,$v) = @_;
    
    # enforce maxsize
    if (defined $obj->{'def'}->{'maxsize'} && $obj->{'def'}->{'maxsize'})
    {
        if ($i + 1 > $obj->{'def'}->{'maxsize'})
        {
            # generate error
            &{$obj->{'exceptionHandler'}}
            (
                $obj->{'___refobj___'},
                $obj->{'def'}->{'name'},
                {
                    'code' => 133,
                    'description' => "maxsize test failed",
                    'value' => $v
                }
            ) if defined $obj->{'exceptionHandler'};
            return;
        }
    }
    
    # only allow store if $v is of the right class
    unless ($obj->_relationship($v,$obj->{'def'}->{'otype'}) =~ /^(?:self|child)$/o)
    {
        # generate error
        &{$obj->{'exceptionHandler'}}
        (
            $obj->{'___refobj___'},
            $obj->{'def'}->{'name'},
            {
                'code' => 173,
                'description' => "element index $i: is not of a valid type",
                'value' => $v
            }
        ) if defined $obj->{'exceptionHandler'};
        return;
    }
    
    return $obj->{'ARRAY'}->[ $i ] = $v;
}
  
sub STORESIZE
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    my ($newsize) = @_;
    
    # enforce maxsize
    if (defined $obj->{'def'}->{'maxsize'} && $obj->{'def'}->{'maxsize'})
    {
        if ($newsize + 1 > $obj->{'def'}->{'maxsize'})
        {
            # generate error
            &{$obj->{'exceptionHandler'}}
            (
                $obj->{'___refobj___'},
                $obj->{'def'}->{'name'},
                {
                    'code' => 133,
                    'description' => "maxsize test failed",
                    'value' => ''
                }
            ) if defined $obj->{'exceptionHandler'};
            return;
        }
    }
    
    
    my $diff = $newsize - @{$obj->{'ARRAY'}};
    
    unless ($diff == 0)
    {
        return
            $diff > 0
                ? $obj->{'ARRAY'}->[ $diff .. $newsize ] = map { undef } ($diff .. $newsize) 
                : map { $obj->POP } ( 0 .. (scalar(@{$obj->{'ARRAY'}}) - $newsize) - 2 ); 
    }
    return;
}
  
sub PUSH
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return push(@{$obj->{'ARRAY'}},@_);
}
  
sub POP
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return pop @{+shift->_enforcement->{'ARRAY'}};
}

sub SHIFT
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return shift @{$obj->{'ARRAY'}};
}

sub UNSHIFT
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    my @list = @_;
    my $size = scalar @list;
    
    # make room for our list
    @{$obj->{'ARRAY'}}[ $size .. $#{$obj->{'ARRAY'}} + $size ] = @{$obj->{'ARRAY'}};
      
    return map { $obj->STORE($_,$list[$_]) } (0 .. $#list);
}

sub SPLICE
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    my $offset = shift || 0;
    my $length = shift || $obj->FETCHSIZE - $offset;
    my @list = ();

    if ( @_ )
    {
        tie @list, __PACKAGE__;
        @list   = @_;
    }
        
    return splice @{$obj->{'ARRAY'}}, $offset, $length, @list;
}

sub EXTEND
{
    my $obj = +shift->_enforcement;
    #----------------------------------
    return $obj->STORESIZE( +shift );
}
  
#-------------------------------------------------------------------------------
# private Methods

sub Trace
{
    my $obj = shift;
    my %caller;
    @caller{ qw(
        0-package
        1-filename
        2-line
        3-subr
        4-has_args
        5-wantarray
        6-evaltext
        7-is_required
        8-hints
        9-bitmask
    ) } = caller(1);
    
    warn "$caller{'3-subr'}\n\t\tcalled from line [ $caller{'2-line'} ] in ($caller{'0-package'}) $caller{'1-filename'}\n";
}

sub _dumpAccessContext
{
    my $obj  = shift;
    my $start = 0;
    my %caller;

    for($start .. 5)
    {
        @caller{ qw(
            0-package
            1-filename
            2-line
            3-subr
            4-has_args
            5-wantarray
            6-evaltext
            7-is_required
            8-hints
            9-bitmask
        ) } = caller($_);

        last unless defined $caller{'0-package'};
        
        warn "\ncaller $_\n" . "-"x50 . "\n";
        $obj->_dumpCaller(\%caller);
    }
}

sub _dumpCaller
{
    my $obj = shift;
    my $caller = shift;
    warn "\n" . (
        join "\n", map
        {
            sprintf "\t%-15s = %-15s", $_,
                defined $caller->{$_}
                    ? $caller->{$_}
                    : 'undef'
        } sort keys %$caller) . "\n\n";
}

sub _callerContext
{
    my ($obj,$level) = @_;
    my $caller = (caller($level || 2))[0];
    
    # ugly hack that needs to be fix
    defined $caller && $caller =~ s/POOF::TEMPORARYNAMESPACE//o;
    
    my $relationship = $obj->_relationship($caller,$obj->{'self'});
    
    return
        $relationship eq 'self'
            ? 0                         # 'private' 
            : $relationship eq 'child'
                ? 1                     # 'protected'
                : $relationship eq 'parent' 
                    ? -1                 # parent has not visibility into children
                    : 2                 # 'public';
                    
}

sub _relationship
{
    my $obj = shift;
    my ($class1,$class2) = map { $_ ? ref $_ ? ref $_ : $_ : '' } @_;

    return 'self' if $class1 eq $class2;

    my %family1 = map { $_ => 1 } Class::ISA::super_path( $class1 );
    my %family2 = map { $_ => 1 } Class::ISA::super_path( $class2 );

    return
        exists $family1{ $class2 }
            ? 'child'
            : exists $family2{ $class1 } 
                ? 'parent' 
                : 'unrelated';
}

sub _classOrChild
{
    my ($obj,$level) = @_;
    my $caller = (caller($level || 2))[0];
    
    my $relationship = $obj->_relationship($caller,$obj);

    return
        $relationship eq 'self'
            ? 1                         # 'private'  
            : $relationship eq 'child'
                ? 1                     # 'protected' 
                : $relationship eq 'parent'
                    ? 1                 # parent has visibility into children
                    : 0                 # 'public';
    
}

sub _enforcement
{
    my $obj = shift;
    # enforce encapsulation
    confess "Access violation"
        unless $obj->{'def'}->{'access'} >= $obj->_callerContext(@_) || $obj->_classOrChild(@_);
    return $obj;
}
  

1;
__END__

=head1 NAME

POOF::Properties::Array - Utility class used by POOF::Collection.

=head1 SYNOPSIS

It is not meant to be used directly.
  
=head1 SEE ALSO

POOF man page.

=head1 AUTHOR

Benny Millares <bmillares@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2007 by Benny Millares

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut

