package HTML::Widgets::NavMenu::Tree::Iterator;

use strict;
use warnings;

use base qw(HTML::Widgets::NavMenu::Object);

use HTML::Widgets::NavMenu::Tree::Iterator::Stack;
use HTML::Widgets::NavMenu::Tree::Iterator::Item;

__PACKAGE__->mk_accessors(qw(
    coords
    stack
    ));

=head1 NAME

HTML::Widgets::NavMenu::Tree::Iterator - an iterator for HTML.

=head1 SYNOPSIS

For internal use only.

=head1 METHODS
=cut



sub _init
{
    my $self = shift;

    $self->stack(HTML::Widgets::NavMenu::Tree::Iterator::Stack->new());

    return 0;
}

=head2 $self->top()

Retrieves the stack top item.

=cut
sub top
{
    my $self = shift;
    return $self->stack()->top();
}

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

    return HTML::Widgets::NavMenu::Tree::Iterator::Item->new(
        $args
    );
}

=head2 $self->get_new_item({'node' => $node, 'parent_item' => $parent})

Gets the new item.

=cut

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

    my $node = $args->{'node'};
    my $parent_item = $args->{'parent_item'};

    return
        $self->_construct_new_item(
            {
                'node' => $node,
                'subs' => $self->get_node_subs( { 'node' => $node } ),
                'accum_state' => 
                    $self->get_new_accum_state(
                        {
                            'item' => $parent_item,
                            'node' => $node,
                        }
                    ),
            }
        );
}

sub _push_into_stack
{
    my $self = shift;

    my $node = shift;

    $self->stack()->push(
        $self->get_new_item(
            {
                'node' => $node,
                'parent_item' => $self->top(),
            }
        ),
    );
}

=head2 $self->traverse()

Traverses the tree.

=cut

sub traverse
{
    my $self = shift;

    $self->_push_into_stack($self->get_initial_node());

    $self->coords([]);

    my $top_item;

    MAIN_LOOP: while ($top_item = $self->top())
    {
        my $visited = $top_item->_is_visited();

        if (!$visited)
        {
            $self->node_start();
        }

        my $sub_item =
            ($self->node_should_recurse() ?
                $top_item->_visit() :
                undef);

        if (defined($sub_item))
        {
            push @{$self->coords()}, $top_item->_visited_index();
            $self->_push_into_stack(
                $self->get_node_from_sub(
                    {
                        'item' => $top_item,
                        'sub' => $sub_item,
                    }
                ),
            );
            next MAIN_LOOP;
        }
        else
        {
            $self->node_end();
            $self->stack->pop();
            pop(@{$self->coords()})
        }
    }

    return 0;
}

=head2 $self->get_node_from_sub()

This function can be overriden to generate a node from the sub-nodes
returned by get_node_subs() in a different way than the default.

=cut

sub get_node_from_sub
{
    my $self = shift;
    my $args = shift;

    return $args->{'sub'};
}

=head2 $self->find_node_by_coords($coords, $callback)

Finds a node by its coordinations.

=cut

sub find_node_by_coords
{
    my $self = shift;
    my $coords = shift;
    my $callback = shift || (sub { });

    my $idx = 0;
    my $item =
        $self->get_new_item(
            {
                'node' => $self->get_initial_node(),
            }
        );

    my $internal_callback =
        sub {
            $callback->(
                'idx' => $idx,
                'item' => $item,
                'self' => $self,
            );
        };
    
    $internal_callback->();
    foreach my $c (@$coords)
    {
        $item =
            $self->get_new_item(
                {
                    'node' =>
                    $self->get_node_from_sub(
                        {
                            'item' => $item,
                            'sub' => $item->_get_sub($c),
                        }
                    ),
                    'parent_item' => $item,
                }
            );
        $idx++;
        $internal_callback->();
    }
    return +{ 'item' => $item, };
}

=head2 $self->get_coords()

Returns the current coordinates of the object.

=cut

sub get_coords
{
    my $self = shift;

    return $self->coords();
}

=head1 COPYRIGHT & LICENSE

Copyright 2006 Shlomi Fish, all rights reserved.

This program is released under the following license: MIT X11.

=cut

1;