/*
 * NormalizInterface: GAP wrapper for Normaliz
 * Copyright (C) 2014  Sebastian Gutsche, Max Horn, Christof Söger
 *
 * 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 of
 * the License, or (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/*
#! @Chapter Functions
#! @Section YOU FORGOT TO SET A SECTION
*/

#include "gap_all.h" // GAP headers

#if GAP_KERNEL_MAJOR_VERSION < 8
#define BOOL Int
#endif

#define mpn_tdiv_q    // workaround issue with FLINT's inline definition of
                      // mpn_tdiv_q

#include "libnormaliz/libnormaliz.h"

#include <vector>

#include <csignal>
using std::signal;

typedef void (*sighandler_t)(int);

// old versions of libnormaliz (before 2.99.1) did not include such a define
#if !defined(NMZ_RELEASE) || NMZ_RELEASE < 30504
#error Your Normaliz version is to old! Update to 3.5.4 or newer.
#endif

#define FUNC_BEGIN try {

#define FUNC_END                                                             \
    }                                                                        \
    catch (std::exception & e)                                               \
    {                                                                        \
        ErrorQuit(e.what(), 0, 0);                                           \
        return Fail;                                                         \
    }                                                                        \
    catch (const char * a)                                                   \
    {                                                                        \
        ErrorQuit(a, 0, 0);                                                  \
        return Fail;                                                         \
    }                                                                        \
    catch (const std::string & a)                                            \
    {                                                                        \
        ErrorQuit(a.c_str(), 0, 0);                                          \
        return Fail;                                                         \
    }                                                                        \
    catch (...)                                                              \
    {                                                                        \
        ErrorQuit("PANIC: caught general exception", 0, 0);                  \
        return Fail;                                                         \
    }

#define SIGNAL_HANDLER_BEGIN                                                 \
    sighandler_t current_interpreter_sigint_handler =                        \
        signal(SIGINT, signal_handler);                                      \
    try {

#define SIGNAL_HANDLER_END                                                   \
    }                                                                        \
    catch (libnormaliz::InterruptException & e)                              \
    {                                                                        \
        signal(SIGINT, current_interpreter_sigint_handler);                  \
        libnormaliz::nmz_interrupted = false;                                \
        ErrorQuit("computation interrupted", 0, 0);                          \
        return 0;                                                            \
    }                                                                        \
    catch (...)                                                              \
    {                                                                        \
        signal(SIGINT, current_interpreter_sigint_handler);                  \
        throw;                                                               \
    }                                                                        \
    signal(SIGINT, current_interpreter_sigint_handler);


// Paranoia check
#ifdef SYS_IS_64_BIT
#if GMP_LIMB_BITS != 64
#error GAP compiled in 64 bit mode, but GMP limbs are not 64 bit
#endif
#else
#if GMP_LIMB_BITS != 32
#error GAP compiled in 32 bit mode, but GMP limbs are not 32 bit
#endif
#endif


using libnormaliz::Cone;
// using libnormaliz::ConeProperty;
using libnormaliz::ConeProperties;
using libnormaliz::Sublattice_Representation;
using libnormaliz::Type::InputType;

#ifdef LIBNORMALIZ_DYNAMIC_BITSET_H
using libnormaliz::dynamic_bitset;
#else
typedef boost::dynamic_bitset<> dynamic_bitset;
#endif

using std::map;
using std::string;
using std::vector;

static void signal_handler(int signal)
{
    libnormaliz::nmz_interrupted = true;
}

static Obj TheTypeNormalizCone;

static UInt T_NORMALIZ = 0;

static inline bool IS_CONE(Obj o)
{
    return TNUM_OBJ(o) == T_NORMALIZ;
}

template <typename Integer>
static inline void SET_CONE(Obj o, libnormaliz::Cone<Integer> * p)
{
    ADDR_OBJ(o)[0] = reinterpret_cast<Obj>(p);
}

template <typename Integer>
static inline libnormaliz::Cone<Integer> * GET_CONE(Obj o)
{
    return reinterpret_cast<libnormaliz::Cone<Integer> *>(ADDR_OBJ(o)[0]);
}

static Obj NewCone(Cone<mpz_class> * C)
{
    Obj o;
    o = NewBag(T_NORMALIZ, 1 * sizeof(Obj));
    SET_CONE<mpz_class>(o, C);
    return o;
}

