#  Copyright (c) 1997-2021
#  Ewgenij Gawrilow, Michael Joswig, and the polymake team
#  Technische Universität Berlin, Germany
#  https://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.
#-------------------------------------------------------------------------------

# @topic category property_types/Basic Types
# This category contains all basic types, in particular those that wrap C++, GMP or perl types such as [[Int]], [[Integer]], [[Rational]], [[Float]], [[Array]], [[String]], ...

# @topic category functions/Arithmetic
# These are functions that perform arithmetic computations.

# @topic category functions/Linear Algebra
# These functions are for algebraic computations and constructions of special matrices.

# make system() function interruptible
# this should be called early enough, before all other rules and application-specific modules are loaded
Interrupts::override_system();

# @category Basic Types
# Plain text without any imposed structure.
declare property_type Text {

   method construct() { undef }

   method construct($) { "$_[1]" }

   type_method parse { $_[1] }

   type_method equal { $_[1] eq $_[2] }

   type_method JSONschema { return { type => "string" }; }
}

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

# @category Basic Types
# Corresponds to the C++ type __std::string__.
declare property_type String : c++ (builtin => 'std::string') {

   method construct() { "" }

   method construct($) { "$_[1]" }

   type_method parse { $_[1] =~ $quoted_re ? $+{quoted} : $_[1] }

   type_method toString {
      my ($proto, $value)=@_;
      !length($value) || $value =~ /\s/ ? "'$value'" : $value
   }

   type_method equal { $_[1] eq $_[2] }

   type_method isa { is_string($_[1]) }

   type_method JSONschema { return { type => "string" }; }
}

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

# @category Basic Types
# Corresponds to the C++ type __Int__.
declare property_type Int : c++ (builtin => 'Int') {

   method construct() { 0 }

   method construct($) {
      use integer;
      0+$_[1]
   }

   type_method parse { extract_integer($_[1]) }

   type_method isa { is_integer($_[1]) }

   type_method JSONschema { return { type => "integer" }; }

   # produce the highest possible value
   user_method max(:static) : c++ (class => 'std::numeric_limits<Int>');

   # produce the lowest possible value
   user_method min(:static) : c++ (class => 'std::numeric_limits<Int>');
}

# @category Basic Types
# Corresponds to the C++ type __bool__.
declare property_type Bool : c++ (builtin => 'bool') {

   method construct() { false }

   method construct($) { !(!$_[1]) }

   type_method parse { extract_boolean($_[1]) }

   type_method isa { is_boolean($_[1]) }

   type_method toString { to_boolean_string($_[1]) }

   type_method JSONschema { return { type => "boolean" }; }
}

# @category Basic Types
# Corresponds to the C++ type __double__.
declare property_type Float : upgrades(Int) : c++ (builtin => 'double') {

   method construct() { 0.0 }

   method construct($) { 0.0+$_[1] }

   type_method parse { extract_float($_[1]) }

   type_method equal {
      my ($proto, $a, $b)=@_;
      if (isinf($a) || isinf($b)) {
         return isinf($a) == isinf($b);
      }
      my $max=max(abs($a), abs($b));
      $max<=1e-7 || abs($a-$b)<=1e-7*$max;
   }

   type_method isa { is_float($_[1]) }

   # produce an infinitely large positive value
   user_method inf(:static) : c++ (name => 'infinity', class => 'std::numeric_limits<double>');

   # produce an infinitely large negative value
   user_method minus_inf() { -&inf }

   type_method JSONschema { return { type => [ "number", "string" ], pattern => '^-?[Ii]nf$' }; }
}

# @category Artificial
declare property_type LocalFloatEpsilon : c++ (special => 'pm::local_epsilon_keeper');

function local_epsilon($) : c++ (include => "polymake/internal/comparators.h");

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

sub inhibited_num { croak( "can't use ", $_[0]->type->full_name, " in a context allowing only primitive numeric types" ) }

# @category Basic Types
# An integer of arbitrary size.
declare property_type Integer : upgrades(Int) : c++ (include => "polymake/Integer.h") {

   # produce an infinitely large positive value
   user_method inf(:static) : c++ (name => 'max', class => 'std::numeric_limits<Integer>');

   # produce an infinitely large negative value
   user_method minus_inf(:static) : c++ (name => 'min', class => 'std::numeric_limits<Integer>');

   operator ++ -- bool neg ! << <<= >> >>= @compare : c++;

   operator + - * / % += -= *= /= %= (*,*:num) : c++;

   function abs(*) : operator abs : c++;

   method compare(*) : operator <=> : c++;

   use overload '0+' => \&inhibited_num;

   type_method JSONschema { return { type => "string", pattern => '^-?(\\d+|inf)$' }; }
}


