/****************************************
*  Computer Algebra System SINGULAR     *
****************************************/
/*
* ABSTRACT: numbers modulo n
*/
#include "misc/auxiliary.h"

#include "misc/mylimits.h"
#include "misc/prime.h" // IsPrime
#include "reporter/reporter.h"

#include "coeffs/si_gmp.h"
#include "coeffs/coeffs.h"
#include "coeffs/modulop.h"
#include "coeffs/rintegers.h"
#include "coeffs/numbers.h"

#include "coeffs/mpr_complex.h"

#include "coeffs/longrat.h"
#include "coeffs/rmodulon.h"

#include <string.h>

void nrnWrite (number a, const coeffs);
#ifdef LDEBUG
static BOOLEAN nrnDBTest      (number a, const char *f, const int l, const coeffs r);
#endif

EXTERN_VAR omBin gmp_nrz_bin;

coeffs nrnInitCfByName(char *s,n_coeffType)
{
  const char start[]="ZZ/bigint(";
  const int start_len=strlen(start);
  if (strncmp(s,start,start_len)==0)
  {
    s+=start_len;
    mpz_t z;
    mpz_init(z);
    s=nEatLong(s,z);
    ZnmInfo info;
    info.base=z;
    info.exp= 1;
    while ((*s!='\0') && (*s!=')')) s++;
    // expect ")" or ")^exp"
    if (*s=='\0') { mpz_clear(z); return NULL; }
    if (((*s)==')') && (*(s+1)=='^'))
    {
      s=s+2;
      int i;
      s=nEati(s,&i,0);
      info.exp=(unsigned long)i;
      return nInitChar(n_Znm,(void*) &info);
    }
    else
      return nInitChar(n_Zn,(void*) &info);
  }
  else return NULL;
}

STATIC_VAR char* nrnCoeffName_buff=NULL;
static char* nrnCoeffName(const coeffs r)
{
  if(nrnCoeffName_buff!=NULL) omFree(nrnCoeffName_buff);
  size_t l = (size_t)mpz_sizeinbase(r->modBase, 10) + 2;
  char* s = (char*) omAlloc(l);
  l+=24;
  nrnCoeffName_buff=(char*)omAlloc(l);
  s= mpz_get_str (s, 10, r->modBase);
  int ll=0;
  if (nCoeff_is_Zn(r))
  {
    if (strlen(s)<10)
      ll=snprintf(nrnCoeffName_buff,l,"ZZ/(%s)",s);
    else
      ll=snprintf(nrnCoeffName_buff,l,"ZZ/bigint(%s)",s);
  }
  else if (nCoeff_is_Ring_PtoM(r))
    ll=snprintf(nrnCoeffName_buff,l,"ZZ/(bigint(%s)^%lu)",s,r->modExponent);
  assume(ll<(int)l); // otherwise nrnCoeffName_buff too small
  omFreeSize((ADDRESS)s, l-22);
  return nrnCoeffName_buff;
}

static BOOLEAN nrnCoeffIsEqual(const coeffs r, n_coeffType n, void * parameter)
{
  /* test, if r is an instance of nInitCoeffs(n,parameter) */
  ZnmInfo *info=(ZnmInfo*)parameter;
  return (n==r->type) && (r->modExponent==info->exp)
  && (mpz_cmp(r->modBase,info->base)==0);
}

static void nrnKillChar(coeffs r)
{
  mpz_clear(r->modNumber);
  mpz_clear(r->modBase);
  omFreeBin((void *) r->modBase, gmp_nrz_bin);
  omFreeBin((void *) r->modNumber, gmp_nrz_bin);
}

static coeffs nrnQuot1(number c, const coeffs r)
{
    coeffs rr;
    long ch = r->cfInt(c, r);
    mpz_t a,b;
    mpz_init_set(a, r->modNumber);
    mpz_init_set_ui(b, ch);
    mpz_t gcd;
    mpz_init(gcd);
    mpz_gcd(gcd, a,b);
    if(mpz_cmp_ui(gcd, 1) == 0)
    {
      WerrorS("constant in q-ideal is coprime to modulus in ground ring");
      WerrorS("Unable to create qring!");
      return NULL;
    }
    if(r->modExponent == 1)
    {
      ZnmInfo info;
      info.base = gcd;
      info.exp = (unsigned long) 1;
      rr = nInitChar(n_Zn, (void*)&info);
    }
    else
    {
      ZnmInfo info;
      info.base = r->modBase;
      int kNew = 1;
      mpz_t baseTokNew;
      mpz_init(baseTokNew);
      mpz_set(baseTokNew, r->modBase);
      while(mpz_cmp(gcd, baseTokNew) > 0)
      {
        kNew++;
        mpz_mul(baseTokNew, baseTokNew, r->modBase);
      }
      //printf("\nkNew = %i\n",kNew);
      info.exp = kNew;
      mpz_clear(baseTokNew);
      rr = nInitChar(n_Znm, (void*)&info);
    }
    mpz_clear(gcd);
    return(rr);
}