static Obj NewProxyCone(Cone<mpz_class> * C, Obj parentCone)
{
    Obj o;
    o = NewBag(T_NORMALIZ, 2 * sizeof(Obj));
    SET_CONE<mpz_class>(o, C);
    ADDR_OBJ(o)[1] = parentCone;
    return o;
}

/* Free function */
static void NormalizFreeFunc(Obj o)
{
    if (SIZE_OBJ(o) != 2 * sizeof(Obj)) {
        delete GET_CONE<mpz_class>(o);
    }
}

/* Type object function for the object */
static Obj NormalizTypeFunc(Obj o)
{
    return TheTypeNormalizCone;
}

static Obj NormalizCopyFunc(Obj o, Int mut)
{
    // Cone objects are mathematically immutable, so
    // we don't need to do anything,
    return o;
}

static void NormalizCleanFunc(Obj o)
{
}

static BOOL NormalizIsMutableObjFuncs(Obj o)
{
    // Cone objects are mathematically immutable.
    return 0L;
}

//
// conversion of various C++ types to GAP objects
//

template <typename T, typename U>
static Obj NmzToGAP(const std::pair<T, U> & in);

template <typename T>
static Obj NmzToGAP(const vector<T> & in);

static Obj NmzToGAP(const dynamic_bitset & in);


static Obj NmzToGAP(const mpz_t x)
{
#if GAP_KERNEL_MAJOR_VERSION >= 7
    return MakeObjInt((const UInt *)x->_mp_d,
                      x->_mp_size);    // added in GAP 4.11
#else
    Obj res;
    Int size = x->_mp_size;
    int sign;
    if (size == 0) {
        return INTOBJ_INT(0);
    }
    else if (size < 0) {
        size = -size;
        sign = -1;
    }
    else {
        sign = +1;
    }
    if (size == 1) {
        res = ObjInt_UInt(x->_mp_d[0]);
        if (sign < 0)
            res = AInvInt(res);
    }
    else {
        size = sizeof(mp_limb_t) * size;
        if (sign > 0)
            res = NewBag(T_INTPOS, size);
        else
            res = NewBag(T_INTNEG, size);
        memcpy(ADDR_INT(res), x->_mp_d, size);
    }
    return res;
#endif
}

static Obj NmzToGAP(unsigned int x)    // key_t = unsigned int
{
    return ObjInt_UInt(x);
}

static Obj NmzToGAP(unsigned long x)    // size_t = unsigned long
{
    return ObjInt_UInt(x);
}

static Obj NmzToGAP(int x)
{
    return ObjInt_Int(x);
}

static Obj NmzToGAP(long x)
{
    return ObjInt_Int(x);
}

static Obj NmzToGAP(const mpz_class x)
{
    return NmzToGAP(x.get_mpz_t());
}

static Obj NmzToGAP(const mpq_class x)
{
    Obj num = NmzToGAP(x.get_num());
    Obj den = NmzToGAP(x.get_den());
    return QUO(num, den);
}

static Obj NmzToGAP(double x)
{
    return NEW_MACFLOAT(x);
}

template <typename T>
static Obj NmzToGAP(const libnormaliz::STANLEYDATA<T> & sd)
{
    Obj ret = NEW_PLIST(T_PLIST, 2);
    ASS_LIST(ret, 1, NmzToGAP(sd.key));
    ASS_LIST(ret, 2, NmzToGAP(sd.offsets));
    return ret;
}

/* TODO: HSOP
 *       There are two representations for Hilbert series in Normaliz,
 * standard and HSOP. Currently, only the standard representation is returned.
 */
static Obj NmzToGAP(const libnormaliz::HilbertSeries & HS)
{
    Obj ret;
    ret = NEW_PLIST(T_PLIST, 3);
    ASS_LIST(ret, 1, NmzToGAP(HS.getNum()));
    ASS_LIST(ret, 2, NmzToGAP(libnormaliz::to_vector(HS.getDenom())));
    ASS_LIST(ret, 3, NmzToGAP(HS.getShift()));
    return ret;
}

static Obj
NmzHilbertQuasiPolynomialToGAP(const libnormaliz::HilbertSeries & HS)
{
    Obj ret = NmzToGAP(HS.getHilbertQuasiPolynomial());
    AddList(ret, NmzToGAP(HS.getHilbertQuasiPolynomialDenom()));
    return ret;
}

