package Catmandu::Fix::Bind::visitor;

use Moo;
use Data::Dumper;
use Catmandu::Util;

with 'Catmandu::Fix::Bind';

has path   => (is => 'ro');

sub unit {
    my ($self,$data) = @_;

    if (defined $self->path) {
        return Catmandu::Util::data_at($self->path,$data);
    }
    else {
        return $data;
    }
}

sub bind {
    my ($self,$mvar,$func,$name) = @_;

    if (Catmandu::Util::is_array_ref($mvar)) {
        return $self->bind_array($mvar,$func,'');
    }
    elsif (Catmandu::Util::is_hash_ref($mvar)) {
        return $self->bind_hash($mvar,$func,'');
    }
    else {
        return $self->bind_hash($mvar,$func,'');
    }
}

sub bind_scalar {
    my ($self,$mvar,$func,$parent) = @_;

    return $func->({ 'key' => $parent , 'scalar' => $mvar })->{'scalar'};
}

sub bind_array {
    my ($self,$mvar,$func,$parent) = @_;

    $mvar = $func->({ 'key' => $parent , 'array' => $mvar })->{'array'};

    my $new_var = [];
    
    for (my $i = 0 ; $i < @$mvar ; $i++) {
        my $item = $mvar->[$i];
        if (Catmandu::Util::is_array_ref($item)) {
            $mvar->[$i] = $self->bind_array($item,$func,$i);
        }
        elsif (Catmandu::Util::is_hash_ref($item)) {
            $mvar->[$i] = $self->bind_hash($item,$func,$i);
        }
        else {
            $mvar->[$i] = $func->({ 'key' => $i ,'scalar' => $item})->{'scalar'};
        }
    }

    return $mvar;
}

sub bind_hash {
    my ($self,$mvar,$func,$parent) = @_;

    $mvar = $func->({ 'key' => $parent , 'hash' => $mvar })->{'hash'};

    for my $key (keys %$mvar) {
        my $item = $mvar->{$key};

        if (Catmandu::Util::is_array_ref($item)) {
            $mvar->{$key} = $self->bind_array($item,$func,$key);
        }
        elsif (Catmandu::Util::is_hash_ref($item)) {
            $mvar->{$key} = $self->bind_hash($item,$func,$key);
        }
        else {
            $mvar->{$key} = $func->({ 'key' => $key , 'scalar' => $item })->{'scalar'};
        }
    }

    return $mvar;
}

=head1 NAME

Catmandu::Fix::Bind::visitor - a binder that computes Fix-es for every element in record 

=head1 SYNOPSIS

 # If data is like:
 
   numbers:
      - one
      - two
      - three
   person:
      name: jennie
      age: 44
      color:
         - green
         - purple
         - brown
         - more:
            foo: bar

 do visitor()
    upcase(scalar)  # upcase every scalar value in the record
 end

 # will produce

   numbers:
      - ONE
      - TWO
      - THREE
   person:
      name: JENNIE
      age: 44
      color:
         - GREEN
         - PURPLE
         - BROWN
         - more:
            foo: BAR

  do visitor()
    # upcase all the 'name' fields in the record
    if all_match(key,name)
      upcase(scalar)
    end
  end

=head1 DESCRIPTION

The visitor binder will iterate over all the elements in a record and preform fixes on them.

Special node names are availabe to process every visited element:

=over 4

=item scalar

Process a Fix on every scalar value. E.g.

   upcase(scalar)
   replace_all(scalar,'$','tested')

=item array

Process a Fix on every array value. E.g.

   sort_field(array)

Values need to be put in the 'array' field to be available for fixes. The scope of 
the array is limited to the array visited.

=item hash

Process a Fix on every hash value. E.g.

   copy_field(hash.age,hash.age2)

Values need to be put in the 'hash' field to be available for fixes. The scope of 
the hash is limited to the hash visited.

=item key

Provides access to the key on which the scalar,array or hash value is found. Eg.

   # Upcase all 'name' fields in the record
   if all_match(key,name)
      upcase(scalar)
   end

=back

=head1 CONFIGURATION

=head2 path 

A path in the data to visit:

  # Visit any field
  do visitor()
    ...
  end

  # Visit only the fields at my.deep.field
  do visitor(-path => my.deep.field )
    ...
  end

=head1 AUTHOR

Patrick Hochstenbach - L<Patrick.Hochstenbach@UGent.be>

=head1 SEE ALSO

L<Catmandu::Fix::Bind>

=cut

1;