static number nrnCopy(number a, const coeffs)
{
  mpz_ptr erg = (mpz_ptr) omAllocBin(gmp_nrz_bin);
  mpz_init_set(erg, (mpz_ptr) a);
  return (number) erg;
}

/*
 * create a number from int
 */
static number nrnInit(long i, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr) omAllocBin(gmp_nrz_bin);
  mpz_init_set_si(erg, i);
  mpz_mod(erg, erg, r->modNumber);
  return (number) erg;
}

/*
 * convert a number to int
 */
static long nrnInt(number &n, const coeffs)
{
  return mpz_get_si((mpz_ptr) n);
}

#if SI_INTEGER_VARIANT==2
#define nrnDelete nrzDelete
#define nrnSize   nrzSize
#else
static void nrnDelete(number *a, const coeffs)
{
  if (*a != NULL)
  {
    mpz_clear((mpz_ptr) *a);
    omFreeBin((void *) *a, gmp_nrz_bin);
    *a = NULL;
  }
}
static int nrnSize(number a, const coeffs)
{
  return mpz_size1((mpz_ptr)a);
}
#endif
/*
 * Multiply two numbers
 */
static number nrnMult(number a, number b, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_mul(erg, (mpz_ptr)a, (mpz_ptr) b);
  mpz_mod(erg, erg, r->modNumber);
  return (number) erg;
}

static void nrnInpMult(number &a, number b, const coeffs r)
{
  mpz_mul((mpz_ptr)a, (mpz_ptr)a, (mpz_ptr) b);
  mpz_mod((mpz_ptr)a, (mpz_ptr)a, r->modNumber);
}

static void nrnPower(number a, int i, number * result, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_powm_ui(erg, (mpz_ptr)a, i, r->modNumber);
  *result = (number) erg;
}

static number nrnAdd(number a, number b, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_add(erg, (mpz_ptr)a, (mpz_ptr) b);
  mpz_mod(erg, erg, r->modNumber);
  return (number) erg;
}

static void nrnInpAdd(number &a, number b, const coeffs r)
{
  mpz_add((mpz_ptr)a, (mpz_ptr)a, (mpz_ptr) b);
  mpz_mod((mpz_ptr)a, (mpz_ptr)a, r->modNumber);
}

static number nrnSub(number a, number b, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_sub(erg, (mpz_ptr)a, (mpz_ptr) b);
  mpz_mod(erg, erg, r->modNumber);
  return (number) erg;
}

static BOOLEAN nrnIsZero(number a, const coeffs)
{
  return 0 == mpz_sgn1((mpz_ptr)a);
}

static number nrnNeg(number c, const coeffs r)
{
  if( !nrnIsZero(c, r) )
    // Attention: This method operates in-place.
    mpz_sub((mpz_ptr)c, r->modNumber, (mpz_ptr)c);
  return c;
}

static number nrnInvers(number c, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  if (nrnIsZero(c,r))
  {
    WerrorS(nDivBy0);
  }
  else
  {
    mpz_invert(erg, (mpz_ptr)c, r->modNumber);
  }
  return (number) erg;
}

/*
 * Give the largest k, such that a = x * k, b = y * k has
 * a solution.
 * a may be NULL, b not
 */
static number nrnGcd(number a, number b, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init_set(erg, r->modNumber);
  if (a != NULL) mpz_gcd(erg, erg, (mpz_ptr)a);
  mpz_gcd(erg, erg, (mpz_ptr)b);
  if(mpz_cmp(erg,r->modNumber)==0)
  {
    mpz_clear(erg);
    omFreeBin((ADDRESS)erg,gmp_nrz_bin);
    return nrnInit(0,r);
  }
  return (number)erg;
}

/*
 * Give the smallest k, such that a * x = k = b * y has a solution
 * TODO: lcm(gcd,gcd) better than gcd(lcm) ?
 */
static number nrnLcm(number a, number b, const coeffs r)
{
  number erg = nrnGcd(NULL, a, r);
  number tmp = nrnGcd(NULL, b, r);
  mpz_lcm((mpz_ptr)erg, (mpz_ptr)erg, (mpz_ptr)tmp);
  nrnDelete(&tmp, r);
  return (number)erg;
}