//
// generic recursive conversion of C++ containers to GAP objects
//

// convert maps to list of [key,value] pairs
template <typename K, typename V>
static Obj NmzToGAP(const map<K, V> & in)
{
    Obj list = NEW_PLIST(T_PLIST, in.size());
    for (const auto & it : in) {
        AddList(list, NmzToGAP(it));
    }
    return list;
}

template <typename T>
static Obj NmzToGAP(const std::list<T> & in)
{
    Obj list = NEW_PLIST(T_PLIST, in.size());
    for (const auto & it : in) {
        AddList(list, NmzToGAP(it));
    }
    return list;
}

template <typename T>
static Obj NmzToGAP(const vector<T> & in)
{
    const size_t n = in.size();
    Obj          list = NEW_PLIST(T_PLIST, n);
    for (size_t i = 0; i < n; ++i) {
        ASS_LIST(list, i + 1, NmzToGAP(in[i]));
    }
    return list;
}

template <>
Obj NmzToGAP(const vector<bool> & in)
{
    const size_t n = in.size();
    Obj          list = NewBag(T_BLIST, SIZE_PLEN_BLIST(n));
    SET_LEN_BLIST(list, n);
    for (size_t i = 0; i < n; ++i) {
        if (in[i])
            SET_BIT_BLIST(list, i + 1);
    }
    return list;
}

static Obj NmzToGAP(const dynamic_bitset & in)
{
    const size_t n = in.size();
    Obj          list = NewBag(T_BLIST, SIZE_PLEN_BLIST(n));
    SET_LEN_BLIST(list, n);
    for (size_t i = 0; i < n; ++i) {
        if (in.test(i))
            SET_BIT_BLIST(list, i + 1);
    }
    return list;
}

template <typename T, typename U>
static Obj NmzToGAP(const std::pair<T, U> & in)
{
    Obj pair = NEW_PLIST(T_PLIST, 2);
    ASS_LIST(pair, 1, NmzToGAP(in.first));
    ASS_LIST(pair, 2, NmzToGAP(in.second));
    return pair;
}

// TODO: generate a "proper" MatrixObj, once available
template <typename T>
static Obj NmzToGAP(const libnormaliz::Matrix<T> & in)
{
    return NmzToGAP(in.get_elements());
}

template <typename T>
static Obj NmzToGAP(const libnormaliz::SHORTSIMPLEX<T> & in)
{
    Obj simplex = NEW_PREC(5);
    ASS_REC(simplex, RNamName("key"), NmzToGAP(in.key));
    ASS_REC(simplex, RNamName("height"), NmzToGAP(in.height));
    ASS_REC(simplex, RNamName("vol"), NmzToGAP(in.vol));
    ASS_REC(simplex, RNamName("mult"), NmzToGAP(in.mult));
    ASS_REC(simplex, RNamName("Excluded"), NmzToGAP(in.Excluded));
    return simplex;
}

bool GAPToNmz(long & out, Obj x)
{
    if (IS_INTOBJ(x)) {
        out = INT_INTOBJ(x);
        return true;
    }
    else if (TNUM_OBJ(x) == T_INTPOS || TNUM_OBJ(x) == T_INTNEG) {
        UInt size = SIZE_INT(x);
        if (size == 1) {
            out = *ADDR_INT(x);
            if (out < 0)
                return false;    // overflow
            if (TNUM_OBJ(x) == T_INTNEG)
                out = -out;
            return true;
        }
    }
    return false;
}

bool GAPToNmz(mpz_class & out, Obj x)
{
    if (IS_INTOBJ(x)) {
        out = INT_INTOBJ(x);
        return true;
    }
    else if (TNUM_OBJ(x) == T_INTPOS || TNUM_OBJ(x) == T_INTNEG) {
        UInt    size = SIZE_INT(x);
        mpz_ptr m = out.get_mpz_t();
        mpz_realloc2(m, size * GMP_NUMB_BITS);
        memcpy(m->_mp_d, ADDR_INT(x), sizeof(mp_limb_t) * size);
        m->_mp_size = (TNUM_OBJ(x) == T_INTPOS) ? (Int)size : -(Int)size;
        return true;
    }
    return false;
}

