use strict;
use warnings;

use PDL::Types qw(ppdefs_all types);
my $A = [ppdefs_all()];
my $AF = [map $_->ppsym, grep !$_->integer, types()]; # all including complex
$AF = [(grep $_ ne 'D', @$AF), 'D']; # so defaults to D if non-float given

#########################################################

pp_addhdr('
#include <math.h>
');

pp_add_exported( '',
		 'badflag check_badflag badvalue orig_badvalue nbad nbadover ngood ngoodover ' .
		 'setbadat ' );

## Header
pp_addpm({At=>'Top'},<<'!NO!SUBS!');

=head1 NAME

PDL::Bad - PDL always processes bad values

=head1 DESCRIPTION

This module is loaded when you do C<use PDL>,
C<use PDL::Lite> or C<use PDL::LiteF>.

Implementation details are given in
L<PDL::BadValues>.

=head1 SYNOPSIS

 use PDL::Bad;
 print "\nBad value per PDL support in PDL is turned " .
     $PDL::Bad::PerPdl ? "on" : "off" . ".\n";

=head1 VARIABLES

=over 4

=item $PDL::Bad::UseNaN

Set to 0 as of PDL 2.040, as no longer available, though NaN can be used
as a badvalue for a given PDL object.

=item $PDL::Bad::PerPdl

Set to 1 as of PDL 2.040 as always available.

=item $PDL::Bad::Status

Set to 1 as of PDL 2.035 as always available.

=back

=cut

!NO!SUBS!

pp_addpm(<<'!NO!SUBS!');

$PDL::Bad::Status = 1;
$PDL::Bad::UseNaN = 0;
$PDL::Bad::PerPdl = 1;

use strict;

use PDL::Types;
use PDL::Primitive;

############################################################
############################################################

!NO!SUBS!

pp_addpm(<<'!NO!SUBS!');
############################################################
############################################################

*badflag         = \&PDL::badflag;
*badvalue        = \&PDL::badvalue;
*orig_badvalue   = \&PDL::orig_badvalue;

############################################################
############################################################

=head2 badflag

=for ref

getter/setter for the bad data flag

=for example

  if ( $x->badflag() ) {
    print "Data may contain bad values.\n";
  }
  $x->badflag(1);      # set bad data flag
  $x->badflag(0);      # unset bad data flag

When called as a setter, this modifies the ndarray on which
it is called. This always returns a Perl scalar with the
final value of the bad flag.

A return value of 1 does not guarantee the presence of
bad data in an ndarray; all it does is say that we need to
I<check> for the presence of such beasties. To actually
find out if there are any bad values present in an ndarray,
use the L</check_badflag> method.

=for bad

This function works with ndarrays that have bad values. It
always returns a Perl scalar, so it never returns bad values.

=head2 badvalue

=for ref

returns (or sets) the value used to indicate a missing (or bad) element
for the given ndarray type. You can give it an ndarray,
a PDL::Type object, or one of C<$PDL_B>, C<$PDL_S>, etc.

=for example

   $badval = badvalue( float );
   $x = ones(ushort,10);
   print "The bad data value for ushort is: ",
      $x->badvalue(), "\n";

This can act as a setter (e.g. C<< $x->badvalue(23) >>),
including with the value C<NaN> for floating-point types.
Note that this B<doesn't change the data in the ndarray> for
floating-point-typed ndarrays.
That is, if C<$x> already has bad values, they will not
be changed to use the given number and if any elements of
C<$x> have that value, they will unceremoniously be marked
as bad data. See L</setvaltobad>, L</setbadtoval>, and
L</setbadif> for ways to actually modify the data in ndarrays

It I<does> change data for integer-typed arrays, changing values that
had the old bad value to have the new one.

It is possible to change the bad value on a per-ndarray basis, so

    $x = sequence (10);
    $x->badvalue (3); $x->badflag (1);
    $y = sequence (10);
    $y->badvalue (4); $y->badflag (1);

will set $x to be C<[0 1 2 BAD 4 5 6 7 8 9]> and $y to be
C<[0 1 2 3 BAD 5 6 7 8 9]>.

=for bad

This method does not care if you call it on an input ndarray
that has bad values. It always returns an ndarray
with the current or new bad value.

=cut

sub PDL::badvalue {
    my ( $self, $val ) = @_;
    my $num;
    if ( UNIVERSAL::isa($self,"PDL") ) {
	$num = $self->get_datatype;
	if ( $num < $PDL_F && defined($val) && $self->badflag ) {
	    $self->inplace->setbadtoval( $val );
	    $self->badflag(1);
	}
	return PDL::Bad::_badvalue_per_pdl_int($self, $val, $num);
    } elsif ( UNIVERSAL::isa($self,"PDL::Type") ) {
	$num = $self->enum;
    } else {
        # assume it's a number
        $num = $self;
    }
    PDL::Bad::_badvalue_int( $val, $num );
}

=head2 orig_badvalue

=for ref

returns the original value used to represent bad values for
a given type.

This routine operates the same as L</badvalue>,
except you can not change the values.

It also has an I<awful> name.

=for example

   $orig_badval = orig_badvalue( float );
   $x = ones(ushort,10);
   print "The original bad data value for ushort is: ", 
      $x->orig_badvalue(), "\n";

=for bad

This method does not care if you call it on an input ndarray
that has bad values. It always returns an ndarray
with the original bad value for the associated type.

=cut

sub PDL::orig_badvalue {
    no strict 'refs';
    my $self = shift;
    my $num;
    if ( UNIVERSAL::isa($self,"PDL") ) {
	$num = $self->get_datatype;
    } elsif ( UNIVERSAL::isa($self,"PDL::Type") ) {
	$num = $self->enum;
    } else {
        # assume it's a number
        $num = $self;
    }
    PDL::Bad::_default_badvalue_int($num);
}

=head2 check_badflag

=for ref

Clear the badflag of an ndarray if it does not
contain any bad values

Given an ndarray whose bad flag is set, check whether it
actually contains any bad values and, if not, clear the flag.
It returns the final state of the badflag.

=for example

 print "State of bad flag == ", $pdl->check_badflag;

=for bad

This method accepts ndarrays with or without bad values. It
returns an ndarray with the final badflag.

=cut

*check_badflag = \&PDL::check_badflag;

sub PDL::check_badflag {
    my $pdl = shift;
    $pdl->badflag(0) if $pdl->badflag and $pdl->nbad == 0;
    return $pdl->badflag;
} # sub: check_badflag()

!NO!SUBS!

pp_addxs(<<'EOF');
pdl *
_badvalue_int(val, type)
    PDL_Anyval val
    int type
  CODE:
    if ( val.type != -1 ) {
#define X_OUTER(datatype, ctype, ppsym, ...) \
      ctype cnewval = val.value.ppsym;
#define X_INNER(datatype, ctype, ppsym, ...) \
      PDL->bvals.ppsym = cnewval;
      PDL_GENERICSWITCH2(
        PDL_TYPELIST_ALL, val.type, X_OUTER, croak("Not a known data type code=%d", val.type),
        PDL_TYPELIST_ALL_, type, X_INNER, croak("Not a known data type code=%d", type))
#undef X_OUTER
#undef X_INNER
    }
    PDL_Anyval newval = {type, {0}};
    pdl* p = PDL->scalar(newval);
    if (!p) PDL->pdl_barf("Error making new pdl");
#define X(datatype, ctype, ppsym, ...) \
    *((ctype *)p->data) = PDL->bvals.ppsym;
    PDL_GENERICSWITCH(PDL_TYPELIST_ALL, type, X, croak("Not a known data type code=%d", type))
#undef X
    RETVAL = p;
  OUTPUT:
    RETVAL

pdl *
_badvalue_per_pdl_int(pdl_val, val, type)
    pdl* pdl_val
    PDL_Anyval val
    int type
  CODE:
    if ( val.type != -1) {
       PDL_Anyval typedval;
       ANYVAL_TO_ANYVAL_NEWTYPE(val, typedval, pdl_val->datatype);
       if (typedval.type < 0) PDL->pdl_barf("Error making typedval");
#define X(datatype, ctype, ppsym, ...) \
       pdl_val->badvalue.type = typedval.type; \
       pdl_val->badvalue.value.ppsym = typedval.value.ppsym;
       PDL_GENERICSWITCH(PDL_TYPELIST_ALL, pdl_val->datatype, X, croak("Not a known data type code=%d", type))
#undef X
       pdl_val->has_badvalue = 1;
       PDL->propagate_badvalue( pdl_val );
    }
    PDL_Anyval newval = {type, {0}};
    pdl* p = PDL->scalar(newval);
    if (!p) PDL->pdl_barf("Error making new pdl");
    if (pdl_val->has_badvalue == 0) {
#define X(datatype, ctype, ppsym, ...) \
       *((ctype *)p->data) = PDL->bvals.ppsym;
       PDL_GENERICSWITCH(PDL_TYPELIST_ALL, type, X, croak("Not a known data type code=%d", type))
#undef X
    } else {
#define X_OUTER(datatype, ctype, ...) \
      ctype *coutvalp = p->data;
#define X_INNER(datatype, ctype, ppsym, ...) \
      *coutvalp = pdl_val->badvalue.value.ppsym;
      PDL_GENERICSWITCH2(
        PDL_TYPELIST_ALL, type, X_OUTER, croak("Not a known data type code=%d", type),
        PDL_TYPELIST_ALL_, pdl_val->badvalue.type, X_INNER, croak("Not a known data type code=%d", pdl_val->badvalue.type))
#undef X_OUTER
#undef X_INNER
    }
    RETVAL = p;
  OUTPUT:
    RETVAL

pdl *
_default_badvalue_int(type)
    int type
  CODE:
    PDL_Anyval val = {type, {0}};
    pdl* p = PDL->scalar(val);
    if (!p) PDL->pdl_barf("Error making new pdl");
#define X(datatype, ctype, ppsym, shortctype, defbval, ...) \
    *((ctype *)p->data) = defbval;
    PDL_GENERICSWITCH(PDL_TYPELIST_ALL, type, X, croak("Not a known data type code=%d", type))
#undef X
    RETVAL = p;
  OUTPUT:
    RETVAL
EOF

pp_def('isbad',
  Pars => q(a(); int [o]b()),
  Doc => <<'EOF',
=for ref

Returns a binary mask indicating which values of
the input are bad values

Returns a 1 if the value is bad, 0 otherwise.
Similar to L<isfinite|PDL::Math/isfinite>.

=for example

 $x = pdl(1,2,3);
 $x->badflag(1);
 set($x,1,$x->badvalue);
 $y = isbad($x);
 print $y, "\n";
 [0 1 0]
EOF
  BadDoc => <<'EOF',
This method works with input ndarrays that are bad. The output ndarray
will never contain bad values, but its bad value flag will be the
same as the input ndarray's flag.
EOF
  HandleBad => 1,
  Code => '$b() = PDL_IF_BAD($ISBAD(a()),0);',
  GenericTypes => $A,
);

pp_def('isgood',
  Pars => q(a(); int [o]b()),
  Doc => <<'EOF',
=for ref

Is a value good?

Returns a 1 if the value is good, 0 otherwise.
Also see L<isfinite|PDL::Math/isfinite>.

=for example

 $x = pdl(1,2,3);
 $x->badflag(1);
 set($x,1,$x->badvalue);
 $y = isgood($x);
 print $y, "\n";
 [1 0 1]
EOF
  BadDoc => <<'EOF',
This method works with input ndarrays that are bad. The output ndarray
will never contain bad values, but its bad value flag will be the
same as the input ndarray's flag.
EOF
  HandleBad => 1,
  Code => '$b() = PDL_IF_BAD($ISGOOD(a()),1);',
  GenericTypes => $A,
);

# perhaps these should have pm code which returns the
# answer if the bad flag is not set
pp_def('nbadover',
  Pars => q(a(n); indx [o] b()),
  Doc => <<'EOF',
=for ref

Find the number of bad elements along the 1st dimension.

This function reduces the dimensionality of an ndarray by one by finding the
number of bad elements along the 1st dimension. In this sense it shares
much in common with the functions defined in L<PDL::Ufunc>. In particular,
by using L<xchg|PDL::Slices/xchg> and similar dimension rearranging methods,
it is possible to perform this calculation over I<any> dimension.
EOF
  BadDoc => <<'EOF',
nbadover processes input values that are bad. The output ndarray will not have
any bad values, but the bad flag will be set if the input ndarray had its bad
flag set.
EOF
  HandleBad => 1,
  Code => q{
    PDL_Indx cnt = 0;
    PDL_IF_BAD(loop(n) %{ if ( $ISBAD(a()) ) { cnt++; } %},)
    $b() = cnt;
  },
  GenericTypes => $A,
);

pp_def('ngoodover',
  Pars => q(a(n); indx [o] b()),
  Doc => <<'EOF',
=for ref

Find the number of good elements along the 1st dimension.

This function reduces the dimensionality of an ndarray
by one by finding the number of good elements
along the 1st dimension.

By using L<xchg|PDL::Slices/xchg> etc. it is possible to use
I<any> dimension.
EOF
  BadDoc => <<'EOF',
ngoodover processes input values that are bad. The output ndarray will not have
any bad values, but the bad flag will be set if the input ndarray had its bad
flag set.
EOF
  HandleBad => 1,
  Code => 
   'PDL_Indx cnt = PDL_IF_BAD(0,$SIZE(n));
    PDL_IF_BAD(loop(n) %{ if ( $ISGOOD(a()) ) { cnt++; } %},)
    $b() = cnt;',
  GenericTypes => $A,
);

# Generate small ops functions to do entire array
foreach my $op ( 
	  ['nbad','nbadover'],
	  ['ngood','ngoodover'],
	  ) {
    pp_addpm(<<"EOD");

*$op->[0] = \\&PDL::$op->[0];
sub PDL::$op->[0] {
	my(\$x) = \@_; my \$tmp;
	\$x->flat->$op->[1](\$tmp=PDL->nullcreate(\$x) );
	return \$tmp;
}
EOD

} # for $op

pp_addpm(<<'!NO!SUBS!');

=head2 nbad

=for ref

Returns the number of bad values in an ndarray

=for bad

Accepts good and bad input ndarrays; output is an ndarray
and is always good.

=head2 ngood

=for ref

Returns the number of good values in an ndarray

=for usage

 $x = ngood($data);

=for bad

Accepts good and bad input ndarrays; output is an ndarray
and is always good.

=head2 setbadat

=for ref

Set the value to bad at a given position.

=for usage

 setbadat $ndarray, @position

C<@position> is a coordinate list, of size equal to the
number of dimensions in the ndarray.
This is a wrapper around L<set|PDL::Core/set> and is
probably mainly useful in test scripts!

=for example

 pdl> $x = sequence 3,4
 pdl> $x->setbadat 2,1
 pdl> p $x
 [
  [  0   1   2]
  [  3   4 BAD]
  [  6   7   8]
  [  9  10  11]
 ]

=for bad

This method can be called on ndarrays that have bad values.
The remainder of the arguments should be Perl scalars indicating
the position to set as bad. The output ndarray will have bad values
and will have its badflag turned on.

=cut

*setbadat = \&PDL::setbadat;
sub PDL::setbadat {
    barf 'Usage: setbadat($pdl, $x, $y, ...)' if $#_<1;
    my $self  = shift; 
    PDL::Core::set_c ($self, [@_], $self->badvalue);
    $self->badflag(1);
    return $self;
}

!NO!SUBS!

# NOTE: the Code section uses SETBAD
#
# have removed inplace stuff because:
#  $x->inplace->setbadif( $x % 2 )
# actually sets the badflag in a for ($x % 2) - this is
# done inplace, and the flag cleared. Hence the setbadif()
# call is NOT done inplace.
#
# Don't want to play around with inplace-type code to
# try and fix this (doubt will be easy)
#
my %setbadif_extra = ( );
if ( 0 ) {
    ## ie if fix inplace issues
    $setbadif_extra{Inplace} = [ 'a' ];
} else {
}
# always make sure the output is "bad"

# note: have made the mask be an integer
pp_def('setbadif',
  Pars => q(a(); int mask(); [o]b()),
  Doc => <<'EOF',
=for ref

Set elements bad based on the supplied mask, otherwise
copy across the data.

=for example

 pdl> $x = sequence(5,5)
 pdl> $x = $x->setbadif( $x % 2 )
 pdl> p "a badflag: ", $x->badflag, "\n"
 a badflag: 1
 pdl> p "a is\n$x"
 [
  [  0 BAD   2 BAD   4]
  [BAD   6 BAD   8 BAD]
  [ 10 BAD  12 BAD  14]
  [BAD  16 BAD  18 BAD]
  [ 20 BAD  22 BAD  24]
 ]

Unfortunately, this routine can I<not> be run inplace, since the
current implementation can not handle the same ndarray used as
C<a> and C<mask> (eg C<< $x->inplace->setbadif($x%2) >> fails).
Even more unfortunate: we can't catch this error and tell you.
EOF
  BadDoc => <<'EOF',
The output always has its bad flag set, even if it does not contain
any bad values (use L</check_badflag> to check
whether there are any bad values in the output). 
The input ndarray can have bad values: any bad values in the input ndarrays
are copied across to the output ndarray.

Also see L</setvaltobad> and L</setnantobad>.
EOF
  HandleBad => 1,
  %setbadif_extra,
  Code => '
broadcastloop %{
  /* if the bad value == 0 then all points are going to be selected ... */
  if ( PDL_IF_BAD($ISBAD(mask()) ||,) $mask() ) {
    $SETBAD(b());
  } else {
    $b() = $a();
  }
%}
$PDLSTATESETBAD(b);
  ',
  GenericTypes => $A,
);

# this is useful because $x->setbadif( $x == 23 )
# is common and that can't be done inplace
pp_def('setvaltobad',
  Pars => q(a(); [o]b()),
  OtherPars => q(double value),
  Doc => <<'EOF',
=for ref

Set bad all those elements which equal the supplied value.

=for example

 $x = sequence(10) % 3;
 $x->inplace->setvaltobad( 0 );
 print "$x\n";
 [BAD 1 2 BAD 1 2 BAD 1 2 BAD]

This is a simpler version of L</setbadif>, but this
function can be done inplace.  See L</setnantobad>
if you want to convert NaN to the bad value.
EOF
  BadDoc => <<'EOF',
The output always has its bad flag set, even if it does not contain
any bad values (use L</check_badflag> to check
whether there are any bad values in the output). 
Any bad values in the input ndarrays are copied across to the output ndarray.
EOF
  HandleBad => 1,
  Inplace => 1,
  Code => q[
broadcastloop %{
  if ( $a() == ($GENERIC(a)) $COMP(value) ) {
    $SETBAD(b());
  } else {
    $b() = $a();
  }
%}
$PDLSTATESETBAD(b);
  ],
  GenericTypes => $A,
);

pp_def('setnantobad',
  Pars => q(a(); [o]b()),
  Doc => <<'EOF',
=for ref

Sets NaN values (for complex, where either is NaN) in the input ndarray bad
(only relevant for floating-point ndarrays).
EOF
  BadDoc => <<'EOF',
This method can process ndarrays with bad values: those bad values
are propagated into the output ndarray. Any value that is not a number
(before version 2.040 the test was for "not finite")
is also set to bad in the output ndarray. If all values from the input
ndarray are good, the output ndarray will B<not> have its
bad flag set.
EOF
  HandleBad => 1,
  GenericTypes => $AF,
  Inplace => 1,
  Code => q[
    int flag = 0;
    broadcastloop %{
      if ( PDL_ISNAN_$PPSYM()($a()) ) {
        $SETBAD(b());
        flag = 1;
      } else {
        $b() = $a();
      }
    %}
    if ( flag ) $PDLSTATESETBAD(b);
  ],
);

pp_def('setinftobad',
  Pars => q(a(); [o]b()),
  Doc => <<'EOF',
=for ref

Sets non-finite values (for complex, where either is non-finite) in
the input ndarray bad (only relevant for floating-point ndarrays).
EOF
  BadDoc => <<'EOF',
This method can process ndarrays with bad values: those bad values
are propagated into the output ndarray. Any value that is not finite
is also set to bad in the output ndarray. If all values from the input
ndarray are finite, the output ndarray will B<not> have its
bad flag set.
EOF
    HandleBad => 1,
    GenericTypes => $AF,
    Inplace => 1,
    Code => q[
        int flag = 0;
        broadcastloop %{
            if ( !PDL_ISFINITE_$PPSYM()($a()) && !PDL_ISNAN_$PPSYM()($a()) ) {
                $SETBAD(b());
                flag = 1;
            }
            else {
                $b() = $a();
            }
        %}
        if ( flag ) $PDLSTATESETBAD(b);
    ],
);

pp_def('setnonfinitetobad',
  Pars => q(a(); [o]b()),
  Doc => <<'EOF',
=for ref

Sets non-finite values (for complex, where either is non-finite) in
the input ndarray bad (only relevant for floating-point ndarrays).
EOF
  BadDoc => <<'EOF',
This method can process ndarrays with bad values: those bad values
are propagated into the output ndarray. Any value that is not finite
is also set to bad in the output ndarray. If all values from the input
ndarray are finite, the output ndarray will B<not> have its
bad flag set.
EOF
  HandleBad => 1,
  GenericTypes => $AF,
  Inplace => 1,
  Code => q[
    int flag = 0;
    broadcastloop %{
      if ( !PDL_ISFINITE_$PPSYM()($a()) ) {
        $SETBAD(b());
        flag = 1;
      } else {
        $b() = $a();
      }
    %}
    if ( flag ) $PDLSTATESETBAD(b);
  ],
);

pp_def('setbadtonan',
  Pars => q(a(); [o] b();),
  Doc => <<'EOF',
=for ref

Sets Bad values to NaN

This is only relevant for floating-point ndarrays. The input ndarray can be
of any type, but if done inplace, the input must be floating point.
EOF
  BadDoc => <<'EOF',
This method processes input ndarrays with bad values. The output ndarrays will
not contain bad values (insofar as NaN is not Bad as far as PDL is concerned)
and the output ndarray does not have its bad flag set. As an inplace
operation, it clears the bad flag.
EOF
  HandleBad => 1,
  GenericTypes => $AF,
  Inplace => 1,
  Code => q{
broadcastloop %{
  if ( $ISBAD(a()) ) {
    $b() = $TFDEGCH(NAN,NAN,NAN,NAN+I*NAN,NAN+I*NAN,NAN+I*NAN);
  } else {
    $b() = $a();
  }
%}
$PDLSTATESETGOOD(b);
  },
);

pp_def('setbadtoval',
  Pars => q(a(); [o]b()),
  OtherPars => q(double newval),
  Doc => <<'EOF',
=for ref

Replace any bad values by a (non-bad) value. 

Also see L</badmask>.

=for example

 $x->inplace->setbadtoval(23);
 print "a badflag: ", $x->badflag, "\n";
 a badflag: 0
EOF
  BadDoc => <<'EOF',
The output always has its bad flag cleared.
If the input ndarray does not have its bad flag set, then
values are copied with no replacement.
EOF
  HandleBad => 1,
  Inplace => 1,
  Code => q{
    PDL_IF_BAD($GENERIC(b) replace = ($GENERIC(b)) $COMP(newval);,)
    broadcastloop %{
      $GENERIC(b) a_val = $a();
      $b() = PDL_IF_BAD($ISBADVAR(a_val,a) ? replace : ,) a_val;
    %}
    $PDLSTATESETGOOD(b); /* always make sure the output is "good" */
  },
  GenericTypes => $A,
);

pp_def('badmask',
  Pars => 'a(); b(); [o]c();',
  Inplace => [ 'a' ],
  HandleBad => 1,
  Code => '
broadcastloop %{
  $c() = ( isfinite((double) $a()) PDL_IF_BAD(&& $ISGOOD(a()),) ) ? $a() : $b();
%}
$PDLSTATESETGOOD(c);
',
  Doc => <<'EOF',
=for ref

Clears all C<infs> and C<nans> in C<$a> to the corresponding value in C<$b>.
EOF
  BadDoc => <<'EOF',
If bad values are present, these are also cleared.
EOF
);

pp_def('copybad',
  Pars => q(a(); mask(); [o]b()),
  Doc => <<'EOF',
=for ref

Copies values from one ndarray to another, setting them
bad if they are bad in the supplied mask.

=for example

 $x = byte( [0,1,3] );
 $mask = byte( [0,0,0] );
 $mask->badflag(1);
 set($mask,1,$mask->badvalue);
 $x->inplace->copybad( $mask );
 p $x;
 [0 BAD 3]

It is equivalent to:

 $c = $x + $mask * 0
EOF
  BadDoc => <<'EOF',
This handles input ndarrays that are bad. If either C<$x>
or C<$mask> have bad values, those values will be marked
as bad in the output ndarray and the output ndarray will have
its bad value flag set to true.
EOF
  HandleBad => 1,
  Inplace => [ 'a' ],
  Code => q{
char anybad = 0;
broadcastloop %{
      PDL_IF_BAD(if ( $ISBAD(mask()) ) {
          $SETBAD(b());
          anybad = 1;
      } else,) {
          $b() = $a();
      }
%}
if (anybad) $PDLSTATESETBAD(b);
  },
  GenericTypes => $A,
);

pp_def('locf',
  Pars => 'a(n); [o]b(n);',
  HandleBad => 1,
  GenericTypes => $A,
  Doc => <<'EOF',
=for ref

Last Observation Carried Forward - replace
every BAD value with the most recent non-BAD value prior to it.
Any leading BADs will be set to 0.
EOF
  Code => q{
    $GENERIC() tmp = 0;
    loop(n) %{
      if ( $ISGOOD(a()) ) tmp = $a();
      $b() = tmp;
    %}
  },
);

#########################################################

pp_addpm({At=>'Bot'},<<'!WITHOUT!SUBS!');

=head1 AUTHOR

Doug Burke (djburke@cpan.org), 2000, 2001, 2003, 2006.

The per-ndarray bad value support is by Heiko Klein (2006).

CPAN documentation fixes by David Mertens (2010, 2013).

All rights reserved. There is no warranty. You are allowed to
redistribute this software / documentation under certain conditions. For
details, see the file COPYING in the PDL distribution. If this file is
separated from the PDL distribution, the copyright notice should be
included in the file.

=cut

!WITHOUT!SUBS!

## End
pp_done();