/* Not needed any more, but may have room for improvement
   number nrnGcd3(number a,number b, number c,ring r)
{
  mpz_ptr erg = (mpz_ptr) omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  if (a == NULL) a = (number)r->modNumber;
  if (b == NULL) b = (number)r->modNumber;
  if (c == NULL) c = (number)r->modNumber;
  mpz_gcd(erg, (mpz_ptr)a, (mpz_ptr)b);
  mpz_gcd(erg, erg, (mpz_ptr)c);
  mpz_gcd(erg, erg, r->modNumber);
  return (number)erg;
}
*/

/*
 * Give the largest k, such that a = x * k, b = y * k has
 * a solution and r, s, s.t. k = s*a + t*b
 * CF: careful: ExtGcd is wrong as implemented (or at least may not
 * give you what you want:
 * ExtGcd(5, 10 modulo 12):
 * the gcdext will return 5 = 1*5 + 0*10
 * however, mod 12, the gcd should be 1
 */
static number nrnExtGcd(number a, number b, number *s, number *t, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr bs  = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr bt  = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_init(bs);
  mpz_init(bt);
  mpz_gcdext(erg, bs, bt, (mpz_ptr)a, (mpz_ptr)b);
  mpz_mod(bs, bs, r->modNumber);
  mpz_mod(bt, bt, r->modNumber);
  *s = (number)bs;
  *t = (number)bt;
  return (number)erg;
}

static BOOLEAN nrnIsOne(number a, const coeffs)
{
  return 0 == mpz_cmp_si((mpz_ptr)a, 1);
}

static BOOLEAN nrnEqual(number a, number b, const coeffs)
{
  return 0 == mpz_cmp((mpz_ptr)a, (mpz_ptr)b);
}

static number nrnGetUnit(number k, const coeffs r)
{
  if (mpz_divisible_p(r->modNumber, (mpz_ptr)k)) return nrnInit(1,r);

  mpz_ptr unit = (mpz_ptr)nrnGcd(NULL, k, r);
  mpz_tdiv_q(unit, (mpz_ptr)k, unit);
  mpz_ptr gcd = (mpz_ptr)nrnGcd(NULL, (number)unit, r);
  if (!nrnIsOne((number)gcd,r))
  {
    mpz_ptr ctmp;
    // tmp := unit^2
    mpz_ptr tmp = (mpz_ptr) nrnMult((number) unit,(number) unit,r);
    // gcd_new := gcd(tmp, 0)
    mpz_ptr gcd_new = (mpz_ptr) nrnGcd(NULL, (number) tmp, r);
    while (!nrnEqual((number) gcd_new,(number) gcd,r))
    {
      // gcd := gcd_new
      ctmp = gcd;
      gcd = gcd_new;
      gcd_new = ctmp;
      // tmp := tmp * unit
      mpz_mul(tmp, tmp, unit);
      mpz_mod(tmp, tmp, r->modNumber);
      // gcd_new := gcd(tmp, 0)
      mpz_gcd(gcd_new, tmp, r->modNumber);
    }
    // unit := unit + modNumber / gcd_new
    mpz_tdiv_q(tmp, r->modNumber, gcd_new);
    mpz_add(unit, unit, tmp);
    mpz_mod(unit, unit, r->modNumber);
    nrnDelete((number*) &gcd_new, r);
    nrnDelete((number*) &tmp, r);
  }
  nrnDelete((number*) &gcd, r);
  return (number)unit;
}

/* XExtGcd  returns a unimodular matrix ((s,t)(u,v)) sth.
 * (a,b)^t ((st)(uv)) = (g,0)^t
 * Beware, the ExtGcd will not necessaairly do this.
 * Problem: if g = as+bt then (in Z/nZ) it follows NOT that
 *             1 = (a/g)s + (b/g) t
 * due to the zero divisors.
 */

