//
// Semigroups package for GAP
// Copyright (C) 2016-2021 James D. Mitchell
//
// 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 3 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, see <https://www.gnu.org/licenses/>.
//

// This file contains everything in the kernel module for the Semigroups
// package that involves GAP directly, i.e. importing functions/variables from
// GAP and declaring functions for GAP etc.

#include "pkg.hpp"

#include <cstddef>        // for size_t
#include <exception>      // for exception
#include <iostream>       // for string
#include <type_traits>    // for conditional<>::type
#include <unordered_map>  // for unordered_map
#include <utility>        // for swap
#include <vector>         // for vector

#include <set>  // for set

// GAP headers
#include "gap_all.h"

// Semigroups package for GAP headers
#include "bipart.hpp"  // for Blocks, Bipartition
#include "cong.hpp"    // for init_cong
#include "conglatt.hpp"
#include "froidure-pin-fallback.hpp"  // for RUN_FROIDURE_PIN
#include "froidure-pin.hpp"           // for init_froidure_pin
#include "semigroups-debug.hpp"       // for SEMIGROUPS_ASSERT
#include "to_cpp.hpp"                 // for to_cpp
#include "to_gap.hpp"                 // for to_gap

// Gapbind14 headers
#include "gapbind14/cpp_fn.hpp"     // for overload_cast
#include "gapbind14/gapbind14.hpp"  // for class_, InstallGlobalFunction

// libsemigroups headers
#include "libsemigroups/bipart.hpp"     // for Blocks, Bipartition
#include "libsemigroups/cong-intf.hpp"  // for congruence_kind
#include "libsemigroups/digraph.hpp"    // for ActionDigraph
#include "libsemigroups/fpsemi.hpp"     // for FpSemigroup
#include "libsemigroups/freeband.hpp"   // for freeband_equal_to
#include "libsemigroups/report.hpp"     // for REPORTER, Reporter
#include "libsemigroups/sims1.hpp"      // for Sims1
#include "libsemigroups/todd-coxeter.hpp"  // for ToddCoxeter, ToddCoxeter::table_type
#include "libsemigroups/types.hpp"         // for word_type, letter_type

#include "libsemigroups/adapters.hpp"
#include "libsemigroups/uf.hpp"

using libsemigroups::Bipartition;
using libsemigroups::Blocks;

using libsemigroups::Hash;
using libsemigroups::detail::Duf;

namespace {
  void set_report(bool const val) {
    libsemigroups::REPORTER.report(val);
  }
}  // namespace

namespace gapbind14 {
  template <>
  struct IsGapBind14Type<libsemigroups::Presentation<libsemigroups::word_type>>
      : std::true_type {};

  template <>
  struct IsGapBind14Type<libsemigroups::Sims1<uint32_t>> : std::true_type {};

  template <>
  struct IsGapBind14Type<typename libsemigroups::Sims1<uint32_t>::iterator>
      : std::true_type {};

  template <>
  struct IsGapBind14Type<libsemigroups::RepOrc> : std::true_type {};
}  // namespace gapbind14