bool GAPToNmz(mpq_class & out, Obj x)
{
    if (IS_INTOBJ(x)) {
        out = INT_INTOBJ(x);
        return true;
    }
    else if (TNUM_OBJ(x) == T_INTPOS || TNUM_OBJ(x) == T_INTNEG) {
        out.get_den() = 1;
        return GAPToNmz(out.get_num(), x);
    }
    else if (TNUM_OBJ(x) == T_RAT) {
        return GAPToNmz(out.get_num(), NUM_RAT(x)) &&
               GAPToNmz(out.get_den(), DEN_RAT(x));
    }
    return false;
}

template <typename Number>
static bool GAPToNmz(vector<Number> & out, Obj V)
{
    if (!IS_LIST(V) || !IS_DENSE_LIST(V))
        return false;
    const int n = LEN_LIST(V);
    out.resize(n);
    for (int i = 0; i < n; ++i) {
        Obj tmp = ELM_LIST(V, i + 1);
        if (!GAPToNmz(out[i], tmp))
            return false;
    }
    return true;
}

// take either a GAP string, or a GAP small integer, and try to convert
// it to a Normaliz ConeProperty enum value.
static libnormaliz::ConeProperty::Enum GetConeProperty(Obj prop)
{
    if (IS_STRING_REP(prop))
        return libnormaliz::toConeProperty(CSTR_STRING(prop));
    if (IS_NONNEG_INTOBJ(prop))
        return (libnormaliz::ConeProperty::Enum)UInt8_ObjInt(prop);

    ErrorQuit("<prop> must be a string or a non-negative small integer", 0,
              0);
}

template <typename Integer>
static Obj _NmzConeIntern(Obj input_list)
{
    bool                                      has_polynomial_input = false;
    string                                    polynomial;
    map<InputType, vector<vector<mpq_class>>> input;
    const int                                 n = LEN_PLIST(input_list);
    if (n & 1) {
        throw std::runtime_error(
            "Input list must have even number of elements");
    }
    for (int i = 0; i < n; i += 2) {
        Obj type = ELM_PLIST(input_list, i + 1);
        if (!IS_STRING_REP(type)) {
            throw std::runtime_error(
                "Element " + std::to_string(i + 1) +
                " of the input list must be a type string");
        }
        string type_str(CSTR_STRING(type));
        Obj    M = ELM_PLIST(input_list, i + 2);
        if (type_str.compare("polynomial") == 0) {
            if (!IS_STRING_REP(M)) {
                throw std::runtime_error(
                    "Element " + std::to_string(i + 2) +
                    " of the input list must be a string");
            }
            polynomial = string(CSTR_STRING(M));
            has_polynomial_input = true;
            continue;
        }
        vector<vector<mpq_class>> Mat;
        bool                      okay = GAPToNmz(Mat, M);
        if (!okay) {
            throw std::runtime_error(
                "Element " + std::to_string(i + 2) +
                " of the input list must be an integer matrix");
        }

        input[libnormaliz::to_type(type_str)] = Mat;
    }

    Cone<Integer> * C = new Cone<Integer>(input);
    if (has_polynomial_input) {
        C->setPolynomial(polynomial);
    }
    Obj Cone = NewCone(C);
    return Cone;
}

static Obj Func_NmzCone(Obj self, Obj input_list)
{
    if (!IS_PLIST(input_list) || !IS_DENSE_LIST(input_list))
        ErrorQuit("Input argument must be a list", 0, 0);

    FUNC_BEGIN
    return _NmzConeIntern<mpz_class>(input_list);
    FUNC_END
}

static Obj Func_NmzCompute(Obj self, Obj cone, Obj to_compute)
{
    if (!IS_CONE(cone))
        ErrorQuit("<cone> must be a Normaliz cone", 0, 0);
    if (!IS_PLIST(to_compute) || !IS_DENSE_LIST(to_compute))
        ErrorQuit("<props> must be a list of strings", 0, 0);

    FUNC_BEGIN
    ConeProperties propsToCompute;
    // we have a list
    const int n = LEN_PLIST(to_compute);

    for (int i = 0; i < n; ++i) {
        Obj prop = ELM_PLIST(to_compute, i + 1);
        if (!IS_STRING_REP(prop)) {
            throw std::runtime_error(
                "Element " + std::to_string(i + 1) +
                " of the input list must be a type string");
        }
        string prop_str(CSTR_STRING(prop));
        propsToCompute.set(libnormaliz::toConeProperty(prop_str));
    }

    Cone<mpz_class> * C = GET_CONE<mpz_class>(cone);

    ConeProperties notComputed;
    SIGNAL_HANDLER_BEGIN
    notComputed = C->compute(propsToCompute);
    SIGNAL_HANDLER_END

    // Cone.compute returns the not computed properties
    // we return a bool, true when everything requested was computed
    return notComputed.none() ? True : False;
    FUNC_END
}