# use automatically for all literal long integral constants in perl input
namespaces::intercept_operation(undef, 'INT', sub { (typeof Integer)->construct->($_[0]) });

# @category Combinatorics
# Compute the binomial coefficient __//n// choose //k//__.
# Negative values of //n// (and //k//) are supported.
# @param Int n
# @param Int k
# @return Integer //n// choose //k//
# @example Print 6 choose 4 like this:
# > print binomial(6,4);
# | 15
user_function binomial(*,$) : c++ (name => 'binom', class => 'Integer', include => "polymake/Integer.h");

# @category Combinatorics
# Compute the __n__-th Fibonacci number
# @param Int n
# @return Integer
# @example
# > print fibonacci(6);
# | 8
user_function fibonacci(Int) : c++ (class => 'Integer', include => "polymake/Integer.h");

# @category Combinatorics
# Compute the __n__-th and __n__-1-th Fibonacci numbers
# @param Int n
# @return List<Integer>
# @example
# > print join " ", fibonacci2(6);
# | 8 5
user_function fibonacci2(Int) : returns(@) : c++ (class => 'Integer', include => "polymake/Integer.h");

# @category Basic Types
# A class for rational numbers.
# You can easily create a Rational like that: $x = 1/2;
declare property_type Rational : upgrades(Integer) : c++ (include => "polymake/Rational.h") {

   # Literal fraction constants in the perl input are also built with this constructor.
   method construct(*,*) : c++ : const_creation( / );

   # Produce an infinitely large positive value.
   user_method inf(:static) : c++ (name => 'max', class => 'std::numeric_limits<Rational>');

   # Produce an infinitely large negative value.
   user_method minus_inf(:static) : c++ (name => 'min', class => 'std::numeric_limits<Rational>');

   operator ++ -- bool neg ! << <<= >> >>= @compare : c++;

   operator + - * / += -= *= /= (*,*:num) : c++;

   function pow(*,*:num) : operator ** : c++ (name => 'pow', class => 'Rational', include => "polymake/Rational.h");

   function abs(*) : operator abs : c++;

   method compare(*) : operator <=> : c++;

   use overload '0+' => \&inhibited_num;

   type_method JSONschema { return { type => "string", pattern => '^-?(\\d+(/\\d+)?|inf)$' }; }
}

property_type Integer {

   function pow(*,*:num) : operator ** : c++ (name => 'pow', class => 'Integer', include => "polymake/Integer.h") {
      if ($_[1] < 0) {
         CROAK_SKIP:
         return &Rational::pow;
      }
      # fallback to Integer pow
   }
}

# @category Basic Types
# A wrapper for AccurateFloat.
declare property_type AccurateFloat : c++ (include => "polymake/AccurateFloat.h") {

   operator bool neg ! @compare + - * / += -= *= /= : c++;

   function abs(*) : operator abs : c++;

   method compare(*) : operator <=> : c++;

   use overload '0+' => \&inhibited_num;
}

# @category Arithmetic
# Returns the __numerator__ of //a// in a reduced representation.
# @param Rational a
# @return Integer
user_function numerator(Rational&&) : c++ : returns(lvalue);


# @category Arithmetic
# Returns the __denominator__ of //a// in a reduced representation.
# @param Rational a
# @return Integer
user_function denominator(Rational&&) : c++ : returns(lvalue);


# @category Arithmetic
# The __floor function__. Returns the smallest integral number not larger than //a//.
# @param Rational a
# @return Rational
# @example
# > print floor(1.8);
# | 1

user_function floor(*) : c++ (include => "polymake/Rational.h");


# @category Arithmetic
# The __ceiling function__. Returns the smallest integral number not smaller than //a//.
# @param Rational a
# @return Rational
user_function ceil(*) : c++ (include => "polymake/Rational.h");


# @category Arithmetic
# Check whether the given number has a finite value.
# @param __Scalar__ a
# @return Bool
# @example
# > print isfinite('inf');
# | false
# > print isfinite(23);
# | true

user_function isfinite(*) : c++ (include => "polymake/Integer.h");


# @category Arithmetic
# Check whether the given number has an infinite value.  Return -1/+1 for infinity and 0 for all finite values.
# @param __Scalar__ a
# @return Int
# @example
# > print isinf('inf');
# | 1
# > print isinf(23);
# | 0
user_function isinf(*) : c++ (include => "polymake/Integer.h");


# @category Arithmetic
# Computes the ratio of two given integral numbers under the assumption that the dividend is a multiple of the divisor.
# @param Integer a
# @param Integer b a divisor of //a//
# @return Integer
# @example
# > print div_exact(10,5);
# | 2
user_function div_exact(*:num, *:num) : c++ (include => "polymake/numerical_functions.h");


# @topic category property_types/Arithmetic
# These types are needed as return types of arithmetic computations.

# @category Arithmetic
# The complete result of an integral division
# @field Scalar quot the quotient
# @field Scalar rem the remainder
# @tparam Scalar type supporting modulo division, e.g. [[Integer]] or [[UniPolynomial]]
declare property_type Div<Scalar> : c++ (include => "polymake/numerical_functions.h");

# @category Arithmetic
# Compute the quotient and remainder of //a// and //b// in one operation.
# @param __Scalar__ a
# @param __Scalar__ b
# @return Div<__Scalar__>
# @example
# > $d = div(10,3);
# > print $d->quot;
# | 3
# > print $d->rem;
# | 1

user_function div(*,*) : c++ (include => "polymake/numerical_functions.h");


# @category Arithmetic
# Computes the __greatest common divisor__ of two integers.
# @param Int a
# @param Int b
# @return Int
# @example
# > print gcd(6,9);
# | 3
user_function gcd(*:num, *:num) : c++ (include => "polymake/numerical_functions.h");

# @category Arithmetic
# The complete result of the calculation of the __greatest common divisor__ of two numbers //a// and //b//:
# @field Scalar g The greatest common divisor, gcd(//a//,//b//)
# @field Scalar p The __co-factor__ of //a//: [[g]]=//a//*p+//b//*[[q]]
# @field Scalar q The __co-factor__ of //b//: [[g]]=//a//*[[p]]+//b//*q
# @field Scalar k1 The __factor__ of //a//: //a//=[[g]]*k1
# @field Scalar	k2 The __factor__ of //b//: //b//=[[g]]*k2
# @tparam Scalar type of operands, must be a GCD domain
declare property_type ExtGCD<Scalar> : c++ (include => "polymake/numerical_functions.h");

# @category Arithmetic
# Compute the greatest common divisor of two numbers (a,b) and accompanying co-factors.
# @param __Scalar__ a
# @param __Scalar__ b
# @return ExtGCD
# @example
# > $GCD = ext_gcd(15,6);
# The GCD of the numbers can then be accessed like this:
# > print $GCD->g;
# | 3
# The ExtGCD type also stores the Bezout coefficients (thus integers p and q such that g=a*p+b*q)...
# > print $GCD->p;
# | 1
# > print $GCD->q;
# | -2
# ...and the quotients k1 of a and k2 of b by g.
# > print $GCD->k1;
# | 5
# > print $GCD->k2;
# | 2

user_function ext_gcd(*:num, *:num) : c++ (include => "polymake/numerical_functions.h");


# @category Arithmetic
# Computes the __least common multiple__ of two integers.
# @param Int a
# @param Int b
# @return Int
# @example
# > print lcm(6,9);
# | 18

user_function lcm(*:num, *:num) : c++ (include => "polymake/numerical_functions.h");


# @category Arithmetic
# Computes the factorial //n//! = n·(n-1)·(n-2)·...·2·1.
# @param Int n >=0
# @return Integer //n//!
user_function fac(*:num) : c++ (name => 'fac', class => 'Integer', include => "polymake/Integer.h");


# @category Internal
# type checking
function is_ordered_field_with_unlimited_precision($) {
   my $type=shift;
   is_object($type) && $type->abstract or defined(wantarray) ? 0 : croak( $type->full_name, " is not a suitable coordinate type" )
}

function is_ordered_field_with_unlimited_precision(Rational) { 1 }

# @category Internal
# type checking
function is_ordered_field($) { &is_ordered_field_with_unlimited_precision }

function is_ordered_field(Float) { 1 }


# @category Arithmetic
# Compare with the zero (0) value of the corresponding data type.
# @param __Scalar__ s
# @return Bool
user_function is_zero(*) : c++ (include => "polymake/internal/comparators.h");

# @category Arithmetic
# Compare with the one (1) value of the corresponding data type.
# @param __Scalar__ s
# @return Bool
user_function is_one(*) : c++ (include => "polymake/internal/comparators.h");


# Explicit conversion to a different scalar type.
# @tparam Target
# @param __Scalar__ s
# @return Target
user_function convert_to<Target>($) {
   my $target_type=typeof Target;
   if ($target_type->isa->($_[0])) {
      $_[0]
   } else {
      $target_type->construct->($_[0]);
   }
}

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

# @category Basic Types
# Realizes quadratic extensions of fields.
#
# You can construct the value a+b\(\sqrt r\) via QuadraticExtension(a, b, r) (where a, b, r are of type //Field//).
# Caution: QuadraticExtension requires [[wiki:external_software#flint]] for normalization.
# For example, the input (1,1,4) will not be reduced to 3 without flint.
#
# @tparam Field default: [[Rational]]
declare property_type QuadraticExtension<Field=Rational> : upgrades(Field) : c++ (include => "polymake/QuadraticExtension.h") {

   # Construct q=a+b*sqrt(r).
   # @param Field a
   # @param Field b
   # @param Field r
   method construct(*, *, *) : c++;

   # construct q=a
   # @param Field a
   method construct(type_upgrades_to<Field>) : c++;

   # The quadratic extension has the form q=a+b*sqrt(r); This returns a.
   # @return Field
   method a() : c++;
   
   # The quadratic extension has the form q=a+b*sqrt(r); This returns b.
   # @return Field
   method b() : c++;

   # The quadratic extension has the form q=a+b*sqrt(r); This returns r.
   # @return Field
   method r() : c++;

   operator ! neg : c++;

   operator @arith (*, *:num) : c++;

   operator @compare : c++;

   function abs(*) : operator abs : c++;

   method compare(*) : operator <=> : c++;
   
   use overload '0+' => \&inhibited_num;
}

function conjugate(QuadraticExtension) : c++;

function is_ordered_field_with_unlimited_precision(QuadraticExtension) { 1 }

function is_qe(QuadraticExtension) { 1 }

function is_qe(type_upgrades_to<Rational>) { 0 }


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

# @category Basic Types
# Corresponds to the C++ type __std::list__.
# @tparam Element
declare property_type List<Element> : c++ (name => 'std::list', include => "polymake/list") {

   operator @compare : c++;

   # Return size of the list in constant time.
   # @return Int
   method size() : c++;

}

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

# primarily used in test suites
function compare_float_sequences($$) {
   my ($seq1, $seq2)=@_;
   my $l=$#$seq1;
   if ($l == $#$seq2) {
      my $fp=typeof Float;
      my $i=0;
      foreach my $e1 (@$seq1) {
         $fp->equal->($e1, $seq2->[$i++]) or return 0;
      }
      1
   }
}

# @category Basic Types
# An array with elements of type //Element//.
# Corresponds to the C++ type [[Array]].
# @tparam Element
declare property_type Array<Element> : (instanceof Polymake::Core::BigObjectType(Element) ? Polymake::Core::BigObjectArray : undef) \
   : c++ (include => "polymake/Array.h") {

   operator @eq : c++;

   # Returns the size.
   # @return Int
   user_method size { scalar @{$_[0]} }

   type_method init {
      my ($proto)=@_;
      if ($proto->super == typeof Polymake::Core::BigObjectArray) {
         $proto->super->init_constructor($proto);
      } elsif ($proto->params->[0] == typeof Float) {
         $proto->equal=\&compare_float_sequences;
      }
   }
}

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

# @category Artificial
# An iterator over a sequence of objects.
# The objects may be stored in a container or be generated on the fly.
# @tparam Element
declare property_type Iterator<*> : c++ {

   # inherit the overloaded ++, bool, and deref
   @ISA=qw( Polymake::Core::CPlusPlus::Iterator );

   # The index of the current element.
   # If the iterator does not support indexing, returns undef.
   # @return Int
   method index() : c++;
}

# Create an iterator visiting all elements in a container implemented as a C++ object.
function entire(*:anchor) : c++ : returns(Iterator);

# @category Artificial
# One-dimensional collection of objects, aka container in STL
# It can be accessed like a perl array.  Sequential iteration via foreach() is always supported;
# random access might be supported depending on the underlying C++ object.
declare property_type Container<*> : c++ {

   # The number of elements in the container
   # Note that for a sparse container, this number can be less or equal to scalar(@$container)
   # because the latter includes all implicit zeros and/or deleted elements
   method size() : c++;

   use overload '""' => \&Core::CPlusPlus::convert_to_string;
}

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

# @category Basic Types
# Corresponds to the C++ type __std::pair__.
# @tparam First
# @tparam Second
declare property_type Pair<First,Second> \
   : c++ (name => 'std::pair', include => "polymake/client.h", fields => [qw(first second)]) {

   operator @compare : c++;
}

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

# @category Artificial
# @tparam X
declare property_type Serialized<X> : c++ (name => "pm::Serialized", include => "polymake/client.h");

# @category Artificial
declare property_type CachedObjectPointer<X...> : c++ (name => "typeid", include => "polymake/client.h");

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

# @category Basic Types
# The Galois field of order 2
declare property_type GF2 : upgrades(Bool) : c++ (include => "polymake/GF2.h") {

   method construct() : c++;

   method construct(*) : c++;

   operator == != : c++;

   operator + - * / += -= *= /= : c++;

   operator ++ -- neg : c++;

   type_method JSONschema { return { type => "string", pattern => '^([01])$' }; }
}

INCLUDE
  set_types
  algebraic_types
  polynomial_types
  graph_types


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