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

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

package PDL::PP::PdlParObj;

use Carp;
use PDL::Types;

# check for bad value support
#
use PDL::Config;
my $usenan = $PDL::Config{BADVAL_USENAN} || 0;

our $macros = <<'EOF';
#define PDL_REDODIMS(declini, cast, type, flag, name, pdlname) \
  declini name ## _datap = (cast(PDL_REPRP_TRANS(pdlname, flag))); \
  declini name ## _physdatap = (cast(pdlname->data));

#define PDL_REDODIMS_BADVAL(declini, cast, type, flag, name, pdlname) \
  PDL_REDODIMS(declini, cast, type, flag, name, pdlname) \
  type name ## _badval = 0; \
  PDL_Anyval name ## _anyval_badval = PDL->get_pdl_badvalue(pdlname); \
  ANYVAL_TO_CTYPE(name ## _badval, type, name ## _anyval_badval);
EOF
our %Typemap = ();
use PDL::Types ':All';

# build a typemap for our translation purposes
# again from info in PDL::Types
for my $typ (typesrtkeys) {
  $Typemap{typefld($typ,'ppforcetype')} = {
					  Ctype => typefld($typ,'ctype'),
					  Cenum => typefld($typ,'sym'),
					  Val =>   typefld($typ,'numval'),
					 };
}

# Try to load Text::Balanced
my $hasTB = 0;
eval q{
	use Text::Balanced;
	$hasTB = 1;
};

# split regex $re separated arglist
# but ignore bracket-protected bits
# (i.e. text that is within matched brackets)
# fallback to simple split if we can't find Text::Balanced
my $prebrackreg = qr/^([^\(\{\[]*)/;
sub splitprotected ($$) {
  my ($re,$txt) = @_;
  return split $re, $txt unless $hasTB;
  return () if !defined $txt || $txt =~ /^\s*$/;
  my ($got,$pre) = (1,'');
  my @chunks = ('');
  my $ct = 0; # infinite loop protection
  while ($got && $txt =~ /[({\[]/ && $ct++ < 1000) {
    # print "iteration $ct\n";
    ($got,$txt,$pre) =
      Text::Balanced::extract_bracketed($txt,'{}()[]',$prebrackreg);
    my @partialargs = split $re, $pre, -1;
    $chunks[-1] .= shift @partialargs if @partialargs;
    push @chunks, @partialargs;
    $chunks[-1] .= $got;
  }
  confess "possible infinite parse loop, splitting '$txt' "
			   if $ct >= 1000;
  my @partialargs = split $re, $txt, -1;
  $chunks[-1] .= shift @partialargs if @partialargs;
  push @chunks, @partialargs if @partialargs;
  # print STDERR "args found: $#chunks\n";
  # print STDERR "splitprotected $txt on $re: [",join('|',@chunks),"]\n";
  return @chunks;
}

# null != [0]
#  - in Core.

#{package PDL;
# sub isnull {
#   my $this = shift;
#   return ($this->getndims==1 && $this->getdim(0)==0) ? 1:0 }
#}

1;

#__DATA__

# need for $badflag is due to hacked get_xsdatapdecl() 
# - this should disappear when (if?) things are done sensibly
#
my $typeregex = join '|', map {typefld($_,'ppforcetype')} typesrtkeys;
our $pars_re = qr/^
	\s*((?:$typeregex)[+]*|)\s*	# $1: first option
	(?:
	\[([^]]*)\]   	# $2: The initial [option] part
	)?\s*
	(\w+)          	# $3: The name
	\(([^)]*)\)  		# $4: The indices
/x;
sub new {
	my($type,$string,$number,$badflag) = @_;
	$badflag ||= 0;
	my $this = bless {Number => $number, BadFlag => $badflag},$type;
	# Parse the parameter string. Note that the regexes for this match were
	# originally defined here, but were moved to PDL::PP for FullDoc parsing.
	$string =~ $pars_re
		 or confess "Invalid pdl def $string (regex $typeregex)\n";
	my($opt1,$opt2,$name,$inds) = ($1,$2,$3,$4);
	map {$_ = '' unless defined($_)} ($opt1,$opt2,$inds); # shut up -w
	print "PDL: '$opt1', '$opt2', '$name', '$inds'\n"
		  if $::PP_VERBOSE;
# Set my internal variables
	$this->{Name} = $name;
	$this->{Flags} = [(split ',',$opt2),($opt1?$opt1:())];
	for(@{$this->{Flags}}) {
		/^io$/ and $this->{FlagW}=1 or
		/^nc$/ and $this->{FlagNCreat}=1 or
		/^o$/ and $this->{FlagOut}=1 and $this->{FlagCreat}=1 and $this->{FlagW}=1 or
		/^oca$/ and $this->{FlagOut}=1 and $this->{FlagCreat}=1 and $this->{FlagW}=1
			and $this->{FlagCreateAlways}=1 or
		/^t$/ and $this->{FlagTemp}=1 and $this->{FlagCreat}=1 and $this->{FlagW}=1 or
		/^phys$/ and $this->{FlagPhys} = 1 or
		/^((?:$typeregex)[+]*)$/ and $this->{Type} = $1 and $this->{FlagTyped} = 1 or
		confess("Invalid flag $_ given for $string\n");
	}
#	if($this->{FlagPhys}) {
#		# warn("Warning: physical flag not implemented yet");
#	}
	if ($this->{FlagTyped} && $this->{Type} =~ s/[+]$// ) {
	  $this->{FlagTplus} = 1;
		}
	if($this->{FlagNCreat}) {
		delete $this->{FlagCreat};
		delete $this->{FlagCreateAlways};
	}
	my @inds = map{
		s/\s//g; 		# Remove spaces
		$_;
	} split ',', $inds;
	$this->{RawInds} = [@inds];
	return $this;
}

sub name {return (shift)->{Name}}

sub add_inds {
	my($this,$dimsobj) = @_;
	$this->{IndObjs} = [map {$dimsobj->get_indobj_make($_)}
		@{$this->{RawInds}}];
	my %indcount;
	$this->{IndCounts} = [
		map {
			0+($indcount{$_->name}++);
		} @{$this->{IndObjs}}
	];
	$this->{IndTotCounts} = [
		map {
			($indcount{$_->name});
		} @{$this->{IndObjs}}
	];
}


# do the dimension checking for perl level threading
# assumes that IndObjs have been created
sub perldimcheck {
  my ($this,$pdl) = @_;
  croak ("can't create ".$this->name) if $pdl->isnull &&
    !$this->{FlagCreat};
  return 1 if $pdl->isnull;
  my $rdims = @{$this->{RawInds}};
  croak ("not enough dimensions for ".$this->name)
    if ($pdl->threadids)[0] < $rdims;
  my @dims = $pdl->dims;
  my ($i,$ind) = (0,undef);
  for $ind (@{$this->{IndObjs}}) {
    $ind->add_value($dims[$i++]);
  }
  return 0; # not creating
}

sub finalcheck {
  my ($this,$pdl) = @_;
  return [] if $pdl->isnull;
  my @corr = ();
  my @dims = $pdl->dims;
  my ($i,$ind) = (0,undef);
  for $ind (@{$this->{IndObjs}}) {
    push @corr,[$i-1,$ind->{Value},$dims[$i-1]] if $dims[$i++] != $ind->{Value};
  }
  return [@corr];
}

# get index sizes for a parameter that has to be created
sub getcreatedims {
  my $this = shift;
  return map
    { croak "can't create: index size ".$_->name." not initialised"
	if !defined($_->{Value}) || $_->{Value} < 1;
      $_->{Value} } @{$this->{IndObjs}};
}


# find the value for a given PDL type
sub typeval {
  my $ctype = shift;
  my @match = grep {$Typemap{$_}->{Ctype} =~ /^$ctype$/} keys(%Typemap);
  if ($#match < 0) {
    use Data::Dumper;
    print Dumper \%Typemap;
    croak "unknown PDL type '$ctype'" ;
  }
  return $Typemap{$match[0]}->{Val};
}

# return the PDL type for this pdl
sub ctype {
  my ($this,$generic) = @_;
  return $generic unless $this->{FlagTyped};
  croak "ctype: unknownn type"
    unless defined($Typemap{$this->{Type}});
  my $type = $Typemap{$this->{Type}}->{Ctype};
  if ($this->{FlagTplus}) {
    $type = $Typemap{$this->{Type}}->{Val} >
      PDL::PP::PdlParObj::typeval($generic) ?
      $Typemap{$this->{Type}}->{Ctype} : $generic;
  }
  return $type;
}

# return the enum type for a parobj; it'd better be typed
sub cenum {
    my $this = shift;
    croak "cenum: unknown type [" . $this->{Type} . "]"
	unless defined($PDL::PP::PdlParObj::Typemap{$this->{Type}});
    return $PDL::PP::PdlParObj::Typemap{$this->{Type}}->{Cenum};
}

sub get_nname{ my($this) = @_;
	"(\$PRIV(pdls[$this->{Number}]))";
}

sub get_nnflag { my($this) = @_;
	"(\$PRIV(vtable->per_pdl_flags[$this->{Number}]))";
}


# XXX There might be weird backprop-of-changed stuff for [phys].
#
# Have changed code to assume that, if(!$this->{FlagCreat})
# then __creating[] will == 0
#  -- see make_redodims_thread() in ../PP.pm
#
sub get_xsnormdimchecks { 
    my($this) = @_;
    my $pdl   = $this->get_nname;
    my $iref  = $this->{IndObjs};
    my $ninds = 0+scalar(@$iref);

    my $str = PDL::PP::pp_line_numbers(__LINE__, "");
    $str .= "if(!__creating[$this->{Number}]) {\n" if $this->{FlagCreat};
    
    # Dimensional Promotion when number of dims is less than required:
    #   Previous warning message now commented out,
    #   which means we only need include the code if $ninds > 0
    #
    if ( $ninds > 0 ) {
	$str .= "   if(($pdl)->ndims < $ninds) {\n" .
	    join('', map { 
		my $size = $iref->[$_-1]->get_size();      
		"      if (($pdl)->ndims < $_ && $size <= 1) $size = 1;\n"
		} (1..$ninds)) 
# XXX why is this here, commented, and not removed? If re-inserted, be sure to use PDL_COMMENT
##		."      /* \$CROAK(\"Too few dimensions for argument \'$this->{Name}\'\\n\"); */\n"
		. "   }\n";
    }

    # Now, the real check.
    my $no = 0;
    for( @$iref ) {
	my $siz = $_->get_size();
	my $dim = "($pdl)->dims[$no]";
	my $ndims = "($pdl)->ndims";
	$str .= "   if($siz == -1 || ($ndims > $no && $siz == 1)) {\n" .
	        "      $siz = $dim;\n" .
		"   } else if($ndims > $no && $siz != $dim) {\n" .
		"      if($dim != 1) {\n" .
                "         \$CROAK(\"Wrong dimensions for parameter '@{[ $this->name ]}'\\n\");\n" .
		"      }\n   }\n";
	$no++;
    } 

    $str .= "PDL->make_physical(($pdl));\n" if $this->{FlagPhys};

    if ( $this->{FlagCreat} ) { 
	$str .= "} else {\n";
	
	# We are creating this pdl.
	$str .= " PDL_Indx dims[".($ninds+1)."]; PDL_COMMENT(\"Use ninds+1 to avoid smart (stupid) compilers\")";
	$str .= join "",
	(map {"dims[$_] = ".$iref->[$_]->get_size().";"} 0 .. $#$iref);
	my $istemp = $this->{FlagTemp} ? 1 : 0;
	$str .="\n PDL->thread_create_parameter(&\$PRIV(__pdlthread),$this->{Number},dims,$istemp);\n";
	$str .= "}";
    }
    return $str;
    
} # sub: get_xsnormdimchecks()

sub get_incname {
	my($this,$ind) = @_;
	if($this->{IndTotCounts}[$ind] > 1) {
	    "__inc_".$this->{Name}."_".($this->{IndObjs}[$ind]->name).$this->{IndCounts}[$ind];
	} else {
	    "__inc_".$this->{Name}."_".($this->{IndObjs}[$ind]->name);
	}
}

sub get_incdecls {
	my($this) = @_;
	if(scalar(@{$this->{IndObjs}}) == 0) {return "";}
	(join '',map {
		"PDL_Indx ".($this->get_incname($_)).";";
	} (0..$#{$this->{IndObjs}}) ) . ";"
}

sub get_incregisters {
	my($this) = @_;
	if(scalar(@{$this->{IndObjs}}) == 0) {return "";}
	(join '',map {
		"register PDL_Indx ".($this->get_incname($_))." = \$PRIV(".
			($this->get_incname($_)).");\n";
	} (0..$#{$this->{IndObjs}}) )
}

sub get_incdecl_copy {
	my($this,$fromsub,$tosub) = @_;
	PDL::PP::pp_line_numbers(__LINE__, join '',map {
		my $iname = $this->get_incname($_);
		&$fromsub($iname)."=".&$tosub($iname).";";
	} (0..$#{$this->{IndObjs}}))
}

sub get_incsets {
	my($this,$str) = @_;
	my $no=0;
	PDL::PP::pp_line_numbers(__LINE__, join '',map {
               "if($str->ndims <= $_ || $str->dims[$_] <= 1)
		  \$PRIV(".($this->get_incname($_)).") = 0; else
		 \$PRIV(".($this->get_incname($_)).
			") = ".($this->{FlagPhys}?
				   "$str->dimincs[$_];" :
				   "PDL_REPRINC($str,$_);");
	} (0..$#{$this->{IndObjs}}) )
}

# Print an access part.
sub do_access {
	my($this,$inds,$context) = @_;
	my $pdl = $this->{Name};
# Parse substitutions into hash
	my %subst = map
	 {/^\s*(\w+)\s*=>\s*(\S*)\s*$/ or confess "Invalid subst $_\n"; ($1,$2)}
	 	splitprotected ',',$inds;
# Generate the text
	my $text;
	$text = "(${pdl}_datap)"."[";
	$text .= join '+','0',map {
		$this->do_indterm($pdl,$_,\%subst,$context);
	} (0..$#{$this->{IndObjs}});
	$text .= "]";
# If not all substitutions made, the user probably made a spelling
# error. Barf.
	if(scalar(keys %subst) != 0) {
		confess("Substitutions left: ".(join ',',keys %subst)."\n");
	}
       return "$text PDL_COMMENT(\"ACCESS($access)\") ";
}

sub has_dim {
	my($this,$ind) = @_;
	my $h = 0;
	for(@{$this->{IndObjs}}) {
		$h++ if $_->name eq $ind;
	}
	return $h;
}

sub do_resize {
	my($this,$ind,$size) = @_;
	my @c;my $index = 0;
	for(@{$this->{IndObjs}}) {
		push @c,$index if $_->name eq $ind; $index ++;
	}
	my $pdl = $this->get_nname;
	return PDL::PP::pp_line_numbers(__LINE__, (join '',map {"$pdl->dims[$_] = $size;\n"} @c).
		"PDL->resize_defaultincs($pdl);PDL->allocdata($pdl);".
		$this->get_xsdatapdecl(undef,1));
}

sub do_pdlaccess {
	my($this) = @_;
	PDL::PP::pp_line_numbers(__LINE__, '$PRIV(pdls['.$this->{Number}.'])');

}

sub do_pointeraccess {
	my($this) = @_;
	return $this->{Name}."_datap";
}

sub do_physpointeraccess {
	my($this) = @_;
	return $this->{Name}."_physdatap";
}

sub do_indterm { my($this,$pdl,$ind,$subst,$context) = @_;
# Get informed
	my $indname = $this->{IndObjs}[$ind]->name;
	my $indno = $this->{IndCounts}[$ind];
	my $indtot = $this->{IndTotCounts}[$ind];
# See if substitutions
	my $substname = ($indtot>1 ? $indname.$indno : $indname);
	my $incname = $indname.($indtot>1 ? $indno : "");
	my $index;
	if(defined $subst->{$substname}) {$index = delete $subst->{$substname};}
	else {
# No => get the one from the nearest context.
		for(reverse @$context) {
			if($_->[0] eq $indname) {$index = $_->[1]; last;}
		}
	}
	if(!defined $index) {confess "Access Index not found: $pdl, $ind, $indname
		On stack:".(join ' ',map {"($_->[0],$_->[1])"} @$context)."\n" ;}
#	return "\$PRIV(".($this->get_incname($ind))."*". $index .")";
# Now we have them in register variables -> no PRIV
       return "(".($this->get_incname($ind))."*".
               "PP_INDTERM(".$this->{IndObjs}[$ind]->get_size().", $index))";
}

# XXX hacked to create a variable containing the bad value for 
# this piddle. 
# This is a HACK (Doug Burke 07/08/00)
# XXX
#
sub get_xsdatapdecl { 
    my($this,$genlooptype,$asgnonly) = @_;
    my $type; 
    my $pdl = $this->get_nname; 
    my $flag = $this->get_nnflag;
    my $name = $this->{Name};
    $type = $this->ctype($genlooptype) if defined $genlooptype;
    my $declini = ($asgnonly ? "" : "$type *");
    my $cast = ($type ? "($type *)" : "");
    my $macro = "PDL_REDODIMS";
    # assuming we always need this 
    # - may not be true - eg if $asgnonly ??
    # - not needed for floating point types when using NaN as bad values
    $macro = "PDL_REDODIMS_BADVAL" if $this->{BadFlag} and $type and
	( $usenan == 0 or $type !~ /^PDL_(Float|Double)$/ );
    PDL::PP::pp_line_numbers(__LINE__, "$macro($declini, $cast, $type, $flag, $name, $pdl)");
}

1;