//#define CF_DEB;
static number nrnXExtGcd(number a, number b, number *s, number *t, number *u, number *v, const coeffs r)
{
  number xx;
#ifdef CF_DEB
  StringSetS("XExtGcd of ");
  nrnWrite(a, r);
  StringAppendS("\t");
  nrnWrite(b, r);
  StringAppendS(" modulo ");
  nrnWrite(xx = (number)r->modNumber, r);
  Print("%s\n", StringEndS());
#endif

  mpz_ptr one = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr bs  = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr bt  = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr bu  = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr bv  = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_init(one);
  mpz_init_set(bs, (mpz_ptr) a);
  mpz_init_set(bt, (mpz_ptr) b);
  mpz_init(bu);
  mpz_init(bv);
  mpz_gcd(erg, bs, bt);

#ifdef CF_DEB
  StringSetS("1st gcd:");
  nrnWrite(xx= (number)erg, r);
#endif

  mpz_gcd(erg, erg, r->modNumber);

  mpz_div(bs, bs, erg);
  mpz_div(bt, bt, erg);

#ifdef CF_DEB
  Print("%s\n", StringEndS());
  StringSetS("xgcd: ");
#endif

  mpz_gcdext(one, bu, bv, bs, bt);
  number ui = nrnGetUnit(xx = (number) one, r);
#ifdef CF_DEB
  n_Write(xx, r);
  StringAppendS("\t");
  n_Write(ui, r);
  Print("%s\n", StringEndS());
#endif
  nrnDelete(&xx, r);
  if (!nrnIsOne(ui, r))
  {
#ifdef CF_DEB
    PrintS("Scaling\n");
#endif
    number uii = nrnInvers(ui, r);
    nrnDelete(&ui, r);
    ui = uii;
    mpz_ptr uu = (mpz_ptr)omAllocBin(gmp_nrz_bin);
    mpz_init_set(uu, (mpz_ptr)ui);
    mpz_mul(bu, bu, uu);
    mpz_mul(bv, bv, uu);
    mpz_clear(uu);
    omFreeBin(uu, gmp_nrz_bin);
  }
  nrnDelete(&ui, r);
#ifdef CF_DEB
  StringSetS("xgcd");
  nrnWrite(xx= (number)bs, r);
  StringAppendS("*");
  nrnWrite(xx= (number)bu, r);
  StringAppendS(" + ");
  nrnWrite(xx= (number)bt, r);
  StringAppendS("*");
  nrnWrite(xx= (number)bv, r);
  Print("%s\n", StringEndS());
#endif

  mpz_mod(bs, bs, r->modNumber);
  mpz_mod(bt, bt, r->modNumber);
  mpz_mod(bu, bu, r->modNumber);
  mpz_mod(bv, bv, r->modNumber);
  *s = (number)bu;
  *t = (number)bv;
  *u = (number)bt;
  *u = nrnNeg(*u, r);
  *v = (number)bs;
  return (number)erg;
}

static BOOLEAN nrnIsMOne(number a, const coeffs r)
{
  if((r->ch==2) && (nrnIsOne(a,r))) return FALSE;
  mpz_t t; mpz_init_set(t, (mpz_ptr)a);
  mpz_add_ui(t, t, 1);
  bool erg = (0 == mpz_cmp(t, r->modNumber));
  mpz_clear(t);
  return erg;
}

static BOOLEAN nrnGreater(number a, number b, const coeffs)
{
  return 0 < mpz_cmp((mpz_ptr)a, (mpz_ptr)b);
}

static BOOLEAN nrnGreaterZero(number k, const coeffs cf)
{
  if (cf->is_field)
  {
    if (mpz_cmp_ui(cf->modBase,2)==0)
    {
      return TRUE;
    }
    #if 0
    mpz_t ch2; mpz_init_set(ch2, cf->modBase);
    mpz_sub_ui(ch2,ch2,1); //cf->modBase is odd
    mpz_divexact_ui(ch2,ch2,2);
    if (mpz_cmp(ch2,(mpz_ptr)k)<0)
    {
      mpz_clear(ch2);
      return FALSE;
    }
    mpz_clear(ch2);
    #endif
  }
  #if 0
  else
  {
    mpz_t ch2; mpz_init_set(ch2, cf->modBase);
    mpz_tdiv_q_ui(ch2,ch2,2);
    if (mpz_cmp(ch2,(mpz_ptr)k)<0)
    {
      mpz_clear(ch2);
      return FALSE;
    }
    mpz_clear(ch2);
  }
  #endif
  return 0 < mpz_sgn1((mpz_ptr)k);
}

static BOOLEAN nrnIsUnit(number a, const coeffs r)
{
  number tmp = nrnGcd(a, (number)r->modNumber, r);
  bool res = nrnIsOne(tmp, r);
  nrnDelete(&tmp, r);
  return res;
}

static number nrnAnn(number k, const coeffs r)
{
  mpz_ptr tmp = (mpz_ptr) omAllocBin(gmp_nrz_bin);
  mpz_init(tmp);
  mpz_gcd(tmp, (mpz_ptr) k, r->modNumber);
  if (mpz_cmp_si(tmp, 1)==0)
  {
    mpz_set_ui(tmp, 0);
    return (number) tmp;
  }
  mpz_divexact(tmp, r->modNumber, tmp);
  return (number) tmp;
}