/*
#! @Section Use a NmzCone
#! @Arguments cone property
#! @Returns whether the cone has already computed the given property
#! @Description
#! See <Ref Func="NmzConeProperty"/> for a list of recognized properties.
#!
#! @InsertChunk NmzHasConeProperty_example
DeclareGlobalFunction("NmzHasConeProperty");
*/
static Obj FuncNmzHasConeProperty(Obj self, Obj cone, Obj prop)
{
    if (!IS_CONE(cone))
        ErrorQuit("<cone> must be a Normaliz cone", 0, 0);

    libnormaliz::ConeProperty::Enum p = GetConeProperty(prop);

    FUNC_BEGIN
    Cone<mpz_class> * C = GET_CONE<mpz_class>(cone);
    return C->isComputed(p) ? True : False;
    FUNC_END
}

static Obj Func_NmzConePropertiesNamesRecord(Obj self)
{
    FUNC_BEGIN

    Obj M = NEW_PREC(libnormaliz::ConeProperty::EnumSize);

    for (int i = 0; i < libnormaliz::ConeProperty::EnumSize; ++i) {
        libnormaliz::ConeProperty::Enum p =
            (libnormaliz::ConeProperty::Enum)i;

#if NMZ_RELEASE >= 30500
        // skip internal control properties
        if (
#if NMZ_RELEASE < 30800
            p == libnormaliz::ConeProperty::ExplicitHilbertSeries ||
#endif
            p == libnormaliz::ConeProperty::NakedDual)
            continue;
#endif
        AssPRec(M, RNamName(libnormaliz::toString(p).c_str()),
                ObjInt_UInt8(p));
    }
    return M;

    FUNC_END
}

/*
#! @Section Use a NmzCone
#! @Arguments cone
#! @Returns a list of strings representing the known (computed) cone
#!   properties
#! @Description
#! Given a Normaliz cone object, return a list of all properties already
#! computed for the cone.
#!
#! @InsertChunk NmzKnownConeProperties_example
DeclareGlobalFunction("NmzKnownConeProperties");
*/
static Obj FuncNmzKnownConeProperties(Obj self, Obj cone)
{
    if (!IS_CONE(cone))
        ErrorQuit("<cone> must be a Normaliz cone", 0, 0);

    FUNC_BEGIN

    size_t n = 0;
    Obj    M = NEW_PLIST(T_PLIST, libnormaliz::ConeProperty::EnumSize);
    Cone<mpz_class> * C = GET_CONE<mpz_class>(cone);

    // FIXME: This code could be a lot simpler if there was
    // a Cone method for reading the value of is_Computed.
    for (int i = 0; i < libnormaliz::ConeProperty::EnumSize; ++i) {
        libnormaliz::ConeProperty::Enum p =
            (libnormaliz::ConeProperty::Enum)i;

        // skip internal control properties
        if (
#if NMZ_RELEASE < 30800
            p == libnormaliz::ConeProperty::ExplicitHilbertSeries ||
#endif
            p == libnormaliz::ConeProperty::NakedDual)
            continue;

        if (C->isComputed(p)) {
            string prop_name(libnormaliz::toString(p));

            ASS_LIST(M, ++n, MakeImmString(prop_name.c_str()));
            if (p == libnormaliz::ConeProperty::HilbertSeries) {
                const libnormaliz::HilbertSeries & HS = C->getHilbertSeries();
                HS.computeHilbertQuasiPolynomial();
                if (HS.isHilbertQuasiPolynomialComputed()) {
                    ASS_LIST(M, ++n, MakeImmString("HilbertQuasiPolynomial"));
                }
            }
        }
    }
    SORT_LIST(M);
    return M;

    FUNC_END
}

