package Iter;

=head1 Iterator Module

A set of iterator functions for traversing the various trees and indexes.
Each iterator expects closures that operate on the elements in the iterated
data structure.


=head2 Generic

	Params: $node, &$loopsub, &$skipsub, &$applysub, &$recursesub

Iterate over $node\'s children. For each iteration:
	
If loopsub( $node, $kid ) returns false, the loop is terminated.
If skipsub( $node, $kid )  returns true, the element is skipped.

Applysub( $node, $kid ) is called
If recursesub( $node, $kid ) returns true, the function recurses into
the current node.

=cut

sub Generic
{
	my ( $root, $loopcond, $skipcond, $applysub, $recursecond ) = @_;

	return sub {
		foreach my $node ( @{$root->{Kids}} ) {

			if ( defined  $loopcond ) {
				return 0 unless $loopcond->( $root, $node );
			}

			if ( defined $skipcond ) {
				next if $skipcond->( $root, $node );
			}

			my $ret = $applysub->( $root, $node );
			return $ret if defined $ret && $ret;

			if ( defined $recursecond 
					&& $recursecond->( $root, $node ) ) {
				$ret = Generic( $node, $loopcond, $skipcond,
						$applysub, $recursecond)->();
				if ( $ret ) {
					return $ret;
				}
			}
		}

		return 0;
	};
}

sub Class
{
	my ( $root, $applysub, $recurse ) = @_;

	return Generic( $root, undef,
		sub {
			return !( $node->{NodeType} eq "class" 
				|| $node->{NodeType} eq "struct" );
		}, 
		$applysub, $recurse );
}

=head2 Tree

	Params: $root, $recurse?, $commonsub, $compoundsub, $membersub,
		$skipsub

Traverse the ast tree starting at $root, skipping if skipsub returns true.

Applying $commonsub( $node, $kid),
then $compoundsub( $node, $kid ) or $membersub( $node, $kid ) depending on
the Compound flag of the node.

=cut

sub Tree
{
	my ( $rootnode, $recurse, $commonsub, $compoundsub, $membersub, 
		 $skipsub ) = @_;

	my $recsub = $recurse ? sub { return 1 if $_[1]->{Compound}; } 
				: undef;

	Generic( $rootnode, undef, $skipsub,
		sub { 					# apply
			my ( $root, $node ) = @_;
			my $ret;

			if ( defined $commonsub ) {
				$ret = $commonsub->( $root, $node );
				return $ret if defined $ret;
			}

			if ( $node->{Compound} && defined $compoundsub ) {
				$ret = $compoundsub->( $root, $node );
				return $ret if defined $ret;
			}
			
			if( !$node->{Compound} && defined $membersub ) {
				$ret = $membersub->( $root, $node );
				return $ret if defined $ret;
			}
			return;
		},
		$recsub 				# skip
	)->();
}

=head2 LocalCompounds

Apply $compoundsub( $node ) to all locally defined compound nodes
(ie nodes that are not external to the library being processed).

=cut

sub LocalCompounds
{
		my ( $rootnode, $compoundsub ) = @_;

		return unless defined $rootnode && defined $rootnode->{Kids};

		foreach my $kid ( sort { $a->{astNodeName} cmp $b->{astNodeName} }
								 @{$rootnode->{Kids}} ) {
				next if !defined $kid->{Compound};

				$compoundsub->( $kid ) unless defined $kid->{ExtSource};
				LocalCompounds( $kid, $compoundsub );
		}
}

=head2 Hierarchy

	Params: $node, $levelDownSub, $printSub, $levelUpSub

This allows easy hierarchy traversal and printing.

Traverses the inheritance hierarchy starting at $node, calling printsub
for each node. When recursing downward into the tree, $levelDownSub($node) is
called, the recursion takes place, and $levelUpSub is called when the
recursion call is completed. 

=cut

sub Hierarchy
{
	my ( $node, $ldownsub, $printsub, $lupsub, $nokidssub ) = @_;

	return if defined $node->{ExtSource}
		&& (!defined $node->{InBy} 
			|| !kdocAstUtil::hasLocalInheritor( $node ));

	$printsub->( $node );

	if ( defined $node->{InBy} ) {
		$ldownsub->( $node );

		foreach my $kid ( 
				sort {$a->{astNodeName} cmp $b->{astNodeName}}
				@{ $node->{InBy} } ) {
			Hierarchy( $kid, $ldownsub, $printsub, $lupsub );
		}

		$lupsub->( $node );
	}
	elsif ( defined $nokidssub ) {
		$nokidssub->( $node );
	}

	return;
}

=head2

	Call $printsub for each *direct* ancestor of $node.
	Only multiple inheritance can lead to $printsub being called more than once.

=cut
sub Ancestors
{
	my ( $node, $rootnode, $noancessub, $startsub, $printsub,
		$endsub ) = @_;
	my @anlist = ();

	return if $node eq $rootnode;

	if ( !exists $node->{InList} ) {
		$noancessub->( $node ) unless !defined $noancessub;
		return;
	}
	
	foreach my $innode ( @{ $node->{InList} } ) {
		my $nref = $innode->{Node};	# real ancestor
		next if defined $nref && $nref == $rootnode;

		push @anlist, $innode;
	}

	if ( $#anlist < 0 ) {
		$noancessub->( $node ) unless !defined $noancessub;
		return;
	}

	$startsub->( $node ) unless !defined $startsub;

	foreach my $innode ( sort { $a->{astNodeName} cmp $b->{astNodeName} }
				@anlist ) {

		# print
		$printsub->( $innode->{Node}, $innode->{astNodeName},
			$innode->{Type}, $innode->{TmplType} ) 
			unless !defined $printsub;
	}

	$endsub->( $node ) unless !defined $endsub;

	return;

}

sub Descendants
{
	my ( $node, $nodescsub, $startsub, $printsub, $endsub ) = @_;

	if ( !exists $node->{InBy} ) {
		$nodescsub->( $node ) unless !defined $nodescsub;
		return;
	}

	
	my @desclist = ();
	DescendantList( \@desclist, $node );
	
	if ( $#desclist < 0 ) {
		$nodescsub->( $node ) unless !defined $nodescsub;
		return;
	}

	$startsub->( $node ) unless !defined $startsub;

	foreach my $innode ( sort { $a->{astNodeName} cmp $b->{astNodeName} }
				@desclist ) {

		$printsub->( $innode) 
			unless !defined $printsub;
	}

	$endsub->( $node ) unless !defined $endsub;

	return;

}

sub DescendantList
{
	my ( $list, $node ) = @_;

	return unless exists $node->{InBy};

	foreach my $kid ( @{ $node->{InBy} } ) {
		push @$list, $kid;
		DescendantList( $list, $kid );
	}
}

=head2 DocTree

=cut

sub DocTree
{
	my ( $rootnode, $allowforward, $recurse, 
		$commonsub, $compoundsub, $membersub ) = @_;
		
	Generic( $rootnode, undef,
		sub {				# skip
			my( $node, $kid ) = @_;

			unless (!(defined $kid->{ExtSource}) 
					&& ($allowforward || $kid->{NodeType} ne "Forward")
					&& ($main::doPrivate || !($kid->{Access} =~ /private/))
					&& exists $kid->{DocNode} ) {

				return 1;
			}

			return;
		},
		sub { 				# apply
			my ( $root, $node ) = @_;

			my $ret;

			if ( defined $commonsub ) {
				$ret = $commonsub->( $root, $node );
				return $ret if defined $ret;
			}

			if ( $node->{Compound} && defined $compoundsub ) {
				$ret = $compoundsub->( $root, $node );
				return $ret if defined $ret;
			}
			elsif( defined $membersub ) {
				$ret = $membersub->( $root, $node );
				return $ret if defined $ret;
			}

			return;
		},
		sub { return 1 if $recurse; return; }	# recurse
		)->();

}

