#  Copyright (c) 1997-2015
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
#  http://www.polymake.org
#
#  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.
#-------------------------------------------------------------------------------

# This file contains methods to parse a polynomial from a string. If the ring
# is very complicated, it might be easier to access the user_function directly,
# which is why it is left available.


property_type Polynomial {

   method construct(Ring, String) {
      toPolynomial($_[2], $_[1], $_[1]->variables);
   }
}

property_type Term {

   method construct(Ring, String) {
      parseTerm($_[2], $_[1]->variables);
   }
}

# @category Data Conversion
# Read a [[Polynomial]] from a String.
# @param String s
# @param ARRAY vars
user_function toPolynomial {
   my ($s, $ring, @vars) = @_;
   # Remove white spaces.
   $s =~ s/\s//g;
   my $stilda = $s;

   foreach my $var (@vars) {
      my $varstring = "$var";
      my $replacement = "m".("a" x (length($varstring)-1));
      $varstring = quotemeta($varstring);
      $stilda =~ s/$varstring/$replacement/g;
   }
   # Check for correct format of stilda:
   $stilda =~ m/^[0123456789\/\*\^\-\+a\(\)m]*$/ or die "Input error: $stilda.";
   return parseSumRecursively($s, $stilda, $ring, @vars);
}

sub parseSumRecursively {
   my ($s,$stilda,$ring,@vars) = @_;
   my ($op) = $stilda =~ m/^([\+\-]?)/x;
   $s = substr($s,length($op),length($s));
   $stilda = substr($stilda,length($op),length($stilda));
   my @term = ();
   my @termtilda = ();
   my $hasbrackets = 0;
   # Match the first summand. While matching unwrap the product structure.
   while (($stilda !~ m/^[+-]/) && (length($stilda) > 0)) {
      if ($stilda =~ m/^\(/) {
         # If we have beginning brackets:
         my ($factor) = $stilda =~ m/(\( (?: [^\(\)]* | (?0) )* \)) /x;
         my ($hat, $pow) = $' =~ m/^(\^?)(\d*)/;
         my $totallength = length($factor)+length($hat)+length($pow);
         $hasbrackets = 1;
         push @termtilda, $factor.$hat.$pow;
         push @term, substr($s,0,$totallength);
         $stilda = substr($stilda,$totallength,length($stilda));
         $s = substr($s,$totallength,length($s));
      } elsif (my ($factor) = $stilda =~ m/^([^\+\-\(\)]*)/x) {
         # If we start with a factor in front:
         push @termtilda, $factor;
         push @term, substr($s,0,length($factor));
         $stilda = substr($stilda,length($factor),length($stilda));
         $s = substr($s,length($factor),length($s));
      }
   }
   my $result = new Polynomial(1, $ring);
   if ($hasbrackets) {
      foreach my $factor (@termtilda) {
         my $f = shift @term;
         if (my ($inner, $pow) = $factor =~ m/^\((.*)\)\^?(\d*)$/) {
            if ($pow) {
               while ($pow>0) {
                  $result = $result * parseSumRecursively(substr($f,1,length($inner)),$inner,$ring,@vars);
                  $pow--;
               }
            } else {
               $result = $result * parseSumRecursively(substr($f,1,length($inner)),$inner,$ring,@vars);
            }
         } else {
            $factor =~ s/^\*?//;
            $factor =~ s/\*?$//;
            $f =~ s/^\*?//;
            $f =~ s/\*?$//;
            if (length($f)>0) {
               # If there was more than a star.
               $result = $result * parseTerm($f,$factor,@vars);
            }
         }
      }
   } else {
      # We must have a monomial!
      $result = new Polynomial(0, $ring) + parseTerm(pop @term, pop @termtilda, @vars);
   }
   if ($op eq "-") {
      $result = -$result;
   }
   if (length($stilda) > 0) {
      $result = parseSumRecursively($s,$stilda, $ring, @vars) + $result;
   }
   return $result;
}



sub parseTerm {
   my ($s, $stilda, @vars) = @_;
   # Sanity check:
   # print "Parsing Monomial.\nS:  ",$s,"\nST: ",$stilda,"\n";
   ($s !~ m/^\*(.*)/) or die "Incorrect monomial form.";
   ($s !~ m/(.*)\*$/) or die "Incorrect monomial form.";
   if ($stilda !~ m/m/) {
      if (index($stilda,"/")>-1) {
         my ($a,$b) = $stilda =~ m/^(\d*)\/(\d*)/;
         return new Rational($a,$b);
      } else {
         return $stilda;
      }
   }
   my %varMap = ();
   my $i = 0;
   foreach my $v (@vars){
      my $varstring = $v."";
      $varMap{$varstring} = $i;
      $i++;
   }
   my $nvars = @vars;
   my $exp = new Vector<Int>($nvars);
   my $totalcoef = 1;
   while (length($s)>0) {
      my ($coef,$star1,$varlength,$hat,$pow,$star2) = $stilda =~ m/^(\d*\/?\d*)(\*?)(ma*)(\^?)(\d*)(\*?)/;
      my $varstring = substr($s, length($coef.$star1), length($varlength));
      if ($pow) {
         $exp->[$varMap{$varstring}] += $pow;
      } else {
         $exp->[$varMap{$varstring}] ++;
      }
      my $totallength = length($coef.$star1.$varlength.$hat.$pow.$star2);
      $s = substr($s, $totallength, length($s));
      $stilda = substr($stilda,$totallength,length($stilda));
      if ($coef) {
         if (index($coef,"/")>-1) {
            my ($a,$b) = $coef =~ m/^(\d*)\/(\d*)/;
            $totalcoef *= new Rational($a,$b);
         } else {
            $totalcoef *= $coef;
         }
      }
   }
   my $result = 1;
   my $i = 0;
   foreach my $var (@vars) {
      my $pow = $exp->[$i];
      while ($pow>0) {
         $result = $result * $var;
         $pow--;
      }
      $i++;
   }
   
   # print "Result: ",$result," ",$exp," ",%varMap,"\n";
   if ($totalcoef != 1) {
      return $totalcoef*$result;
   } else {
      return $result;
   }
}