template <typename Integer>
static Obj _NmzConePropertyImpl(Obj cone, libnormaliz::ConeProperty::Enum p)
{
    Cone<Integer> * C = GET_CONE<Integer>(cone);
    ConeProperties  notComputed;

    SIGNAL_HANDLER_BEGIN
    notComputed = C->compute(ConeProperties(p));
    SIGNAL_HANDLER_END

#if NMZ_RELEASE >= 30700
    // workaround a bug where computing HilbertQuasiPolynomial after
    // HilbertSeries was already computed returned notComputed equal to
    // NoGradingDenom, even though of course the quasi polynomial was
    // available.
    notComputed.reset(libnormaliz::ConeProperty::NoGradingDenom);
#endif
    if (notComputed.any()) {
        ErrorQuit("Failed to compute %s, missing properties: %s",
                  (Int)(libnormaliz::toString(p).c_str()),
                  (Int)(libnormaliz::toString(notComputed).c_str()));
        return Fail;
    }

    // workaround bug in certain Normaliz versions, where the output type for
    // ClassGroups is reported as libnormaliz::OutputType::Vector, but calling
    // getVectorConeProperty on it produces an error
    if (p == libnormaliz::ConeProperty::ClassGroup)
        return NmzToGAP(C->getClassGroup());

#if NMZ_RELEASE >= 30700 && NMZ_RELEASE < 30703
    // workaround bug where getMachineIntegerConeProperty does not support
    // NumberLatticePoints
    if (p == libnormaliz::ConeProperty::NumberLatticePoints)
        return NmzToGAP(C->getNumberLatticePoints());
#endif

#if NMZ_RELEASE < 30804
    // workaround: these two properties are marked as having output type
    // "void"
    if (p == libnormaliz::ConeProperty::IsTriangulationNested)
        return C->isTriangulationNested() ? True : False;
    if (p == libnormaliz::ConeProperty::IsTriangulationPartial)
        return C->isTriangulationPartial() ? True : False;
#endif

    switch (libnormaliz::output_type(p)) {
    case libnormaliz::OutputType::Matrix:
        // TODO: switch to getMatrixConePropertyMatrix ?
        return NmzToGAP(C->getMatrixConeProperty(p));

    case libnormaliz::OutputType::MatrixFloat:
        // TODO: switch to getFloatMatrixConePropertyMatrix ?
        return NmzToGAP(C->getFloatMatrixConeProperty(p));

    case libnormaliz::OutputType::Vector:
        return NmzToGAP(C->getVectorConeProperty(p));

    case libnormaliz::OutputType::Integer:
        return NmzToGAP(C->getIntegerConeProperty(p));

    case libnormaliz::OutputType::GMPInteger:
        return NmzToGAP(C->getGMPIntegerConeProperty(p));

    case libnormaliz::OutputType::Rational:
        return NmzToGAP(C->getRationalConeProperty(p));

#if NMZ_RELEASE >= 30700
    case libnormaliz::OutputType::FieldElem:
        throw "OutputType::FieldElem not yet supported";
#endif

    case libnormaliz::OutputType::Float:
        return NmzToGAP(C->getFloatConeProperty(p));

    case libnormaliz::OutputType::MachineInteger:
        return NmzToGAP(C->getMachineIntegerConeProperty(p));

    case libnormaliz::OutputType::Bool:
        return C->getBooleanConeProperty(p) ? True : False;

    case libnormaliz::OutputType::Complex:
        // more complex data structures are handled below
        break;

    case libnormaliz::OutputType::Void:
        // throw "cone property is input-only";
        return Fail;

    default:
        throw "unsupported output_type";
    }

    switch (p) {
#if NMZ_RELEASE >= 30703
    case libnormaliz::ConeProperty::AmbientAutomorphisms:
    case libnormaliz::ConeProperty::Automorphisms:
    case libnormaliz::ConeProperty::CombinatorialAutomorphisms:
    case libnormaliz::ConeProperty::EuclideanAutomorphisms:
    case libnormaliz::ConeProperty::RationalAutomorphisms:
        throw "querying automorphisms not yet supported";
#endif

    case libnormaliz::ConeProperty::ConeDecomposition:
#if NMZ_RELEASE >= 30810
        return NmzToGAP(C->getConeDecomposition());
#else
        return NmzToGAP(C->getOpenFacets());
#endif

#if NMZ_RELEASE >= 30600
    case libnormaliz::ConeProperty::EhrhartQuasiPolynomial:
#if NMZ_RELEASE >= 30700
        return NmzHilbertQuasiPolynomialToGAP(C->getEhrhartSeries());
#else
        throw "Extracting EhrhartQuasiPolynomial requires Normaliz >= 3.7.0";
#endif
#endif

    case libnormaliz::ConeProperty::EhrhartSeries:
#if NMZ_RELEASE >= 30700
        return NmzToGAP(C->getEhrhartSeries());
#else
        throw "Extracting EhrhartSeries requires Normaliz >= 3.7.0";
#endif

#if NMZ_RELEASE >= 30700
    case libnormaliz::ConeProperty::FaceLattice:
        return NmzToGAP(C->getFaceLattice());

    case libnormaliz::ConeProperty::FVector:
        return NmzToGAP(C->getFVector());
#endif

    case libnormaliz::ConeProperty::Incidence:
        return NmzToGAP(C->getIncidence());

    case libnormaliz::ConeProperty::HilbertQuasiPolynomial:
        return NmzHilbertQuasiPolynomialToGAP(C->getHilbertSeries());

    case libnormaliz::ConeProperty::HilbertSeries:
        return NmzToGAP(C->getHilbertSeries());

    case libnormaliz::ConeProperty::InclusionExclusionData:
        return NmzToGAP(C->getInclusionExclusionData());

    case libnormaliz::ConeProperty::IntegerHull:
        return NewProxyCone(&(C->getIntegerHullCone()), cone);

    case libnormaliz::ConeProperty::ProjectCone:
        return NewProxyCone(&(C->getProjectCone()), cone);

    case libnormaliz::ConeProperty::Sublattice:
        return _NmzBasisChangeIntern(C);

    case libnormaliz::ConeProperty::StanleyDec:
        return NmzToGAP(C->getStanleyDec());

    case libnormaliz::ConeProperty::Triangulation:
        return NmzToGAP(C->getTriangulation());

    case libnormaliz::ConeProperty::WeightedEhrhartQuasiPolynomial:
        return NmzHilbertQuasiPolynomialToGAP(
            C->getWeightedEhrhartSeries().first);

    case libnormaliz::ConeProperty::WeightedEhrhartSeries:
        return NmzToGAP(C->getWeightedEhrhartSeries());

    default:
        throw "unsupported cone property " + libnormaliz::toString(p);
    }

    return Fail;
}