static BOOLEAN nrnDivBy(number a, number b, const coeffs r)
{
  /* b divides a iff b/gcd(a, b) is a unit in the given ring: */
  number n = nrnGcd(a, b, r);
  mpz_tdiv_q((mpz_ptr)n, (mpz_ptr)b, (mpz_ptr)n);
  bool result = nrnIsUnit(n, r);
  nrnDelete(&n, NULL);
  return result;
}

static int nrnDivComp(number a, number b, const coeffs r)
{
  if (nrnEqual(a, b,r)) return 2;
  if (mpz_divisible_p((mpz_ptr) a, (mpz_ptr) b)) return -1;
  if (mpz_divisible_p((mpz_ptr) b, (mpz_ptr) a)) return 1;
  return 0;
}

static number nrnDiv(number a, number b, const coeffs r)
{
  if (nrnIsZero(b,r))
  {
    WerrorS(nDivBy0);
    return nrnInit(0,r);
  }
  else if (r->is_field)
  {
    number inv=nrnInvers(b,r);
    number erg=nrnMult(a,inv,r);
    nrnDelete(&inv,r);
    return erg;
  }
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  if (mpz_divisible_p((mpz_ptr)a, (mpz_ptr)b))
  {
    mpz_divexact(erg, (mpz_ptr)a, (mpz_ptr)b);
    return (number)erg;
  }
  else
  {
    mpz_ptr gcd = (mpz_ptr)nrnGcd(a, b, r);
    mpz_divexact(erg, (mpz_ptr)b, gcd);
    if (!nrnIsUnit((number)erg, r))
    {
      WerrorS("Division not possible, even by cancelling zero divisors.");
      nrnDelete((number*) &gcd, r);
      nrnDelete((number*) &erg, r);
      return (number)NULL;
    }
    // a / gcd(a,b) * [b / gcd (a,b)]^(-1)
    mpz_ptr tmp = (mpz_ptr)nrnInvers((number) erg,r);
    mpz_divexact(erg, (mpz_ptr)a, gcd);
    mpz_mul(erg, erg, tmp);
    nrnDelete((number*) &gcd, r);
    nrnDelete((number*) &tmp, r);
    mpz_mod(erg, erg, r->modNumber);
    return (number)erg;
  }
}

static number nrnMod(number a, number b, const coeffs r)
{
  /*
    We need to return the number rr which is uniquely determined by the
    following two properties:
      (1) 0 <= rr < |b| (with respect to '<' and '<=' performed in Z x Z)
      (2) There exists some k in the integers Z such that a = k * b + rr.
    Consider g := gcd(n, |b|). Note that then |b|/g is a unit in Z/n.
    Now, there are three cases:
      (a) g = 1
          Then |b| is a unit in Z/n, i.e. |b| (and also b) divides a.
          Thus rr = 0.
      (b) g <> 1 and g divides a
          Then a = (a/g) * (|b|/g)^(-1) * b (up to sign), i.e. again rr = 0.
      (c) g <> 1 and g does not divide a
          Then denote the division with remainder of a by g as this:
          a = s * g + t. Then t = a - s * g = a - s * (|b|/g)^(-1) * |b|
          fulfills (1) and (2), i.e. rr := t is the correct result. Hence
          in this third case, rr is the remainder of division of a by g in Z.
     Remark: according to mpz_mod: a,b are always non-negative
  */
  mpz_ptr g = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr rr = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(g);
  mpz_init_set_ui(rr, 0);
  mpz_gcd(g, (mpz_ptr)r->modNumber, (mpz_ptr)b); // g is now as above
  if (mpz_cmp_si(g, 1L) != 0) mpz_mod(rr, (mpz_ptr)a, g); // the case g <> 1
  mpz_clear(g);
  omFreeBin(g, gmp_nrz_bin);
  return (number)rr;
}

/* CF: note that Z/nZ has (at least) two distinct euclidean structures
 * 1st phi(a) := (a mod n) which is just the structure directly
 *     inherited from Z
 * 2nd phi(a) := gcd(a, n)
 * The 1st version is probably faster as everything just comes from Z,
 * but the 2nd version behaves nicely wrt. to quotient operations
 * and HNF and such. In agreement with nrnMod we imlement the 2nd here
 *
 * For quotrem note that if b exactly divides a, then
 *   min(v_p(a), v_p(n))  >= min(v_p(b), v_p(n))
 * so if we divide  a and b by g:= gcd(a,b,n), then   b becomes a
 * unit mod n/g.
 * Thus we 1st compute the remainder (similar to nrnMod) and then
 * the exact quotient.
 */
static number nrnQuotRem(number a, number b, number  * rem, const coeffs r)
{
  mpz_t g, aa, bb;
  mpz_ptr qq = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_ptr rr = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(qq);
  mpz_init(rr);
  mpz_init(g);
  mpz_init_set(aa, (mpz_ptr)a);
  mpz_init_set(bb, (mpz_ptr)b);

  mpz_gcd(g, bb, r->modNumber);
  mpz_mod(rr, aa, g);
  mpz_sub(aa, aa, rr);
  mpz_gcd(g, aa, g);
  mpz_div(aa, aa, g);
  mpz_div(bb, bb, g);
  mpz_div(g, r->modNumber, g);
  mpz_invert(g, bb, g);
  mpz_mul(qq, aa, g);
  if (rem)
    *rem = (number)rr;
  else {
    mpz_clear(rr);
    omFreeBin(rr, gmp_nrz_bin);
  }
  mpz_clear(g);
  mpz_clear(aa);
  mpz_clear(bb);
  return (number) qq;
}

/*
 * Helper function for computing the module
 */

STATIC_VAR mpz_ptr nrnMapCoef = NULL;

static number nrnMapModN(number from, const coeffs /*src*/, const coeffs dst)
{
  return nrnMult(from, (number) nrnMapCoef, dst);
}

static number nrnMap2toM(number from, const coeffs /*src*/, const coeffs dst)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_mul_ui(erg, nrnMapCoef, (unsigned long)from);
  mpz_mod(erg, erg, dst->modNumber);
  return (number)erg;
}

static number nrnMapZp(number from, const coeffs /*src*/, const coeffs dst)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  // TODO: use npInt(...)
  mpz_mul_si(erg, nrnMapCoef, (unsigned long)from);
  mpz_mod(erg, erg, dst->modNumber);
  return (number)erg;
}

number nrnMapGMP(number from, const coeffs /*src*/, const coeffs dst)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init(erg);
  mpz_mod(erg, (mpz_ptr)from, dst->modNumber);
  return (number)erg;
}

static number nrnMapQ(number from, const coeffs src, const coeffs dst)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  nlMPZ(erg, from, src);
  mpz_mod(erg, erg, dst->modNumber);
  return (number)erg;
}

#if SI_INTEGER_VARIANT==3
static number nrnMapZ(number from, const coeffs /*src*/, const coeffs dst)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  if (n_Z_IS_SMALL(from))
    mpz_init_set_si(erg, SR_TO_INT(from));
  else
    mpz_init_set(erg, (mpz_ptr) from);
  mpz_mod(erg, erg, dst->modNumber);
  return (number)erg;
}
#elif SI_INTEGER_VARIANT==2

static number nrnMapZ(number from, const coeffs src, const coeffs dst)
{
  if (SR_HDL(from) & SR_INT)
  {
    long f_i=SR_TO_INT(from);
    return nrnInit(f_i,dst);
  }
  return nrnMapGMP(from,src,dst);
}
#elif SI_INTEGER_VARIANT==1
static number nrnMapZ(number from, const coeffs src, const coeffs dst)
{
  return nrnMapQ(from,src,dst);
}
#endif
void nrnWrite (number a, const coeffs /*cf*/)
{
  char *s,*z;
  if (a==NULL)
  {
    StringAppendS("o");
  }
  else
  {
    int l=mpz_sizeinbase((mpz_ptr) a, 10) + 2;
    s=(char*)omAlloc(l);
    z=mpz_get_str(s,10,(mpz_ptr) a);
    StringAppendS(z);
    omFreeSize((ADDRESS)s,l);
  }
}

nMapFunc nrnSetMap(const coeffs src, const coeffs dst)
{
  /* dst = nrn */
  if ((src->rep==n_rep_gmp) && nCoeff_is_Z(src))
  {
    return nrnMapZ;
  }
  if ((src->rep==n_rep_gap_gmp) /*&& nCoeff_is_Z(src)*/)
  {
    return nrnMapZ;
  }
  if (src->rep==n_rep_gap_rat) /*&& nCoeff_is_Q(src)) or Z*/
  {
    return nrnMapQ;
  }
  // Some type of Z/n ring / field
  if (nCoeff_is_Zn(src) || nCoeff_is_Ring_PtoM(src) ||
      nCoeff_is_Ring_2toM(src) || nCoeff_is_Zp(src))
  {
    if (   (!nCoeff_is_Zp(src))
        && (mpz_cmp(src->modBase, dst->modBase) == 0)
        && (src->modExponent == dst->modExponent)) return ndCopyMap;
    else
    {
      mpz_ptr nrnMapModul = (mpz_ptr) omAllocBin(gmp_nrz_bin);
      // Computing the n of Z/n
      if (nCoeff_is_Zp(src))
      {
        mpz_init_set_si(nrnMapModul, src->ch);
      }
      else
      {
        mpz_init(nrnMapModul);
        mpz_set(nrnMapModul, src->modNumber);
      }
      // nrnMapCoef = 1 in dst       if dst is a subring of src
      // nrnMapCoef = 0 in dst / src if src is a subring of dst
      if (nrnMapCoef == NULL)
      {
        nrnMapCoef = (mpz_ptr) omAllocBin(gmp_nrz_bin);
        mpz_init(nrnMapCoef);
      }
      if (mpz_divisible_p(nrnMapModul, dst->modNumber))
      {
        mpz_set_ui(nrnMapCoef, 1);
      }
      else
      if (mpz_divisible_p(dst->modNumber,nrnMapModul))
      {
        mpz_divexact(nrnMapCoef, dst->modNumber, nrnMapModul);
        mpz_ptr tmp = dst->modNumber;
        dst->modNumber = nrnMapModul;
        if (!nrnIsUnit((number) nrnMapCoef,dst))
        {
          dst->modNumber = tmp;
          nrnDelete((number*) &nrnMapModul, dst);
          return NULL;
        }
        mpz_ptr inv = (mpz_ptr) nrnInvers((number) nrnMapCoef,dst);
        dst->modNumber = tmp;
        mpz_mul(nrnMapCoef, nrnMapCoef, inv);
        mpz_mod(nrnMapCoef, nrnMapCoef, dst->modNumber);
        nrnDelete((number*) &inv, dst);
      }
      else
      {
        nrnDelete((number*) &nrnMapModul, dst);
        return NULL;
      }
      nrnDelete((number*) &nrnMapModul, dst);
      if (nCoeff_is_Ring_2toM(src))
        return nrnMap2toM;
      else if (nCoeff_is_Zp(src))
        return nrnMapZp;
      else
        return nrnMapModN;
    }
  }
  return NULL;      // default
}

static number nrnInitMPZ(mpz_t m, const coeffs r)
{
  mpz_ptr erg = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init_set(erg,m);
  mpz_mod(erg, erg, r->modNumber);
  return (number) erg;
}

static void nrnMPZ(mpz_t m, number &n, const coeffs)
{
  mpz_init_set(m, (mpz_ptr)n);
}

/*
 * set the exponent (allocate and init tables) (TODO)
 */

static void nrnSetExp(unsigned long m, coeffs r)
{
  /* clean up former stuff */
  if (r->modNumber != NULL) mpz_clear(r->modNumber);

  r->modExponent= m;
  r->modNumber = (mpz_ptr)omAllocBin(gmp_nrz_bin);
  mpz_init_set (r->modNumber, r->modBase);
  mpz_pow_ui (r->modNumber, r->modNumber, m);
}

/* We expect this ring to be Z/n^m for some m > 0 and for some n > 2 which is not a prime. */
static void nrnInitExp(unsigned long m, coeffs r)
{
  nrnSetExp(m, r);
  assume (r->modNumber != NULL);
//CF: in general, the modulus is computed somewhere. I don't want to
//  check it's size before I construct the best ring.
//  if (mpz_cmp_ui(r->modNumber,2) <= 0)
//    WarnS("nrnInitExp failed (m in Z/m too small)");
}

#ifdef LDEBUG
static BOOLEAN nrnDBTest (number a, const char *f, const int l, const coeffs r)
{
  if ( (mpz_sgn1((mpz_ptr) a) < 0) || (mpz_cmp((mpz_ptr) a, r->modNumber) > 0) )
  {
    Warn("mod-n: out of range at %s:%d\n",f,l);
    return FALSE;
  }
  return TRUE;
}
#endif

/*2
* extracts a long integer from s, returns the rest    (COPY FROM longrat0.cc)
*/
static const char * nlCPEatLongC(char *s, mpz_ptr i)
{
  const char * start=s;
  if (!(*s >= '0' && *s <= '9'))
  {
    mpz_init_set_ui(i, 1);
    return s;
  }
  mpz_init(i);
  while (*s >= '0' && *s <= '9') s++;
  if (*s=='\0')
  {
    mpz_set_str(i,start,10);
  }
  else
  {
    char c=*s;
    *s='\0';
    mpz_set_str(i,start,10);
    *s=c;
  }
  return s;
}

