//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : modular_operations.inl
// Author      : Patrick Theobald (PT)
// Last change : PT, Sep 28 1995, initial version
//

#ifndef LIDIA_MODULAR_OPERATIONS_INL
#define LIDIA_MODULAR_OPERATIONS_INL

/**
 ** base operations
 **/

inline void 
pos_div_rem(bigint & q, bigint & r, const bigint & a, const bigint & b)
{
  debug_handler_l("bigint_matrix", "in inline pos_div_rem()", LDBL_MATRIX);
  
  div_rem(q, r, a, b);

  if (r.is_lt_zero())
    if (b.is_lt_zero()) 
      {
	subtract(r, r, b);
	inc(q);
      }
    else 
      {
	add(r, r, b);
	dec(q);
      }
}

inline void 
pos_div_rem_long(long & q, long & r, long a, long b) 
{
  debug_handler_l("bigint_matrix", "in inline pos_div_rem_long()", LDBL_MATRIX);

  q = a / b; 
  r = a - q * b;

  if (r < 0)
    if (b < 0) 
      {
	r -= b;
	q++;
      }
    else 
      {
	r += b;
	q--;
      }
}

inline void 
best_remainder(bigint & a, const bigint & b, const bigint & mod) 
{
  debug_handler_l("bigint_matrix", "in inline best_remainder()", LDBL_MATRIX);

  if (mod.is_one() || b.is_zero()) 
    a.assign_zero();
  else 
    {
      bigint mod2;
      shift_right(mod2, mod, 1);
      remainder(a, b, mod);
      if (a <= -mod2) 
	add(a, a, mod);
      if (a > mod2) 
	subtract(a, a, mod);
    }
}

inline void 
best_remainder_long(long & a, const bigint & b, long mod) 
{
  debug_handler_l("bigint_matrix", "in inline best_remainder_long()", LDBL_MATRIX);

  if (mod == 1 || b.is_zero()) 
    a = 0;
  else 
    {
      register long mod2 = mod / 2;
      remainder(a, b, mod);
      if (a <= -mod2) 
	a += mod;
      if (a > mod2) 
	a -= mod;
    }
}

inline void 
add_mod(bigint & c, const bigint & a, const bigint & b, const bigint & mod)
{
  debug_handler_l("bigint_matrix", "in inline add_mod()", LDBL_MATRIX);

  if (mod.is_one()) 
    c.assign_zero();
  else 
    {
      bigint mod2;
      shift_right(mod2, mod, 1);
      add(c, a, b);
      if (c <= -mod2) 
	add(c, c, mod);
      if (c > mod2) 
	subtract(c, c, mod);
    }
}

inline void 
add_mod_long(long & d, long a, long b, long mod)
{
  debug_handler_l("bigint_matrix", "in inline add_mod_long()", LDBL_MATRIX);

  if (mod == 1) 
    d = 0;
  else 
    {
      register long mod2 = mod / 2;
      double c = (double) a + (double) b;
      if (c <= (double) -mod2) 
	c += (double) mod;
      if (c > (double) mod2) 
	c -= (double) mod;
      d = (long) c;
    }
}

inline void 
sub_mod(bigint & c, const bigint & a, const bigint & b, const bigint & mod)
{
  debug_handler("bigint_matrix","in inline sub_mod()");

  if (mod.is_one()) 
    c.assign_zero();
  else 
    {
      bigint mod2;
      shift_right(mod2,mod,1);
      subtract(c, a, b);
      if (c <= -mod2) 
	add(c, c, mod);
      if (c > mod2) 
	subtract(c, c, mod);
    }
}

inline void 
sub_mod_long(long & d, long a, long b, long mod)
{
  debug_handler_l("bigint_matrix", "in inline sub_mod_long()", LDBL_MATRIX);

  if (mod == 1) 
    d = 0;
  else 
    {
      register long mod2 = mod/2;
      double c = (double) a - (double) b;
      if (c <= (double) -mod2)
	c += (double) mod;
      if (c > (double) mod2) 
	c -= (double) mod;
      d = (long) c;
    }
}

inline void 
mult_mod(bigint & c, const bigint & a, const bigint & b, const bigint & mod)
{
  debug_handler_l("bigint_matrix", "in inline mult_mod()", LDBL_MATRIX);

  if (mod.is_one()) 
    c.assign_zero();
  else 
    {
      bigint mod2;
      shift_right(mod2, mod, 1);
      multiply(c, a, b);
      best_remainder(c, c, mod);
    }
}

inline void 
mult_mod_long(long & d, long a, long b, long mod)
{
  debug_handler_l("bigint_matrix", "in inline mult_mod_long()", LDBL_MATRIX);

  if (mod == 1) 
    d = 0;
  else 
    {
      register long mod2 = mod/2;
      double ab = ((double) a) * ((double) b);
      register long q = (long) (ab / ((double) mod));
      register long res = (long) (ab - (((double) q) * ((double) mod)));
      if (res > mod2) 
	res -= mod;
      if (res <= -mod2) 
	res += mod;
      d = res;
    }
}

inline void 
div_mod(bigint & c, const bigint & a, const bigint & b, const bigint & mod)
{
  debug_handler_l("bigint_matrix", "in inline div_mod()", LDBL_MATRIX);

  bigint u, v;
  bigint d = xgcd(u, v, b, mod);
  if (!d.is_one())
    lidia_error_handler("bigint_matrix", "div_mod - Version bigint :: "
			"Inverse undefined");
  mult_mod(c, a, u, mod);
}

inline void 
div_mod_long(long & c, long a, long b, long mod)
{
  debug_handler_l("bigint_matrix", "in inline div_mod_long()", LDBL_MATRIX);
  
  long u, v;
  register long d = xgcd(u, v, b, mod);
  if (d != 1)
    lidia_error_handler("bigint_matrix", "div_mod_long - Version long :: "
			"Inverse undefined");
  mult_mod_long(c, a, u, mod);
}

inline void 
inv_mod(bigint & c, const bigint & a, const bigint & mod) 
{
  debug_handler_l("bigint_matrix", "in inline inv_mod()", LDBL_MATRIX);

  bigint d, v, mod2;
  shift_right(mod2, mod, 1);
  d = xgcd(c, v, a, mod);
  if (!d.is_one())
    lidia_error_handler("bigint_matrix", "inv_mod - Version bigint :: "
			"Inverse undefined");
  if (c <= -mod2) 
    add(c, c, mod);
  if (c > mod2) 
    subtract(c, c, mod);
}

inline void 
inv_mod_long(long & e, long a, long mod)
{
  debug_handler_l("bigint_matrix", "in inline inv_mod_long()", LDBL_MATRIX);

  long t, mod2 = mod/2;
  register long d = xgcd(e, t, a, mod);
  if (d != 1)
    lidia_error_handler("bigint_matrix", "inv_mod_long - Version long :: "
			"Inverse undefined");
  if (e <= -mod2) 
    e += mod;
  if (e > mod2) 
    e -= mod;
}

#endif
  