GAPBIND14_MODULE(libsemigroups) {
  ////////////////////////////////////////////////////////////////////////
  // Free functions
  ////////////////////////////////////////////////////////////////////////

  gapbind14::InstallGlobalFunction("set_report", &set_report);
  gapbind14::InstallGlobalFunction("should_report",
                                   &libsemigroups::report::should_report);
  gapbind14::InstallGlobalFunction("hardware_concurrency",
                                   &std::thread::hardware_concurrency);
  gapbind14::InstallGlobalFunction(
      "freeband_equal_to",
      gapbind14::overload_cast<libsemigroups::word_type,
                               libsemigroups::word_type>(
          &libsemigroups::freeband_equal_to<libsemigroups::word_type>));

  gapbind14::InstallGlobalFunction("LATTICE_OF_CONGRUENCES",
                                   &semigroups::LATTICE_OF_CONGRUENCES);

  ////////////////////////////////////////////////////////////////////////
  // Initialise from other cpp files
  ////////////////////////////////////////////////////////////////////////

  init_froidure_pin_base(gapbind14::module());
  init_froidure_pin_bipart(gapbind14::module());
  init_froidure_pin_bmat(gapbind14::module());
  init_froidure_pin_matrix(gapbind14::module());
  init_froidure_pin_max_plus_mat(gapbind14::module());
  init_froidure_pin_min_plus_mat(gapbind14::module());
  init_froidure_pin_pperm(gapbind14::module());
  init_froidure_pin_pbr(gapbind14::module());
  init_froidure_pin_transf(gapbind14::module());
  init_cong(gapbind14::module());

  ////////////////////////////////////////////////////////////////////////
  // FpSemigroup
  ////////////////////////////////////////////////////////////////////////

  using libsemigroups::FpSemigroup;
  using libsemigroups::word_type;

  gapbind14::class_<FpSemigroup>("FpSemigroup")
      .def(gapbind14::init<>{})
      .def("set_alphabet",
           gapbind14::overload_cast<size_t>(&FpSemigroup::set_alphabet))
      .def("add_rule",
           gapbind14::overload_cast<word_type const&, word_type const&>(
               &FpSemigroup::add_rule))
      .def("set_identity",
           gapbind14::overload_cast<libsemigroups::letter_type>(
               &FpSemigroup::set_identity));

  ////////////////////////////////////////////////////////////////////////
  // ToddCoxeter
  ////////////////////////////////////////////////////////////////////////

  using libsemigroups::congruence_kind;
  using libsemigroups::congruence::ToddCoxeter;
  using table_type = libsemigroups::congruence::ToddCoxeter::table_type;

  gapbind14::class_<ToddCoxeter>("ToddCoxeter")
      .def(gapbind14::init<congruence_kind>{})
      .def("set_number_of_generators", &ToddCoxeter::set_number_of_generators)
      .def("number_of_generators", &ToddCoxeter::number_of_generators)
      .def("prefill",
           gapbind14::overload_cast<table_type const&>(&ToddCoxeter::prefill));

  using libsemigroups::Presentation;

  gapbind14::class_<Presentation<word_type>>("Presentation")
      .def(gapbind14::init<>{}, "make")
      .def("alphabet",
           gapbind14::overload_cast<>(&Presentation<word_type>::alphabet))
      .def("set_alphabet",
           [](Presentation<word_type>& thing, word_type val) -> void {
             thing.alphabet(val);
           })
      .def("alphabet_from_rules",
           [](Presentation<word_type>& thing) -> void {
             thing.alphabet_from_rules();
           })
      .def("contains_empty_word",
           [](Presentation<word_type>& thing, bool val) -> void {
             thing.contains_empty_word(val);
           })
      .def("validate", &Presentation<word_type>::validate)
      .def("number_of_rules",
           [](Presentation<word_type> const& thing) -> size_t {
             return thing.rules.size();
           });

  gapbind14::InstallGlobalFunction(
      "presentation_add_rule",
      gapbind14::overload_cast<Presentation<word_type>&,
                               word_type const&,
                               word_type const&>(
          &libsemigroups::presentation::add_rule<word_type>));

  using libsemigroups::Sims1;

  gapbind14::class_<typename Sims1<uint32_t>::iterator>("Sims1Iterator")
      .def("increment", [](typename Sims1<uint32_t>::iterator& it) { ++it; })
      .def("deref",
           [](typename Sims1<uint32_t>::iterator const& it) { return *it; });

  gapbind14::class_<Sims1<uint32_t>>("Sims1")
      .def(gapbind14::init<congruence_kind>{}, "make")
      .def("short_rules",
           [](Sims1<uint32_t>& s, Presentation<word_type> const& p) {
             s.short_rules(p);
           })
      .def("extra",
           [](Sims1<uint32_t>& s, Presentation<word_type> const& p) {
             s.extra(p);
           })
      .def("number_of_threads",
           [](Sims1<uint32_t>& s, size_t val) { s.number_of_threads(val); })
      .def("number_of_congruences", &Sims1<uint32_t>::number_of_congruences)
      .def("cbegin", &Sims1<uint32_t>::cbegin);

  using libsemigroups::RepOrc;

  gapbind14::class_<RepOrc>("RepOrc")
      .def(gapbind14::init<>{}, "make")
      .def("short_rules",
           [](RepOrc& ro, Presentation<word_type> const& p) {
             ro.short_rules(p);
           })
      .def("number_of_threads",
           [](RepOrc& ro, size_t val) { ro.number_of_threads(val); })
      .def("max_nodes", [](RepOrc& ro, size_t val) { ro.max_nodes(val); })
      .def("min_nodes", [](RepOrc& ro, size_t val) { ro.min_nodes(val); })
      .def("target_size", [](RepOrc& ro, size_t val) { ro.target_size(val); })
      .def("digraph", &RepOrc::digraph<uint32_t>);
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////

Obj SEMIGROUPS;

Obj TheTypeTBlocksObj;

Obj TYPE_BIPART;   // function
Obj TYPES_BIPART;  // plist

UInt T_BIPART = 0;
UInt T_BLOCKS = 0;

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

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

void TBipartObjCleanFunc(Obj o) {}

void TBlocksObjCleanFunc(Obj o) {}

void TBipartObjFreeFunc(Obj o) {
  SEMIGROUPS_ASSERT(TNUM_OBJ(o) == T_BIPART);
  delete bipart_get_cpp(o);
}

void TBlocksObjFreeFunc(Obj o) {
  SEMIGROUPS_ASSERT(TNUM_OBJ(o) == T_BLOCKS);
  delete blocks_get_cpp(o);
}

Obj TBipartObjTypeFunc(Obj o) {
  return ELM_PLIST(TYPES_BIPART, bipart_get_cpp(o)->degree() + 1);
}

Obj TBlocksObjTypeFunc(Obj o) {
  return TheTypeTBlocksObj;
}

#ifdef GAP_ENABLE_SAVELOAD

void TBipartObjSaveFunc(Obj o) {
  Bipartition* b = bipart_get_cpp(o);
  SaveUInt4(b->degree());
  for (auto it = b->cbegin(); it < b->cend(); it++) {
    SaveUInt4(*it);
  }
}

void TBipartObjLoadFunc(Obj o) {
  UInt4                 deg = LoadUInt4();
  std::vector<uint32_t> blocks;
  blocks.reserve(2 * deg);

  for (size_t i = 0; i < 2 * deg; i++) {
    blocks.push_back(LoadUInt4());
  }
  ADDR_OBJ(o)[0] = reinterpret_cast<Obj>(new Bipartition(blocks));
  SEMIGROUPS_ASSERT(ADDR_OBJ(o)[1] == NULL && ADDR_OBJ(o)[2] == NULL);
}

void TBlocksObjSaveFunc(Obj o) {
  Blocks* b = blocks_get_cpp(o);
  SaveUInt4(b->degree());
  if (b->degree() != 0) {
    SaveUInt4(b->number_of_blocks());
    for (auto it = b->cbegin(); it < b->cend(); it++) {
      SaveUInt4(*it);
    }
    for (auto it = b->cbegin_lookup(); it < b->cend_lookup(); it++) {
      SaveUInt1(static_cast<UInt1>(*it));
    }
  }
}

void TBlocksObjLoadFunc(Obj o) {
  UInt4 deg = LoadUInt4();
  if (deg == 0) {
    ADDR_OBJ(o)[0] = reinterpret_cast<Obj>(new Blocks());
    return;
  }
  UInt4 nr_blocks = LoadUInt4();

  Blocks* blocks = new Blocks(deg);

  for (size_t i = 0; i < deg; i++) {
    blocks->set_block(i, LoadUInt4());
  }
  for (size_t i = 0; i < nr_blocks; i++) {
    blocks->set_is_transverse_block(i, static_cast<bool>(LoadUInt1()));
  }
#ifdef SEMIGROUPS_KERNEL_DEBUG
  libsemigroups::validate(*blocks);
#endif
  ADDR_OBJ(o)[0] = reinterpret_cast<Obj>(blocks);
}

#endif

// Filters for IS_BIPART, IS_BLOCKS

Obj IsBipartFilt;

Obj IsBipartHandler(Obj self, Obj val) {
  if (TNUM_OBJ(val) == T_BIPART) {
    return True;
  } else if (TNUM_OBJ(val) < FIRST_EXTERNAL_TNUM) {
    return False;
  } else {
    return DoFilter(self, val);
  }
}

Obj IsBlocksFilt;

Obj IsBlocksHandler(Obj self, Obj val) {
  if (TNUM_OBJ(val) == T_BLOCKS) {
    return True;
  } else if (TNUM_OBJ(val) < FIRST_EXTERNAL_TNUM) {
    return False;
  } else {
    return DoFilter(self, val);
  }
}

// Imported types and functions from the library, defined below

Obj HTValue;
Obj HTAdd;
Obj Pinfinity;
Obj Ninfinity;
Obj IsInfinity;
Obj IsNegInfinity;
Obj IsBooleanMat;
Obj BooleanMatType;
Obj IsMaxPlusMatrix;
Obj MaxPlusMatrixType;
Obj IsMinPlusMatrix;
Obj MinPlusMatrixType;
Obj IsTropicalMinPlusMatrix;
Obj TropicalMinPlusMatrixType;
Obj IsTropicalMaxPlusMatrix;
Obj TropicalMaxPlusMatrixType;
Obj IsProjectiveMaxPlusMatrix;
Obj ProjectiveMaxPlusMatrixType;
Obj IsNTPMatrix;
Obj NTPMatrixType;
Obj IntegerMatrixType;
Obj IsPBR;
Obj TYPES_PBR;
Obj TYPE_PBR;
Obj DegreeOfPBR;
Obj LARGEST_MOVED_PT_TRANS;

Obj IsSemigroup;
Obj IsMatrixObj;
Obj BaseDomain;
Obj Matrix;
Obj Integers;
Obj NrRows;

/*****************************************************************************
 *V  GVarFilts . . . . . . . . . . . . . . . . . . . list of filters to export
 */

#if defined(GAP_KERNEL_MAJOR_VERSION) && (GAP_KERNEL_MAJOR_VERSION >= 2)
typedef Obj (*GVarFilt)(Obj, Obj);
#else
typedef Obj (*GVarFilt)(/*arguments*/);
#endif

static StructGVarFilt GVarFilts[] = {
    {"IS_BIPART",
     "obj",
     &IsBipartFilt,
     (GVarFilt) IsBipartHandler,
     "pkg.cpp:IS_BIPART"},

    {"IS_BLOCKS",
     "obj",
     &IsBlocksFilt,
     (GVarFilt) IsBlocksHandler,
     "pkg.cpp:IS_BLOCKS"},

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

/*****************************************************************************/

#define GVAR_ENTRY(srcfile, name, nparam, params)                \
  {                                                              \
#name, nparam, params, (ObjFunc) name, srcfile ":Func" #name \
  }

// Table of functions to export

static StructGVarFunc GVarFuncs[] = {
    GVAR_ENTRY("froidure-pin-fallback.cpp",
               SCC_UNION_LEFT_RIGHT_CAYLEY_GRAPHS,
               2,
               "scc1, scc2"),
    GVAR_ENTRY("froidure-pin-fallback.cpp", FIND_HCLASSES, 2, "left, right"),
    GVAR_ENTRY("froidure-pin-fallback.cpp",
               RUN_FROIDURE_PIN,
               3,
               "obj, limit, report"),

    GVAR_ENTRY("bipart.cpp", BIPART_NC, 1, "list"),
    GVAR_ENTRY("bipart.cpp", BIPART_EXT_REP, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_INT_REP, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_HASH, 2, "x, data"),
    GVAR_ENTRY("bipart.cpp", BIPART_DEGREE, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_RANK, 2, "x, nothing"),
    GVAR_ENTRY("bipart.cpp", BIPART_NR_BLOCKS, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_NR_LEFT_BLOCKS, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_PERM_LEFT_QUO, 2, "x, y"),
    GVAR_ENTRY("bipart.cpp", BIPART_LEFT_PROJ, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_RIGHT_PROJ, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_STAR, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_LAMBDA_CONJ, 2, "x, y"),
    GVAR_ENTRY("bipart.cpp", BIPART_STAB_ACTION, 2, "x, p"),
    GVAR_ENTRY("bipart.cpp", BIPART_LEFT_BLOCKS, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BIPART_RIGHT_BLOCKS, 1, "x"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_NC, 1, "blocks"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_EXT_REP, 1, "blocks"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_DEGREE, 1, "blocks"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_RANK, 1, "blocks"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_NR_BLOCKS, 1, "blocks"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_HASH, 2, "blocks, data"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_PROJ, 1, "blocks"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_EQ, 2, "blocks1, blocks2"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_LT, 2, "blocks1, blocks2"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_E_TESTER, 2, "left, right"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_E_CREATOR, 2, "left, right"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_LEFT_ACT, 2, "blocks, x"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_RIGHT_ACT, 2, "blocks, x"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_INV_LEFT, 2, "blocks, x"),
    GVAR_ENTRY("bipart.cpp", BLOCKS_INV_RIGHT, 2, "blocks, x"),
    GVAR_ENTRY("bipart.cpp",
               BIPART_NR_IDEMPOTENTS,
               4,
               "o, scc, lookup, nr_threads"),

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

/******************************************************************************
 *F  InitKernel( <module> )  . . . . . . . . initialise kernel data structures
 */
static Int InitKernel(StructInitInfo* module) {
  gapbind14::init_kernel("libsemigroups");

  /* init filters and functions */
  InitHdlrFiltsFromTable(GVarFilts);
  InitHdlrFuncsFromTable(GVarFuncs);

  ImportGVarFromLibrary("SEMIGROUPS", &SEMIGROUPS);

  // T_BIPART
  T_BIPART = RegisterPackageTNUM("bipartition", TBipartObjTypeFunc);

  CopyObjFuncs[T_BIPART]      = &TBipartObjCopyFunc;
  CleanObjFuncs[T_BIPART]     = &TBipartObjCleanFunc;
  IsMutableObjFuncs[T_BIPART] = &AlwaysNo;

#ifdef GAP_ENABLE_SAVELOAD
  SaveObjFuncs[T_BIPART] = TBipartObjSaveFunc;
  LoadObjFuncs[T_BIPART] = TBipartObjLoadFunc;
#endif

  InitMarkFuncBags(T_BIPART, &MarkAllButFirstSubBags);
  InitFreeFuncBag(T_BIPART, &TBipartObjFreeFunc);

  ProdFuncs[T_BIPART][T_BIPART] = BIPART_PROD;
  EqFuncs[T_BIPART][T_BIPART]   = BIPART_EQ;
  LtFuncs[T_BIPART][T_BIPART]   = BIPART_LT;

  ImportGVarFromLibrary("TYPE_BIPART", &TYPE_BIPART);
  ImportGVarFromLibrary("TYPES_BIPART", &TYPES_BIPART);

  // T_BLOCKS
  T_BLOCKS = RegisterPackageTNUM("blocks", TBlocksObjTypeFunc);

  CopyObjFuncs[T_BLOCKS]      = &TBlocksObjCopyFunc;
  CleanObjFuncs[T_BLOCKS]     = &TBlocksObjCleanFunc;
  IsMutableObjFuncs[T_BLOCKS] = &AlwaysNo;

#ifdef GAP_ENABLE_SAVELOAD
  SaveObjFuncs[T_BLOCKS] = TBlocksObjSaveFunc;
  LoadObjFuncs[T_BLOCKS] = TBlocksObjLoadFunc;
#endif

  InitMarkFuncBags(T_BLOCKS, &MarkNoSubBags);
  InitFreeFuncBag(T_BLOCKS, &TBlocksObjFreeFunc);

  EqFuncs[T_BLOCKS][T_BLOCKS] = BLOCKS_EQ;
  LtFuncs[T_BLOCKS][T_BLOCKS] = BLOCKS_LT;

  InitCopyGVar("TheTypeTBlocksObj", &TheTypeTBlocksObj);

  // Import things from the library

  ImportGVarFromLibrary("HTValue", &HTValue);
  ImportGVarFromLibrary("HTAdd", &HTAdd);

  ImportGVarFromLibrary("infinity", &Pinfinity);
  ImportGVarFromLibrary("Ninfinity", &Ninfinity);

  ImportGVarFromLibrary("IsInfinity", &IsInfinity);
  ImportGVarFromLibrary("IsNegInfinity", &IsNegInfinity);

  ImportGVarFromLibrary("TYPES_PBR", &TYPES_PBR);
  ImportGVarFromLibrary("TYPE_PBR", &TYPE_PBR);

  ImportGVarFromLibrary("IsPBR", &IsPBR);
  ImportGVarFromLibrary("DegreeOfPBR", &DegreeOfPBR);

  ImportGVarFromLibrary("IsBooleanMat", &IsBooleanMat);
  ImportGVarFromLibrary("BooleanMatType", &BooleanMatType);

  ImportGVarFromLibrary("IsMaxPlusMatrix", &IsMaxPlusMatrix);
  ImportGVarFromLibrary("MaxPlusMatrixType", &MaxPlusMatrixType);

  ImportGVarFromLibrary("IsMinPlusMatrix", &IsMinPlusMatrix);
  ImportGVarFromLibrary("MinPlusMatrixType", &MinPlusMatrixType);

  ImportGVarFromLibrary("IsTropicalMaxPlusMatrix", &IsTropicalMaxPlusMatrix);
  ImportGVarFromLibrary("TropicalMaxPlusMatrixType",
                        &TropicalMaxPlusMatrixType);

  ImportGVarFromLibrary("IsTropicalMinPlusMatrix", &IsTropicalMinPlusMatrix);
  ImportGVarFromLibrary("TropicalMinPlusMatrixType",
                        &TropicalMinPlusMatrixType);

  ImportGVarFromLibrary("IsProjectiveMaxPlusMatrix",
                        &IsProjectiveMaxPlusMatrix);
  ImportGVarFromLibrary("ProjectiveMaxPlusMatrixType",
                        &ProjectiveMaxPlusMatrixType);

  ImportGVarFromLibrary("IsNTPMatrix", &IsNTPMatrix);
  ImportGVarFromLibrary("NTPMatrixType", &NTPMatrixType);

  ImportGVarFromLibrary("IntegerMatrixType", &IntegerMatrixType);

  ImportGVarFromLibrary("LARGEST_MOVED_PT_TRANS", &LARGEST_MOVED_PT_TRANS);

  ImportGVarFromLibrary("IsSemigroup", &IsSemigroup);
  ImportGVarFromLibrary("IsMatrixObj", &IsMatrixObj);
  ImportGVarFromLibrary("BaseDomain", &BaseDomain);
  ImportGVarFromLibrary("Integers", &Integers);
  ImportGVarFromLibrary("NrRows", &NrRows);
  ImportGVarFromLibrary("Matrix", &Matrix);

  return 0;
}

static Int PostRestore(StructInitInfo* module) {
  set_report(false);
  return 0;
}

static Int InitLibrary(StructInitInfo* module) {
  gapbind14::init_library("libsemigroups");
  InitGVarFiltsFromTable(GVarFilts);
  InitGVarFuncsFromTable(GVarFuncs);
#ifdef LIBSEMIGROUPS_HPCOMBI_ENABLED
  ExportAsConstantGVar(LIBSEMIGROUPS_HPCOMBI_ENABLED);
#endif
  return PostRestore(module);
}

/******************************************************************************
 *F  InitInfopl()  . . . . . . . . . . . . . . . . . table of init functions
 */
static StructInitInfo module = {/* type        = */ MODULE_DYNAMIC,
                                /* name        = */ "semigroups",
                                /* revision_c  = */ 0,
                                /* revision_h  = */ 0,
                                /* version     = */ 0,
                                /* crc         = */ 0,
                                /* initKernel  = */ InitKernel,
                                /* initLibrary = */ InitLibrary,
                                /* checkInit   = */ 0,
                                /* preSave     = */ 0,
                                /* postSave    = */ 0,
                                /* postRestore = */ PostRestore};

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