static Obj Func_NmzConeProperty(Obj self, Obj cone, Obj prop)
{
    if (!IS_CONE(cone))
        ErrorQuit("<cone> must be a Normaliz cone", 0, 0);

    libnormaliz::ConeProperty::Enum p = GetConeProperty(prop);

    FUNC_BEGIN
    return _NmzConePropertyImpl<mpz_class>(cone, p);
    FUNC_END
}


/*
#! @Section Use a NmzCone
#! @Arguments verboseFlag
#! @Returns the previous verbosity
#! @Description
#! Set the global default verbosity state in libnormaliz.
#! This will influence all NmzCone created afterwards, but not any existing
#! ones.
#!
#! See also <Ref Func="NmzSetVerbose"/>
DeclareGlobalFunction("NmzSetVerboseDefault");
*/
static Obj FuncNmzSetVerboseDefault(Obj self, Obj value)
{
    if (value != True && value != False)
        ErrorQuit("<value> must be a boolean value", 0, 0);
    FUNC_BEGIN
    return libnormaliz::setVerboseDefault(value == True) ? True : False;
    FUNC_END
}


/*
#! @Arguments cone verboseFlag
#! @Returns the previous verbosity
#! @Description
#! Set the verbosity state for a cone.
#!
#! See also <Ref Func="NmzSetVerboseDefault"/>
DeclareGlobalFunction("NmzSetVerbose");
*/
static Obj FuncNmzSetVerbose(Obj self, Obj cone, Obj value)
{
    if (!IS_CONE(cone))
        ErrorQuit("<cone> must be a Normaliz cone", 0, 0);
    if (value != True && value != False)
        ErrorQuit("<value> must be a boolean value", 0, 0);
    bool old_value;

    FUNC_BEGIN
    Cone<mpz_class> * C = GET_CONE<mpz_class>(cone);
    old_value = C->setVerbose(value == True);
    return old_value ? True : False;
    FUNC_END
}

/*
#! @Section Cone properties
*/


template <typename Integer>
static Obj _NmzBasisChangeIntern(Cone<Integer> * C)
{
    Sublattice_Representation<Integer> bc;
    SIGNAL_HANDLER_BEGIN
    bc = C->getSublattice();
    SIGNAL_HANDLER_END

    Obj res = NEW_PLIST(T_PLIST, 3);
    ASS_LIST(res, 1, NmzToGAP(bc.getEmbedding()));
    ASS_LIST(res, 2, NmzToGAP(bc.getProjection()));
    ASS_LIST(res, 3, NmzToGAP(bc.getAnnihilator()));
    // Dim, Rank, Equations and Congruences are already covered by special
    // functions The index is not always computed and not so relevant
    return res;
}


static Obj Func_NmzVersion(Obj self)
{
    Obj res = NEW_PLIST(T_PLIST, 3);
    ASS_LIST(res, 1, INTOBJ_INT(NMZ_VERSION_MAJOR));
    ASS_LIST(res, 2, INTOBJ_INT(NMZ_VERSION_MINOR));
    ASS_LIST(res, 3, INTOBJ_INT(NMZ_VERSION_PATCH));
    return res;
}

// GVAR_FUNC in GAP 4.9 and 4.10 triggers warnings when used in C++ code; so
// we force it to the definition from GAP 4.11
#undef GVAR_FUNC
#define GVAR_FUNC(name, nargs, args)                                         \
    {                                                                        \
#name, nargs, args, (ObjFunc)Func##name, __FILE__ ":" #name          \
    }

// Table of functions to export
static StructGVarFunc GVarFuncs[] = {
    GVAR_FUNC(_NmzCone, 1, "list"),

    GVAR_FUNC(_NmzCompute, 2, "cone, props"),
    GVAR_FUNC(NmzSetVerboseDefault, 1, "value"),
    GVAR_FUNC(NmzSetVerbose, 2, "cone, value"),

    GVAR_FUNC(NmzHasConeProperty, 2, "cone, prop"),
    GVAR_FUNC(_NmzConeProperty, 2, "cone, prop"),
    GVAR_FUNC(NmzKnownConeProperties, 1, "cone"),
    GVAR_FUNC(_NmzConePropertiesNamesRecord, 0, ""),

    GVAR_FUNC(_NmzVersion, 0, ""),

    { 0 } /* Finish with an empty entry */

};

// initialise kernel data structures
static Int InitKernel(StructInitInfo * module)
{
    /* init filters and functions                                          */
    InitHdlrFuncsFromTable(GVarFuncs);

    InitCopyGVar("TheTypeNormalizCone", &TheTypeNormalizCone);

    T_NORMALIZ = RegisterPackageTNUM("NormalizCone", NormalizTypeFunc);

    InitMarkFuncBags(T_NORMALIZ, &MarkAllSubBags);
    InitFreeFuncBag(T_NORMALIZ, &NormalizFreeFunc);

    CopyObjFuncs[T_NORMALIZ] = &NormalizCopyFunc;
    CleanObjFuncs[T_NORMALIZ] = &NormalizCleanFunc;
    IsMutableObjFuncs[T_NORMALIZ] = &NormalizIsMutableObjFuncs;

    /* return success                                                      */
    return 0;
}

// initialise library data structures
static Int InitLibrary(StructInitInfo * module)
{
    /* init filters and functions */
    InitGVarFuncsFromTable(GVarFuncs);

    /* return success                                                      */
    return 0;
}

// table of init functions
static StructInitInfo module = {
    /* type        = */ MODULE_DYNAMIC,
    /* name        = */ "Normaliz",
    /* revision_c  = */ 0,
    /* revision_h  = */ 0,
    /* version     = */ 0,
    /* crc         = */ 0,
    /* initKernel  = */ InitKernel,
    /* initLibrary = */ InitLibrary,
    /* checkInit   = */ 0,
    /* preSave     = */ 0,
    /* postSave    = */ 0,
    /* postRestore = */ 0
};

extern "C" StructInitInfo * Init__Dynamic(void)
{
    return &module;
}
