package Excel::Template::Iterator;

use strict;

BEGIN {
    use vars qw(@ISA);
    @ISA = qw(Excel::Template::Base);

    use Excel::Template::Base;
}

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);

    unless (Excel::Template::Factory::isa($self->{CONTEXT}, 'CONTEXT'))
    {
        die "Internal Error: No context object passed to ", __PACKAGE__, $/;
    }

    $self->{MAXITERS} ||= 0;

    # This is the index we will work on NEXT, in whatever direction the
    # iterator is going.
    $self->{INDEX} = -1;

    # This is a short-circuit parameter to let the iterator function in a
    # null state.
    $self->{NO_PARAMS} = 0;
    unless ($self->{NAME} =~ /\w/)
    {
        $self->{NO_PARAMS} = 1;

        warn "INTERNAL ERROR: 'NAME' was blank was blank when passed to ", __PACKAGE__, $/ if $^W;

        return $self;
    }

    # Cache the reference to the appropriate data.
    $self->{DATA} = $self->{CONTEXT}->param($self->{NAME});

    unless (ref $self->{DATA} eq 'ARRAY')
    {
        $self->{NO_PARAMS} = 1;
        warn "'$self->{NAME}' does not have a list of parameters", $/ if $^W;

        return $self;
    }

    unless (@{$self->{DATA}})
    {
        $self->{NO_PARAMS} = 1;
    }

    $self->{MAX_INDEX} = $#{$self->{DATA}};

    return $self;
}

sub enter_scope
{
    my $self = shift;

    return 0 if $self->{NO_PARAMS};

    for my $x ($self->{DATA}[$self->{INDEX}])
    {
        $x->{uc $_} = delete $x->{$_} for keys %$x;
    }

    push @{$self->{CONTEXT}{PARAM_MAP}}, $self->{DATA}[$self->{INDEX}];

    return 1;
}

sub exit_scope
{
    my $self = shift;

    return 0 if $self->{NO_PARAMS};

    # There has to be the base parameter map and at least the one that
    # Iterator::enter_scope() added on top.
    @{$self->{CONTEXT}{PARAM_MAP}} > 1 ||
        die "Internal Error: ", __PACKAGE__, "'s internal param_map off!", $/;

    pop @{$self->{CONTEXT}{PARAM_MAP}};

    return 1;
}

sub can_continue
{
    my $self = shift;

    return 0 if $self->{NO_PARAMS};

    return 1 if $self->more_params;

    return 0;
}

sub more_params
{
    my $self = shift;

    return 0 if $self->{NO_PARAMS};

    return 1 if $self->{MAX_INDEX} > $self->{INDEX};

    return 0;
}

# Call this method BEFORE incrementing the index to the next value.
sub _do_globals
{
    my $self = shift;

    my $data = $self->{DATA}[$self->{INDEX}];

    # Perl's arrays are 0-indexed. Thus, the first element is at index "0".
    # This means that odd-numbered elements are at even indices, and vice-versa.
    # This also means that MAX (the number of elements in the array) can never
    # be the value of an index. It is NOT the last index in the array.

    $data->{'__FIRST__'} ||= ($self->{INDEX} == 0);
    $data->{'__INNER__'} ||= (0 < $self->{INDEX} && $self->{INDEX} < $self->{MAX_INDEX});
    $data->{'__LAST__'}  ||= ($self->{INDEX} == $self->{MAX_INDEX});
    $data->{'__ODD__'}   ||= !($self->{INDEX} % 2);

    return 1;
}

sub next
{
    my $self = shift;

    return 0 if $self->{NO_PARAMS};

    return 0 unless $self->more_params;

    $self->exit_scope;

    $self->{INDEX}++;

    $self->_do_globals;

    $self->enter_scope;

    return 1;
}

# This method doesn't seem to be used ...
# If it is reinstated, here's the POD for it
#=head2 back_up
#
#Go to the previous iteration of the loop
#
#sub back_up
#{
#    my $self = shift;
#
#    return 0 if $self->{NO_PARAMS};
#
#    $self->exit_scope;
#
#    $self->{INDEX}--;
#
#    $self->_do_globals;
#
#    $self->enter_scope;
#
#    return 1;
#}

# This method doesn't seem to be used ...
# If it is reinstated, here's the POD for it
#=head2 reset
#
#Resets the iterator
#
#sub reset
#{
#    my $self = shift;
#
#    return 0 if $self->{NO_PARAMS};
#
#    $self->{INDEX} = -1;
#
#    return 1;
#}

1;
__END__

=head1 NAME

Excel::Template::Iterator

=head1 PURPOSE

This is meant for internal use only. Documentation is provided for subclassing.

=head1 NODE NAME

None

=head1 INHERITANCE

None

=head1 ATTRIBUTES

None

=head1 CHILDREN

None

=head1 AFFECTS

This is a helper class for LOOP

=head1 DEPENDENCIES

None

=head1 METHODS

=head2 can_continue

Determines if the iterator can continue.

Currently, this wraps more_params(), but there other possible situations, such as the page ending.

=head2 more_params

Determines if the iterator for the loop has more parameters that it can consume

=head2 next

Go to the next iteration of the loop

=head1 AUTHOR

Rob Kinyon (rob.kinyon@gmail.com)

=head1 SEE ALSO

LOOP

=cut
