#  Copyright (c) 1997-2004
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
#  http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version: http://www.gnu.org/licenses/gpl.txt.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#-----------------------------------------------------------------------------
#  $Project: polymake $$Id: utilities,v 1.11 2004/11/24 18:05:00 gawrilow Exp $

require Poly::ProgramPipe;

# all functions here are automatically exported in other modules and applications,
# as soon as they USE this module

####
#### following functions become superfluous when data sharing between C++ and perl is implemented
####

function product {
   my ($m,$n) = @_;
   my $p=1;
   my $k;
   foreach $k ($m..$n) {
      $p = $p * $k;
   }
   $p
}

function binomial {
   my ($n,$k) = @_;
   if ($k>$n/2) {
      product($k+1,$n) / product(2,$n-$k);
   } else {
      product($n-$k+1,$n) / product(2,$k);
   }
}

function fibonacci {
   my ($m) = @_;
   my @numbers;
   if ($m>=1) {
      push @numbers, 1;
      if ($m>=2) {
	 push @numbers, 1;
	 for (my $i=2; $i<$m; ++$i) {
	    push @numbers, $numbers[$i-1]+$numbers[$i-2];
	 }
      }
   }
   return @numbers;
}

function alternating_sum_and_sign {
   my $alternating_sum=0;
   my $sign=1;
   foreach my $n (is_array($_[0]) ? @{$_[0]} : @_) {
      $alternating_sum += $sign*$n;
      $sign=-$sign;
   }
   return ($alternating_sum,$sign);
}

function transpose {
   my $n=0;			# line counter
   my @t=();			# transposed incidence matrix
   foreach (@{$_[0]}) {
      foreach my $x (/\d+/g) {
	 $t[$x].="$n "
      }
      $n++;
   }
   foreach (@t) { s/^(.*) $/{$1}\n/ }
   \@t
}

function max_elem {	# property => max cardinal over all lines
   my $answer=-1;
   foreach (@{$_[0]}) {
      /(\d+)(?:\s*\}\s*)?$/;
      if ($answer<$1) { $answer=$1; }
   }
   return $answer;
}

function row_sizes {	# property => ( cardinal list )
   map { my $cnt=0; while (/\d+/g) { ++$cnt } $cnt } @{$_[0]};
}

function col_sizes {	# "property" => ( cardinal list )
   my @answer;
   foreach (@{$_[0]}) {
      foreach my $elem (/\d+/g) {
	 ++$answer[$elem];
      }
   }
   return @answer;
}

function permute_rows {
   my ($old, $indices)=@_;
   my @new=@$old[@$indices];
   \@new;
}

function permute_cols {
   my ($old, $indices, $already_inversed)=@_;
   if (!$already_inversed) {
      my @inverse_indices; $#inverse_indices=$#$indices;
      for (my $i=0; $i<@inverse_indices; ++$i) { $inverse_indices[$indices->[$i]]=$i }
      $indices=\@inverse_indices;
   }
   my @new=map {
      my ($head, $tail)=/^(\D*) (?: .*\d (\D*))? $/x;
      defined($tail)
      ? do { my @row=sort { $a <=> $b } @$indices[ /\d+/g ];
             "$head@row$tail" }
      : $head
   } @$old;
   \@new;
}

function permute_rows_cols {
   permute_cols(&permute_rows, $_[1]);
}

function permute_elements {
   my ($old, $indices)=@_;
   if (is_array($old)) {
      my @new=map {
         my @row=split;
         "@row[@$indices]\n";
      } @$old;
      \@new;
   } else {
      my @row=split /\s+/, $old;
      "@row[@$indices]";
   }
}

function permute_sets {
   my ($old, $indices)=@_;
   my @inverse_indices; $#inverse_indices=$#$indices;
   for (my $i=0; $i<@inverse_indices; ++$i) { $inverse_indices[$indices->[$i]]=$i }
   my @new=map {
      my @sets=sort { compare_sets($a,$b); } @{permute_cols([ /\{[^{}]*\}/g ], \@inverse_indices, 1)};
      "{@sets}\n";
   } @$old;
   \@new;
}

function compare_sets {
   my ($a, $b)=@_;
   my @a= $a =~ /\d+/g;
   my @b= $b =~ /\d+/g;
   my $result;
   for (my $i=0; $i<=main::min($#a,$#b); ++$i) {
      $result=$a[$i]<=>$b[$i] and last;
   }
   $result ||= $#a <=> $#b;
};

function diff_list_of_sets {
   my ($from,$to)=@_;
   my $cnt=0;
   my %bag=map { my @set=/\d+/g; "@set" => $cnt++ } @$from;
   my @result=map { my @set=/\d+/g; my $key="@set";
		    exists $bag{$key} ? $bag{$key} : die "lists of sets are different\n"
		  } @$to;
   \@result;
}

# k, item, item, ... => list of k_subsets: [ item, ... ], ...
function all_subsets_of_k {
   my $k=shift;
   my $n=@_;
   croak( "parameter k=$k out of range" ) if $k<0 || $k>$n;
   return [] if !$k;
   my @result;
   my @index=0..$k-1;
   my $ptr=$k-1;

   while (1) {
      push @result, [ @_[@index] ];
      next if ++$index[$ptr] < $n;
      do {
	 return @result if --$ptr<0;
      } while ((++$index[$ptr])+$k-$ptr > $n);
      while ($ptr<$k-1) {
	 ++$ptr;
	 $index[$ptr]=$index[$ptr-1]+1;
      }
   }
}

# "LABEL ..." => [ "LABEL" ];  labels starting with _ are replaced by white spaces.
function split_labels {
   [ map { s/^_.*/ /; $_ } split /\s+/, shift ]
}

# takes (vertex) labels and incidence information to produce new (facet) labels
function induced_labels {
   my ($v_labels, $incidences) = @_;
   my @v_label = $v_labels =~ /\S+/g;
   join(" ", map { join(",", @v_label[/\d+/g]) } @$incidences);
}

# find a permutation between two label sets; be prepared for non-unique labels
function diff_labels {
   my ($from, $to)=map { is_array($_) ? $_ : [ split ] } @_;
   die "lists of labels are different\n" if $#$from != $#$to;
   my %map;
   my $pos=0;
   push @{$map{$_}}, $pos++ for @$from;
   my $bag;
   my @perm=map {
      if (defined ($bag=$map{$_})) {
         $pos=shift @$bag;
         delete $map{$_} unless @$bag;
         $pos;
      } else {
         die "lists of labels are different\n";
      }
   } @$to;
   die "lists of labels are different\n" if keys %map;	# just to calm the paranoia
   \@perm;
}

##############################################################################
#
# ('program_name', ...) => 'full path to the first one found in PATH'
#
function find_via_path {
   foreach my $dir (split /:/, $ENV{PATH}) {
      foreach my $progname (@_) {
	 if (-x "$dir/$progname") {
	    return $dir eq "." ? `pwd`."/$progname" : "$dir/$progname";
	 }
      }
   }
   undef;
}

# Local Variables:
# mode: perl
# c-basic-offset:3
# End:
