#  Copyright (c) 1997-2018
#  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.
#-------------------------------------------------------------------------------

# @category Singular interface
# An intermediate object wrapping the ideal on the Singular side and providing its methods.
declare property_type SingularIdeal : c++ (include => ["polymake/ideal/singularIdeal.h"]) {

   # @category Constructors
   # Construct a Singular ideal with monomial ordering given by a matrix.
   method construct(Array<Polynomial>, Matrix<Int>) : c++;

   # @category Constructors
   # Construct a Singular ideal with monomial ordering given by a vector.
   method construct(Array<Polynomial>, Vector<Int>) : c++;

   # @category Constructors
   # Construct a Singular ideal with monomial ordering given by its name (in the Singular notation).
   method construct(Array<Polynomial>, String) : c++;

   # @category Singular interface
   # Compute the Groebner basis.
   method groebner() : void : c++ ;

   # Compute the dimension of the ideal.
   method dim() : c++ ;

   # Check via saturation whether the ideal contains a monomial.
   method contains_monomial() : c++ ;

   # Compute the initial ideal. Depends on monomial ordering.
   method initial_ideal() : c++;

   # Compute the radical of the ideal.
   method radical() : c++ ;

   method division(Polynomial) : c++;

   method solve() : c++;

   # Reduce a polynomial modulo the ideal, i.e. check ideal membership.
   method reduce(Polynomial) : c++;

   method polynomials() : c++;

   # Compute the primary decomposition of the ideal.
   method primary_decomposition() : c++;

}

object Groebner {

   # @category Singular interface
   # Intermediate object wrapping the Singular objects, i.e. the ring with the monomial ordering and the ideal.
   property SINGULAR_IDEAL : SingularIdeal : non_storable;

   # Reduce a [[Polynomial]] //p// with respect to the Groebner basis.
   # @param Polynomial p
   # @return Polynomial
   user_method reduce(Polynomial) {
      my ($self, $p) = @_;
      $self->parent->N_VARIABLES == $p->n_vars() or croak ("incompatible rings");
      return $self->SINGULAR_IDEAL->reduce($p);
   }

   user_method division(Polynomial) {
      my ($self, $p) = @_;
      $self->parent->N_VARIABLES == $p->n_vars() or croak ("incompatible rings");
      return $self->SINGULAR_IDEAL->division($p);
   }

}

object Ideal {

   rule GROEBNER.INITIAL_IDEAL : GROEBNER.SINGULAR_IDEAL, N_VARIABLES {
      my $i = $this->GROEBNER->SINGULAR_IDEAL->initial_ideal();
      $this->GROEBNER->INITIAL_IDEAL = new Ideal(GENERATORS=>$i->polynomials(), N_VARIABLES=>$this->N_VARIABLES);
   }

   rule GROEBNER.SINGULAR_IDEAL : GENERATORS, GROEBNER.ORDER_MATRIX {
      $this->GROEBNER->SINGULAR_IDEAL = new SingularIdeal($this->GENERATORS, $this->GROEBNER->ORDER_MATRIX);
      $this->GROEBNER->SINGULAR_IDEAL->groebner();
   }
   precondition : !ZERO;

   rule GROEBNER.SINGULAR_IDEAL : GENERATORS, GROEBNER.ORDER_VECTOR {
      $this->GROEBNER->SINGULAR_IDEAL = new SingularIdeal($this->GENERATORS, $this->GROEBNER->ORDER_VECTOR);
      $this->GROEBNER->SINGULAR_IDEAL->groebner();
   }
   precondition : !ZERO;

   rule GROEBNER.SINGULAR_IDEAL : GENERATORS, GROEBNER.ORDER_NAME {
      $this->GROEBNER->SINGULAR_IDEAL = new SingularIdeal($this->GENERATORS, $this->GROEBNER->ORDER_NAME);
      $this->GROEBNER->SINGULAR_IDEAL->groebner();
   }
   precondition : !ZERO;

   rule GROEBNER.BASIS : GROEBNER.SINGULAR_IDEAL, N_VARIABLES{
      $this->GROEBNER->BASIS = $this->GROEBNER->SINGULAR_IDEAL->polynomials();
   }