sub MembersByType
{
	my ( $node, $startgrpsub, $methodsub, $endgrpsub, $nokidssub ) = @_;

# public
	# types
	# data
	# methods
	# signals
	# slots
	# static
# protected
# private (if enabled)

	if ( !defined $node->{Kids} ) {
			$nokidssub->( $node ) if defined $nokidssub;
			return;
	}

	foreach my $acc ( qw/public protected private/ ) {
		next if $acc eq "private" && !$main::doPrivate;
		$access = $acc;

		my @types = ();
		my @data = ();
		my @signals = ();
		my @k_dcops = ();
		my @k_dcop_signals = ();
		my @k_dcop_hiddens = ();
		my @slots =();
		my @methods = ();
		my @static = ();
		my @modules = ();
		my @interfaces = ();

		# Build lists
		foreach my $kid ( @{$node->{Kids}} ) {
			next unless ( $kid->{Access} =~ /$access/
			          && !$kid->{ExtSource})
			         || ( $access eq "public" 
				    && ( $kid->{Access} eq "signals" 
				      || $kid->{Access} =~ "k_dcop" # note the =~ 
                  || $kid->{Access} eq "K_DCOP"));

			my $type = $kid->{NodeType};

			if ( $type eq "method" ) {
				if ( $kid->{Flags} =~ "s" ) {
					push @static, $kid;
				}
				elsif ( $kid->{Flags} =~ "l" ) {
					push @slots, $kid;
				}
				elsif ( $kid->{Flags} =~ "n" ) {
					push @signals, $kid;
				}
				elsif ( $kid->{Flags} =~ "d" ) {
					push @k_dcops, $kid;
				}
				elsif ( $kid->{Flags} =~ "z" ) {
					push @k_dcop_signals, $kid;
				}
				elsif ( $kid->{Flags} =~ "y" ) {
					push @k_dcop_hiddens, $kid;
				}
				else {
					push @methods, $kid; }
			}
			elsif ( $kid->{Compound} ) {
				if ( $type eq "module" ) {
					push @modules, $kid;
				}
				elsif ( $type eq "interface" ) {
					push @interfaces, $kid;
				}
				else {
					push @types, $kid;
				}
			}
			elsif ( $type eq "typedef" || $type eq "enum" ) {
				push @types, $kid;
			}
			else {
				push @data, $kid;
			}
		}

		# apply
		$uc_access = ucfirst( $access );
		
		doGroup( "$uc_access Types", $node, \@types, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "Modules", $node, \@modules, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "Interfaces", $node, \@interfaces, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "$uc_access Methods", $node, \@methods, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "$uc_access Slots", $node, \@slots, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "Signals", $node, \@signals, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "k_dcop", $node, \@k_dcops, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "k_dcop_signals", $node, \@k_dcop_signals, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "k_dcop_hiddens", $node, \@k_dcop_hiddens, $startgrpsub,
			$methodsub, $endgrpsub);
		doGroup( "$uc_access Static Methods", $node, \@static, 
			$startgrpsub, $methodsub, $endgrpsub);
		doGroup( "$uc_access Members", $node, \@data, $startgrpsub,
			$methodsub, $endgrpsub);
	}
}

sub doGroup
{
	my ( $name, $node, $list, $startgrpsub, $methodsub, $endgrpsub ) = @_;

        my ( $hasMembers ) = 0;
        foreach my $kid ( @$list ) {
                if ( !exists $kid->{DocNode}->{Reimplemented} ) {
                        $hasMembers = 1;
                        break;
                }
        }
	return if !$hasMembers;
	
	if ( defined $methodsub ) {
		foreach my $kid ( @$list ) {
                        if ( !exists $kid->{DocNode}->{Reimplemented} ) {
         		        $methodsub->( $node, $kid );
                        }
		}
	}

	$endgrpsub->( $name ) if defined $endgrpsub;
}

sub ByGroupLogical
{
	my ( $root, $startgrpsub, $itemsub, $endgrpsub ) = @_;

	return 0 unless defined $root->{Groups};

	foreach my $groupname ( sort keys %{$root->{Groups}} ) {
		next if $groupname eq "astNodeName"||$groupname eq "NodeType";

		my $group = $root->{Groups}->{ $group };
		next unless $group->{Kids};
		
		$startgrpsub->( $group->{astNodeName}, $group->{Desc} );

		foreach my $kid (sort {$a->{astNodeName} cmp $b->{astNodeName}}
					@group->{Kids} ) {
			$itemsub->( $root, $kid );
		}
		$endgrpsub->( $group->{Desc} );	
	}

	return 1;
}

sub SeeAlso
{
	my ( $node, $nonesub, $startsub, $printsub, $endsub ) = @_;

	if( !defined $node ) {
		$nonesub->();
		return;
	}

	my $doc = $node;

	if ( $node->{NodeType} ne "DocNode" ) {
		$doc = $node->{DocNode};
		if ( !defined $doc ) {
			$nonesub->() if defined $nonesub;
			return;
		}
	}

	if ( !defined $doc->{See} ) {
		$nonesub->() if defined $nonesub;
		return;
	}

	my $see = $doc->{See};
	my $ref = $doc->{SeeRef};

	if ( $#$see < 1 ) {
		$nonesub->() if defined $nonesub;
		return;
	}

	$startsub->( $node ) if defined $startsub;

	for my $i ( 0..$#$see ) {
		my $seelabel = $see->[ $i ];
		my $seenode = undef;
		if ( defined $ref ) {
			$seenode = $ref->[ $i ]; 
		}

		$printsub->( $seelabel, $seenode ) if defined $printsub;
	}

	$endsub->( $node ) if defined $endsub;

	return;
}

1;