#!/usr/bin/perl -w

package WalkTree;
use strict;
use warnings;
use Carp;

BEGIN {
    use Exporter ();

    our $VERSION = 1.00; # version for version checking

    our @ISA = qw(Exporter);
    our @EXPORT = qw(WalkTree);     # exported functions

}


# ------------------------------------------------------------------------------
# Get the n-th child of the tree. Set some child's properties.
sub child {
    my ( $tree, $n ) = @_[0..1];

    my $child = $tree->{ 'children' }[ $n ];
    ref( $child ) or croak "Out of select ($n)";

    $child->{'PATH'} or $child->{'PATH'}= $tree->{'PATH'};
    $child->{'LEVEL'} = $tree->{'LEVEL'} + 1;
    $child->{'SEL'} = [ @{$tree->{'SEL'}}, $n ];
    $child->{'PARENT'} = $tree;
    return $child;
}

# ------------------------------------------------------------------------------
# Read a tree from file if required. Add some fields.
sub update_content {
    my ($tree) = @_;

    ref( $tree ) or $tree = { 'datfile' => $tree, 'PATH' => '' };

    $tree->{'base'} and do {
        $tree->{'PATH'} .= $tree->{'base'};
        delete $tree->{'base'};
    };

    $tree->{'datfile'} and do {
        # absolute path
        $tree->{'datfile'} =~ m<^/> and $tree->{'PATH'} = '';

        my $datfile = $tree->{'PATH'} . $tree->{'datfile'};
        my $data = do( $datfile ) or croak "Read of $datfile failed\n($@)";

        my @stat = stat( $datfile );
        $tree->{'MTIME'} = $stat[9];

        # update path
        $tree->{'datfile'} =~ /^(.*\/)([^\/]+)$/ and do {
            $tree->{'PATH'} .= $1;
            $tree->{'datfile'} = $2;
        };

        $tree->{'DATFILE'} = $tree->{'datfile'};

        # Add fields from datfile, keep existing.
        foreach my $k ( keys( %{$data} ) ) {
            exists( $tree->{$k} ) or $tree->{$k} = $data->{$k};
        }

        delete( $tree->{'datfile'} );

        $tree->{'base'} and do {
            $tree->{'PATH'} .= $tree->{'base'};
            delete $tree->{'base'};
        };
    };

    $tree->{'PATH'} or $tree->{'PATH'} = '';
    defined( $tree->{'LEVEL'} ) or $tree->{'LEVEL'} = 0;
    defined( $tree->{'SEL'} ) or $tree->{'SEL'} = [];

    return $tree;
}

# ------------------------------------------------------------------------------
sub WalkTree {
    my ($tree, $dispatch, $sel) = splice( @_, 0, 3 );

    $sel or $sel = [];

    $tree = update_content( $tree );

    # Node( tree, last, ... )
    foreach my $i ( @{$sel} ) {
        $dispatch->Node( $tree, 0, @_ );
        $tree = child( $tree, $i );
        $tree = update_content( $tree );
    }

    $dispatch->Node( $tree, 1, @_ );

    #children
    my $children = $tree->{ 'children' };
    my $t = 0;
    my $method;

    $dispatch->StartChildren( $tree, @_ );

    while( $t <= $#{$children} ) {
        if( ref( $children->[$t] ) ) {    # subfolder
            $dispatch->Child( update_content( child( $tree, $t ) ), $tree, @_ );
            ++ $t;
        } else {
            $method = $$children[$t];
            $dispatch->$method( $children->[$t+1], $tree, @_ );
            $t += 2;
        }
    }

    $dispatch->EndChildren( $tree, @_ );

    return $tree;
}

1;