static const char * nrnRead (const char *s, number *a, const coeffs r)
{
  mpz_ptr z = (mpz_ptr) omAllocBin(gmp_nrz_bin);
  {
    s = nlCPEatLongC((char *)s, z);
  }
  mpz_mod(z, z, r->modNumber);
  if ((*s)=='/')
  {
    mpz_ptr n = (mpz_ptr) omAllocBin(gmp_nrz_bin);
    s++;
    s=nlCPEatLongC((char*)s,n);
    if (!nrnIsOne((number)n,r))
    {
      *a=nrnDiv((number)z,(number)n,r);
      mpz_clear(z);
      omFreeBin((void *)z, gmp_nrz_bin);
      mpz_clear(n);
      omFreeBin((void *)n, gmp_nrz_bin);
    }
  }
  else
    *a = (number) z;
  return s;
}

static number nrnConvFactoryNSingN( const CanonicalForm n, const coeffs r)
{
  return nrnInit(n.intval(),r);
}

static CanonicalForm nrnConvSingNFactoryN( number n, BOOLEAN setChar, const coeffs r )
{
  if (setChar) setCharacteristic( r->ch );
   return CanonicalForm(nrnInt( n,r ));
}

/* for initializing function pointers */
BOOLEAN nrnInitChar (coeffs r, void* p)
{
  assume( (getCoeffType(r) == n_Zn) || (getCoeffType (r) == n_Znm) );
  ZnmInfo * info= (ZnmInfo *) p;
  r->modBase= (mpz_ptr)nrnCopy((number)info->base, r); //this circumvents the problem
  //in bigintmat.cc where we cannot create a "legal" nrn that can be freed.
  //If we take a copy, we can do whatever we want.

  nrnInitExp (info->exp, r);

  /* next computation may yield wrong characteristic as r->modNumber
     is a GMP number */
  r->ch = mpz_get_ui(r->modNumber);

  r->is_field=FALSE;
  r->is_domain=FALSE;
  r->rep=n_rep_gmp;

  r->cfInit        = nrnInit;
  r->cfDelete      = nrnDelete;
  r->cfCopy        = nrnCopy;
  r->cfSize        = nrnSize;
  r->cfInt         = nrnInt;
  r->cfAdd         = nrnAdd;
  r->cfInpAdd      = nrnInpAdd;
  r->cfSub         = nrnSub;
  r->cfMult        = nrnMult;
  r->cfInpMult     = nrnInpMult;
  r->cfDiv         = nrnDiv;
  r->cfAnn         = nrnAnn;
  r->cfIntMod      = nrnMod;
  r->cfExactDiv    = nrnDiv;
  r->cfInpNeg      = nrnNeg;
  r->cfInvers      = nrnInvers;
  r->cfDivBy       = nrnDivBy;
  r->cfDivComp     = nrnDivComp;
  r->cfGreater     = nrnGreater;
  r->cfEqual       = nrnEqual;
  r->cfIsZero      = nrnIsZero;
  r->cfIsOne       = nrnIsOne;
  r->cfIsMOne      = nrnIsMOne;
  r->cfGreaterZero = nrnGreaterZero;
  r->cfWriteLong   = nrnWrite;
  r->cfRead        = nrnRead;
  r->cfPower       = nrnPower;
  r->cfSetMap      = nrnSetMap;
  //r->cfNormalize   = ndNormalize;
  r->cfLcm         = nrnLcm;
  r->cfGcd         = nrnGcd;
  r->cfIsUnit      = nrnIsUnit;
  r->cfGetUnit     = nrnGetUnit;
  r->cfExtGcd      = nrnExtGcd;
  r->cfXExtGcd     = nrnXExtGcd;
  r->cfQuotRem     = nrnQuotRem;
  r->cfCoeffName   = nrnCoeffName;
  r->nCoeffIsEqual = nrnCoeffIsEqual;
  r->cfKillChar    = nrnKillChar;
  r->cfQuot1       = nrnQuot1;
  r->cfInitMPZ     = nrnInitMPZ;
  r->cfMPZ         = nrnMPZ;
#if SI_INTEGER_VARIANT==2
  r->cfWriteFd     = nrzWriteFd;
  r->cfReadFd      = nrzReadFd;
#endif

#ifdef LDEBUG
  r->cfDBTest      = nrnDBTest;
#endif
  if ((r->modExponent==1)&&(mpz_size1(r->modBase)==1))
  {
    long p=mpz_get_si(r->modBase);
    if ((p<=FACTORY_MAX_PRIME)&&(p==IsPrime(p))) /*factory limit: <2^29*/
    {
      r->convFactoryNSingN=nrnConvFactoryNSingN;
      r->convSingNFactoryN=nrnConvSingNFactoryN;
    }
  }
  return FALSE;
}
