// m > 1, standard representation, no tricks

namespace cln {

static void std_fprint (cl_heap_modint_ring* R, std::ostream& stream, const _cl_MI &x)
{
	fprint(stream,R->_retract(x));
	fprint(stream," mod ");
	fprint(stream,R->modulus);
}

static const cl_I std_reduce_modulo (cl_heap_modint_ring* R, const cl_I& x)
{
	return mod(x,R->modulus);
}

static const _cl_MI std_canonhom (cl_heap_modint_ring* R, const cl_I& x)
{
	return _cl_MI(R, mod(x,R->modulus));
}

static const cl_I std_retract (cl_heap_modint_ring* R, const _cl_MI& x)
{
	unused R;
	return x.rep;
}

static const _cl_MI std_random (cl_heap_modint_ring* R, random_state& randomstate)
{
	return _cl_MI(R, random_I(randomstate,R->modulus));
}

static const _cl_MI std_zero (cl_heap_modint_ring* R)
{
	return _cl_MI(R, 0);
}

static cl_boolean std_zerop (cl_heap_modint_ring* R, const _cl_MI& x)
{
	unused R;
	return zerop(x.rep);
}

static const _cl_MI std_plus (cl_heap_modint_ring* R, const _cl_MI& x, const _cl_MI& y)
{
	var cl_I zr = x.rep + y.rep;
	return _cl_MI(R, (zr >= R->modulus ? zr - R->modulus : zr));
}

static const _cl_MI std_minus (cl_heap_modint_ring* R, const _cl_MI& x, const _cl_MI& y)
{
	var cl_I zr = x.rep - y.rep;
	return _cl_MI(R, (minusp(zr) ? zr + R->modulus : zr));
}

static const _cl_MI std_uminus (cl_heap_modint_ring* R, const _cl_MI& x)
{
	return _cl_MI(R, (zerop(x.rep) ? (cl_I)0 : R->modulus - x.rep));
}

static const _cl_MI std_one (cl_heap_modint_ring* R)
{
	return _cl_MI(R, 1);
}

static const _cl_MI std_mul (cl_heap_modint_ring* R, const _cl_MI& x, const _cl_MI& y)
{
	return _cl_MI(R, mod(x.rep * y.rep, R->modulus));
}

static const _cl_MI std_square (cl_heap_modint_ring* R, const _cl_MI& x)
{
	return _cl_MI(R, mod(square(x.rep), R->modulus));
}

static const cl_MI_x std_recip (cl_heap_modint_ring* R, const _cl_MI& x)
{
	var const cl_I& xr = x.rep;
	var cl_I u, v;
	var cl_I g = xgcd(xr,R->modulus,&u,&v);
	// g = gcd(x,m) = x*u+m*v
	if (eq(g,1))
		return cl_MI(R, (minusp(u) ? u + R->modulus : u));
	if (zerop(xr))
		cl_error_division_by_0();
	return cl_notify_composite(R,xr);
}

static const cl_MI_x std_div (cl_heap_modint_ring* R, const _cl_MI& x, const _cl_MI& y)
{
	var const cl_I& yr = y.rep;
	var cl_I u, v;
	var cl_I g = xgcd(yr,R->modulus,&u,&v);
	// g = gcd(y,m) = y*u+m*v
	if (eq(g,1))
		return cl_MI(R, mod(x.rep * (minusp(u) ? u + R->modulus : u), R->modulus));
	if (zerop(yr))
		cl_error_division_by_0();
	return cl_notify_composite(R,yr);
}

static uint8 const ord2_table[256] = // maps i -> ord2(i) for i>0
{
 63,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  6,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  7,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  6,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
  4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0
};

static uint8 const odd_table[256] = // maps i -> i/2^ord2(i) for i>0
{
  0,   1,   1,   3,   1,   5,   3,   7,   1,   9,   5,  11,   3,  13,   7,  15,
  1,  17,   9,  19,   5,  21,  11,  23,   3,  25,  13,  27,   7,  29,  15,  31,
  1,  33,  17,  35,   9,  37,  19,  39,   5,  41,  21,  43,  11,  45,  23,  47,
  3,  49,  25,  51,  13,  53,  27,  55,   7,  57,  29,  59,  15,  61,  31,  63,
  1,  65,  33,  67,  17,  69,  35,  71,   9,  73,  37,  75,  19,  77,  39,  79,
  5,  81,  41,  83,  21,  85,  43,  87,  11,  89,  45,  91,  23,  93,  47,  95,
  3,  97,  49,  99,  25, 101,  51, 103,  13, 105,  53, 107,  27, 109,  55, 111,
  7, 113,  57, 115,  29, 117,  59, 119,  15, 121,  61, 123,  31, 125,  63, 127,
  1, 129,  65, 131,  33, 133,  67, 135,  17, 137,  69, 139,  35, 141,  71, 143,
  9, 145,  73, 147,  37, 149,  75, 151,  19, 153,  77, 155,  39, 157,  79, 159,
  5, 161,  81, 163,  41, 165,  83, 167,  21, 169,  85, 171,  43, 173,  87, 175,
 11, 177,  89, 179,  45, 181,  91, 183,  23, 185,  93, 187,  47, 189,  95, 191,
  3, 193,  97, 195,  49, 197,  99, 199,  25, 201, 101, 203,  51, 205, 103, 207,
 13, 209, 105, 211,  53, 213, 107, 215,  27, 217, 109, 219,  55, 221, 111, 223,
  7, 225, 113, 227,  57, 229, 115, 231,  29, 233, 117, 235,  59, 237, 119, 239,
 15, 241, 121, 243,  61, 245, 123, 247,  31, 249, 125, 251,  63, 253, 127, 255
};

static const _cl_MI std_expt_pos (cl_heap_modint_ring* R, const _cl_MI& x, const cl_I& y)
{
#if 0
	// Methode:
	// Right-Left Binary, [Cohen, Algorithm 1.2.1.]
	//   a:=x, b:=y.
	//   Solange b gerade, setze a:=a*a, b:=b/2. (Invariante: a^b = x^y.)
	//   c:=a.
	//   Solange b:=floor(b/2) >0 ist,
	//     setze a:=a*a, und falls b ungerade, setze c:=a*c.
	//   Liefere c.
	var _cl_MI a = x;
	var cl_I b = y;
	while (!oddp(b)) { a = R->_square(a); b = b >> 1; } // a^b = x^y
	var _cl_MI c = a;
	until (eq(b,1)) {
		b = b >> 1;
		a = R->_square(a);
		// a^b*c = x^y
		if (oddp(b))
			c = R->_mul(a,c);
	}
	return c;
#else
	// Methode:
	// Left-Right base 2^k, [Cohen, Algorithm 1.2.4.]
	// Good values of k, depending on the size nn of the exponent n:
	//   k = 1 for nn <= 8
	//   k = 2 for nn <= 24
	//   k = 3 for nn <= 69.8
	//   ... k for nn <= k*(k+1)*2^(2k)/(2^(k+1)-k-2)
	var cl_I n = y;
	var uintL nn = integer_length(n);
	// n has nn bits.
	if (nn <= 8) {
		// k = 1, normal Left-Right Binary algorithm.
		var uintL _n = FN_to_UL(n);
		var _cl_MI a = x;
		for (var int i = nn-2; i >= 0; i--) {
			a = R->_square(a);
			if (_n & bit(i))
				a = R->_mul(a,x);
		}
		return cl_MI(R,a);
	} else {
		// General Left-Right base 2^k algorithm.
		CL_ALLOCA_STACK;
		var uintL k;
		     if (nn <= 24)	k = 2;
		else if (nn <= 69)	k = 3;
		else if (nn <= 196)	k = 4;
		else if (nn <= 538)	k = 5;
		else if (nn <= 1433)	k = 6;
		else if (nn <= 3714)	k = 7;
		else if (nn <= 9399)	k = 8;
		else if (nn <= 23290)	k = 9;
		else if (nn <= 56651)	k = 10;
		else if (nn <= 135598)	k = 11;
		else if (nn <= 320034)	k = 12;
		else if (nn <= 746155)	k = 13;
		else if (nn <= 1721160)	k = 14;
		else if (nn <= 3933180)	k = 15;
		else /* if (nn <= 8914120) */ k = 16;
		var uintL nnk = ceiling(nn,k); // number of base-2^k digits in n
		var uint16* n_digits = cl_alloc_array(uint16,nnk);
		// Split n into base-2^k digits.
		{
			var const uintD* n_LSDptr;
			var const uintD* n_MSDptr;
			I_to_NDS_nocopy(n, n_MSDptr=,,n_LSDptr=,cl_false,);
			var const uintL k_mask = bit(k)-1;
			var uintD carry = 0;
			var unsigned int carrybits = 0;
			for (var uintL i = 0; i < nnk; i++) {
				if (carrybits >= k) {
					n_digits[i] = carry & k_mask;
					carry = carry >> k;
					carrybits -= k;
				} else {
					var uintD next =
					  (n_LSDptr==n_MSDptr ? 0 : lsprefnext(n_LSDptr));
					n_digits[i] = (carry | (next << carrybits)) & k_mask;
					carry = next >> (k-carrybits);
					carrybits = intDsize - (k-carrybits);
				}
			}
		}
		// Compute maximum odd base-2^k digit.
		var uintL maxodd = 1;
		if (k <= 8) {
			for (var uintL i = 0; i < nnk; i++) {
				var uintL d = n_digits[i];
				if (d > 0) {
					d = odd_table[d];
					if (d > maxodd) maxodd = d;
				}
			}
		} else {
			for (var uintL i = 0; i < nnk; i++) {
				var uintL d = n_digits[i];
				if (d > 0) {
					var uintL d2; ord2_32(d,d2=);
					d = d>>d2;
					if (d > maxodd) maxodd = d;
				}
			}
		}
		maxodd = (maxodd+1)/2; // number of odd powers we need
		var _cl_MI* x_oddpow = cl_alloc_array(_cl_MI,maxodd);
		var _cl_MI x2 = (maxodd > 1 ? R->_square(x) : R->_zero());
		{
			init1(_cl_MI, x_oddpow[0]) (x);
			for (var uintL i = 1; i < maxodd; i++)
				init1(_cl_MI, x_oddpow[i]) (R->_mul(x_oddpow[i-1],x2));
		}
		var _cl_MI a;
		// Compute a = x^n_digits[nnk-1].
		{
			var uintL d = n_digits[nnk-1];
			if (d == 0) cl_abort();
			var uintL d2;
			if (k <= 8)
				d2 = ord2_table[d];
			else
				ord2_32(d,d2=);
			d = d>>d2; // d := d/2^ord2(d)
			d = floor(d,2);
			a = x_oddpow[d]; // x^d
			// Square d2 times.
			if (d==0 && maxodd > 1 && d2>0) {
				a = x2; d2--;
			}
			if (!(d2 < k)) cl_abort();
			for ( ; d2>0; d2--)
				a = R->_square(a);
		}
		for (var sintL i = nnk-2; i >= 0; i--) {
			// Compute a := a^(2^k) * x^n_digits[i].
			var uintL d = n_digits[i];
			var uintL d2;
			if (d > 0) {
				if (k <= 8)
					d2 = ord2_table[d];
				else
					ord2_32(d,d2=);
				d = d>>d2; // d/2^ord2(d)
				d = floor(d,2);
				for (var sintL j = k-d2; j>0; j--)
					a = R->_square(a);
				a = R->_mul(a,x_oddpow[d]);
			} else
				d2 = k;
			// Square d2 times.
			if (!(d2 <= k)) cl_abort();
			for ( ; d2>0; d2--)
				a = R->_square(a);
		}
		{
			for (var uintL i = 0; i < maxodd; i++)
				x_oddpow[i].~_cl_MI();
		}
		return cl_MI(R,a);
	}
#endif
}

static const cl_MI_x std_expt (cl_heap_modint_ring* R, const _cl_MI& x, const cl_I& y)
{
	if (!minusp(y)) {
		if (zerop(y))
			return R->one();
		else
			return cl_MI(R,R->_expt_pos(x,y));
	} else
		return R->_recip(R->_expt_pos(x,-y));
}

static cl_modint_setops std_setops = {
	std_fprint,
	modint_equal,
	std_random
};
static cl_modint_addops std_addops = {
	std_zero,
	std_zerop,
	std_plus,
	std_minus,
	std_uminus
};
static cl_modint_mulops std_mulops = {
	std_one,
	std_canonhom,
	std_mul,
	std_square,
	std_expt_pos,
	std_recip,
	std_div,
	std_expt,
	std_reduce_modulo,
	std_retract
};

class cl_heap_modint_ring_std : public cl_heap_modint_ring {
	SUBCLASS_cl_heap_modint_ring()
public:
	// Constructor.
	cl_heap_modint_ring_std (const cl_I& m)
		: cl_heap_modint_ring (m, &std_setops, &std_addops, &std_mulops) {}
	// Virtual destructor.
	~cl_heap_modint_ring_std () {}
};

}  // namespace cln