   rule GROEBNER.INITIAL_IDEAL.GENERATORS, GROEBNER.INITIAL_IDEAL.N_VARIABLES, GROEBNER.BASIS : GENERATORS, GROEBNER.ORDER_MATRIX , N_VARIABLES {
      my $si;
      unless ( defined($si = $this->lookup("GROEBNER.SINGULAR_IDEAL"))) {
         $si = new SingularIdeal($this->GENERATORS, $this->GROEBNER->ORDER_MATRIX);
         $si->groebner();
      }
      $this->GROEBNER->BASIS = $si->polynomials();
      my $i = $si->initial_ideal();
      $this->GROEBNER->INITIAL_IDEAL->GENERATORS = $i->polynomials();
      $this->GROEBNER->INITIAL_IDEAL->N_VARIABLES = $this->N_VARIABLES;
   }
   precondition : !ZERO;


	rule GROEBNER.SINGULAR_IDEAL : GROEBNER.BASIS , GROEBNER.ORDER_MATRIX {
  	   $this->GROEBNER->SINGULAR_IDEAL = new SingularIdeal($this->GROEBNER->BASIS, $this->GROEBNER->ORDER_MATRIX);
  	   $this->GROEBNER->SINGULAR_IDEAL->groebner();
  	}

  	rule MONOMIAL : GENERATORS {
  	   my $si;
  	   unless (defined($si = $this->lookup("GROEBNER"))) {
  	      $si = $this->GROEBNER(ORDER_NAME=>"dp",temporary);
  	   }
  	   my $result = 1;
  	   # One can easily show that it suffices to check whether the generators
  	   # of the initial ideal are contained in the ideal.
  	   foreach my $mon (@{$si->INITIAL_IDEAL->GENERATORS}){
  	      $result &&= ($si->reduce($mon)->trivial);
  	   }
  	   $this->MONOMIAL = $result;
  	}

  	rule DIM : GENERATORS {
  	   my $si;
  	   unless (defined($si = $this->lookup("GROEBNER.SINGULAR_IDEAL"))) {
  	      $si = $this->GROEBNER(ORDER_NAME=>"dp",temporary)->SINGULAR_IDEAL;
  	   }

  	   $this->DIM = $si->dim();
  	}

	user_method SOLVE : N_VARIABLES, GENERATORS {
  	  my $this = shift;
  	  my $si;
  	  unless (defined($si = $this->lookup("GROEBNER.SINGULAR_IDEAL"))) {
  	      $si = $this->GROEBNER(ORDER_NAME=>"dp",temporary)->SINGULAR_IDEAL;
  	  }
  	  $si->solve();
  	}

  	rule PRIMARY_DECOMPOSITION : N_VARIABLES, GENERATORS {
  	   my $n_vars = $this->N_VARIABLES;
  	   my $si;
  	   unless (defined($si = $this->lookup("GROEBNER.SINGULAR_IDEAL"))) {
  	      $si = $this->GROEBNER(ORDER_NAME=>"dp",temporary)->SINGULAR_IDEAL;
  	   }
  	   my @pd = $si->primary_decomposition();
  	   my @res;
  	   foreach my $id (@pd) {
  	      push @res, new Ideal(GENERATORS=>$id->polynomials(),N_VARIABLES=>$n_vars,PRIMARY=>1);
  	   }
  	   $this->PRIMARY_DECOMPOSITION = \@res;
  	}

  	rule RADICAL : N_VARIABLES, GENERATORS {
  	   my $si;
  	   unless (defined($si = $this->lookup("GROEBNER.SINGULAR_IDEAL"))) {
  	      $si = $this->GROEBNER(ORDER_NAME=>"dp",temporary)->SINGULAR_IDEAL;
  	   }
  	   my $radical = $si->radical();
  	   $this->RADICAL = new Ideal(GENERATORS=>$radical->polynomials(),N_VARIABLES=>$this->N_VARIABLES);
  	}

  	# Check via saturation whether the ideal contains a monomial.
  	# Returns a monomial from the ideal or the trivial monomial if there is none.
  	# @param String s Optional term order (see [[ORDER_NAME]]) for intermediate Groebner bases, default: "dp"
  	# @return Polynomial
  	user_method contains_monomial(;$="dp") {
  	   my ($I,$order) = @_;
  	   return $I->GROEBNER(ORDER_NAME=>$order)->SINGULAR_IDEAL->contains_monomial();
  	}
}


# Local Variables:
# mode: perl
# cperl-indent-level:3
# indent-tabs-mode:nil
# End:
