#include <NTL/ZZ_pEX.h>
#include <NTL/vec_vec_ZZ_p.h>
#include <NTL/ZZX.h>

NTL_START_IMPL

const ZZ_pEX& ZZ_pEX::zero()
{
   static const ZZ_pEX z; // GLOBAL (assumes C++11 thread-safe init)
   return z;
}

istream& operator>>(istream& s, ZZ_pEX& x)
{
   NTL_INPUT_CHECK_RET(s, s >> x.rep);
   x.normalize();
   return s;
}

ostream& operator<<(ostream& s, const ZZ_pEX& a)
{
   return s << a.rep;
}

void ZZ_pEX::normalize()
{
   long n;
   const ZZ_pE* p;

   n = rep.length();
   if (n == 0) return;
   p = rep.elts() + n;
   while (n > 0 && IsZero(*--p)) {
      n--;
   }
   rep.SetLength(n);
}

long IsZero(const ZZ_pEX& a)
{
   return a.rep.length() == 0;
}

long IsOne(const ZZ_pEX& a)
{
    return a.rep.length() == 1 && IsOne(a.rep[0]);
}

long operator==(const ZZ_pEX& a, long b)
{
   if (b == 0)
      return IsZero(a);

   if (b == 1)
      return IsOne(a);

   long da = deg(a);

   if (da > 0) return 0;

   NTL_ZZ_pRegister(bb);
   bb = b;

   if (da < 0)
      return IsZero(bb);

   return a.rep[0] == bb;
}

long operator==(const ZZ_pEX& a, const ZZ_p& b)
{
   if (IsZero(b))
      return IsZero(a);

   long da = deg(a);

   if (da != 0)
      return 0;

   return a.rep[0] == b;
}

long operator==(const ZZ_pEX& a, const ZZ_pE& b)
{
   if (IsZero(b))
      return IsZero(a);

   long da = deg(a);

   if (da != 0)
      return 0;

   return a.rep[0] == b;
}

void SetCoeff(ZZ_pEX& x, long i, const ZZ_pE& a)
{
   long j, m;

   if (i < 0)
      LogicError("SetCoeff: negative index");

   if (NTL_OVERFLOW(i, 1, 0))
      ResourceError("overflow in SetCoeff");

   m = deg(x);

   if (i > m && IsZero(a)) return;

   if (i > m) {
      /* careful: a may alias a coefficient of x */

      long alloc = x.rep.allocated();

      if (alloc > 0 && i >= alloc) {
         ZZ_pE aa = a;
         x.rep.SetLength(i+1);
         x.rep[i] = aa;
      }
      else {
         x.rep.SetLength(i+1);
         x.rep[i] = a;
      }

      for (j = m+1; j < i; j++)
         clear(x.rep[j]);
   }
   else
      x.rep[i] = a;

   x.normalize();
}

void SetCoeff(ZZ_pEX& x, long i, const ZZ_p& aa)
{
   long j, m;

   if (i < 0)
      LogicError("SetCoeff: negative index");

   if (NTL_OVERFLOW(i, 1, 0))
      ResourceError("overflow in SetCoeff");

   NTL_ZZ_pRegister(a);  // watch out for aliases!
   a = aa;

   m = deg(x);

   if (i > m && IsZero(a)) return;

   if (i > m) {
      x.rep.SetLength(i+1);
      for (j = m+1; j < i; j++)
         clear(x.rep[j]);
   }
   x.rep[i] = a;
   x.normalize();
}

void SetCoeff(ZZ_pEX& x, long i, long a)
{
   if (a == 1)
      SetCoeff(x, i);
   else {
      NTL_ZZ_pRegister(T);
      T = a;
      SetCoeff(x, i, T);
   }
}

void SetCoeff(ZZ_pEX& x, long i)
{
   long j, m;

   if (i < 0)
      LogicError("coefficient index out of range");

   if (NTL_OVERFLOW(i, 1, 0))
      ResourceError("overflow in SetCoeff");

   m = deg(x);

   if (i > m) {
      x.rep.SetLength(i+1);
      for (j = m+1; j < i; j++)
         clear(x.rep[j]);
   }
   set(x.rep[i]);
   x.normalize();
}

void SetX(ZZ_pEX& x)
{
   clear(x);
   SetCoeff(x, 1);
}

long IsX(const ZZ_pEX& a)
{
   return deg(a) == 1 && IsOne(LeadCoeff(a)) && IsZero(ConstTerm(a));
}

const ZZ_pE& coeff(const ZZ_pEX& a, long i)
{
   if (i < 0 || i > deg(a))
      return ZZ_pE::zero();
   else
      return a.rep[i];
}

const ZZ_pE& LeadCoeff(const ZZ_pEX& a)
{
   if (IsZero(a))
      return ZZ_pE::zero();
   else
      return a.rep[deg(a)];
}

const ZZ_pE& ConstTerm(const ZZ_pEX& a)
{
   if (IsZero(a))
      return ZZ_pE::zero();
   else
      return a.rep[0];
}

void conv(ZZ_pEX& x, const ZZ_pE& a)
{
   if (IsZero(a))
      x.rep.SetLength(0);
   else {
      x.rep.SetLength(1);
      x.rep[0] = a;
   }
}

void conv(ZZ_pEX& x, long a)
{
   if (a == 0)
      clear(x);
   else if (a == 1)
      set(x);
   else {
      NTL_ZZ_pRegister(T);
      T = a;
      conv(x, T);
   }
}

void conv(ZZ_pEX& x, const ZZ& a)
{
   NTL_ZZ_pRegister(T);
   conv(T, a);
   conv(x, T);
}

void conv(ZZ_pEX& x, const ZZ_p& a)
{
   if (IsZero(a))
      clear(x);
   else if (IsOne(a))
      set(x);
   else {
      x.rep.SetLength(1);
      conv(x.rep[0], a);
      x.normalize();
   }
}

void conv(ZZ_pEX& x, const ZZ_pX& aa)
{
   ZZ_pX a = aa; // in case a aliases the rep of a coefficient of x

   long n = deg(a)+1;
   long i;

   x.rep.SetLength(n);
   for (i = 0; i < n; i++)
      conv(x.rep[i], coeff(a, i));
}

void conv(ZZ_pEX& x, const vec_ZZ_pE& a)
{
   x.rep = a;
   x.normalize();
}

/* additional legacy conversions for v6 conversion regime */

void conv(ZZ_pEX& x, const ZZX& a)
{
   long n = a.rep.length();
   long i;

   x.rep.SetLength(n);
   for (i = 0; i < n; i++)
      conv(x.rep[i], a.rep[i]);

   x.normalize();
}

/* ------------------------------------- */

void add(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long da = deg(a);
   long db = deg(b);
   long minab = min(da, db);
   long maxab = max(da, db);
   x.rep.SetLength(maxab+1);

   long i;
   const ZZ_pE *ap, *bp;
   ZZ_pE* xp;

   for (i = minab+1, ap = a.rep.elts(), bp = b.rep.elts(), xp = x.rep.elts();
        i; i--, ap++, bp++, xp++)
      add(*xp, (*ap), (*bp));

   if (da > minab && &x != &a)
      for (i = da-minab; i; i--, xp++, ap++)
         *xp = *ap;
   else if (db > minab && &x != &b)
      for (i = db-minab; i; i--, xp++, bp++)
         *xp = *bp;
   else
      x.normalize();
}

void add(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pE& b)
{
   long n = a.rep.length();
   if (n == 0) {
      conv(x, b);
   }
   else if (&x == &a) {
      add(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else if (x.rep.MaxLength() == 0) {
      x = a;
      add(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else {
      // ugly...b could alias a coeff of x

      ZZ_pE *xp = x.rep.elts();
      add(xp[0], a.rep[0], b);
      x.rep.SetLength(n);
      xp = x.rep.elts();
      const ZZ_pE *ap = a.rep.elts();
      long i;
      for (i = 1; i < n; i++)
         xp[i] = ap[i];
      x.normalize();
   }
}

void add(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_p& b)
{
   long n = a.rep.length();
   if (n == 0) {
      conv(x, b);
   }
   else if (&x == &a) {
      add(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else if (x.rep.MaxLength() == 0) {
      x = a;
      add(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else {
      // ugly...b could alias a coeff of x

      ZZ_pE *xp = x.rep.elts();
      add(xp[0], a.rep[0], b);
      x.rep.SetLength(n);
      xp = x.rep.elts();
      const ZZ_pE *ap = a.rep.elts();
      long i;
      for (i = 1; i < n; i++)
         xp[i] = ap[i];
      x.normalize();
   }
}

void add(ZZ_pEX& x, const ZZ_pEX& a, long b)
{
   if (a.rep.length() == 0) {
      conv(x, b);
   }
   else {
      if (&x != &a) x = a;
      add(x.rep[0], x.rep[0], b);
      x.normalize();
   }
}

void sub(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long da = deg(a);
   long db = deg(b);
   long minab = min(da, db);
   long maxab = max(da, db);
   x.rep.SetLength(maxab+1);

   long i;
   const ZZ_pE *ap, *bp;
   ZZ_pE* xp;

   for (i = minab+1, ap = a.rep.elts(), bp = b.rep.elts(), xp = x.rep.elts();
        i; i--, ap++, bp++, xp++)
      sub(*xp, (*ap), (*bp));

   if (da > minab && &x != &a)
      for (i = da-minab; i; i--, xp++, ap++)
         *xp = *ap;
   else if (db > minab)
      for (i = db-minab; i; i--, xp++, bp++)
         negate(*xp, *bp);
   else
      x.normalize();
}

void sub(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pE& b)
{
   long n = a.rep.length();
   if (n == 0) {
      conv(x, b);
      negate(x, x);
   }
   else if (&x == &a) {
      sub(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else if (x.rep.MaxLength() == 0) {
      x = a;
      sub(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else {
      // ugly...b could alias a coeff of x

      ZZ_pE *xp = x.rep.elts();
      sub(xp[0], a.rep[0], b);
      x.rep.SetLength(n);
      xp = x.rep.elts();
      const ZZ_pE *ap = a.rep.elts();
      long i;
      for (i = 1; i < n; i++)
         xp[i] = ap[i];
      x.normalize();
   }
}

void sub(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_p& b)
{
   long n = a.rep.length();
   if (n == 0) {
      conv(x, b);
      negate(x, x);
   }
   else if (&x == &a) {
      sub(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else if (x.rep.MaxLength() == 0) {
      x = a;
      sub(x.rep[0], a.rep[0], b);
      x.normalize();
   }
   else {
      // ugly...b could alias a coeff of x

      ZZ_pE *xp = x.rep.elts();
      sub(xp[0], a.rep[0], b);
      x.rep.SetLength(n);
      xp = x.rep.elts();
      const ZZ_pE *ap = a.rep.elts();
      long i;
      for (i = 1; i < n; i++)
         xp[i] = ap[i];
      x.normalize();
   }
}

void sub(ZZ_pEX& x, const ZZ_pEX& a, long b)
{
   if (a.rep.length() == 0) {
      conv(x, b);
      negate(x, x);
   }
   else {
      if (&x != &a) x = a;
      sub(x.rep[0], x.rep[0], b);
      x.normalize();
   }
}

void sub(ZZ_pEX& x, const ZZ_pE& b, const ZZ_pEX& a)
{
   long n = a.rep.length();
   if (n == 0) {
      conv(x, b);
   }
   else if (x.rep.MaxLength() == 0) {
      negate(x, a);
      add(x.rep[0], x.rep[0], b);
      x.normalize();
   }
   else {
      // ugly...b could alias a coeff of x

      ZZ_pE *xp = x.rep.elts();
      sub(xp[0], b, a.rep[0]);
      x.rep.SetLength(n);
      xp = x.rep.elts();
      const ZZ_pE *ap = a.rep.elts();
      long i;
      for (i = 1; i < n; i++)
         negate(xp[i], ap[i]);
      x.normalize();
   }
}

void sub(ZZ_pEX& x, const ZZ_p& a, const ZZ_pEX& b)
{
   NTL_ZZ_pRegister(T);   // avoids aliasing problems
   T = a;
   negate(x, b);
   add(x, x, T);
}

void sub(ZZ_pEX& x, long a, const ZZ_pEX& b)
{
   NTL_ZZ_pRegister(T);
   T = a;
   negate(x, b);
   add(x, x, T);
}

void mul(ZZ_pEX& c, const ZZ_pEX& a, const ZZ_pEX& b)
{
   if (&a == &b) {
      sqr(c, a);
      return;
   }

   if (IsZero(a) || IsZero(b)) {
      clear(c);
      return;
   }

   if (deg(a) == 0) {
      mul(c, b, ConstTerm(a));
      return;
   }

   if (deg(b) == 0) {
      mul(c, a, ConstTerm(b));
      return;
   }

   // general case...Kronecker subst

   ZZ_pX A, B, C;

   long da = deg(a);
   long db = deg(b);

   long n = ZZ_pE::degree();
   long n2 = 2*n-1;

   if (NTL_OVERFLOW(da+db+1, n2, 0))
      ResourceError("overflow in ZZ_pEX mul");

   long i, j;

   A.rep.SetLength((da+1)*n2);

   for (i = 0; i <= da; i++) {
      const ZZ_pX& coeff = rep(a.rep[i]);
      long dcoeff = deg(coeff);
      for (j = 0; j <= dcoeff; j++)
         A.rep[n2*i + j] = coeff.rep[j];
   }

   A.normalize();

   B.rep.SetLength((db+1)*n2);

   for (i = 0; i <= db; i++) {
      const ZZ_pX& coeff = rep(b.rep[i]);
      long dcoeff = deg(coeff);
      for (j = 0; j <= dcoeff; j++)
         B.rep[n2*i + j] = coeff.rep[j];
   }

   B.normalize();

   mul(C, A, B);

   long Clen = C.rep.length();
   long lc = (Clen + n2 - 1)/n2;
   long dc = lc - 1;

   c.rep.SetLength(dc+1);

   ZZ_pX tmp;

   for (i = 0; i <= dc; i++) {
      tmp.rep.SetLength(n2);
      for (j = 0; j < n2 && n2*i + j < Clen; j++)
         tmp.rep[j] = C.rep[n2*i + j];
      for (; j < n2; j++)
         clear(tmp.rep[j]);
      tmp.normalize();
      conv(c.rep[i], tmp);
   }

   c.normalize();
}

void mul(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pE& b)
{
   if (IsZero(b)) {
      clear(x);
      return;
   }

   ZZ_pE t;
   t = b;

   long i, da;

   const ZZ_pE *ap;
   ZZ_pE* xp;

   da = deg(a);
   x.rep.SetLength(da+1);
   ap = a.rep.elts();
   xp = x.rep.elts();

   for (i = 0; i <= da; i++)
      mul(xp[i], ap[i], t);

   x.normalize();
}

void mul(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_p& b)
{
   if (IsZero(b)) {
      clear(x);
      return;
   }

   NTL_ZZ_pRegister(t);
   t = b;

   long i, da;

   const ZZ_pE *ap;
   ZZ_pE* xp;

   da = deg(a);
   x.rep.SetLength(da+1);
   ap = a.rep.elts();
   xp = x.rep.elts();

   for (i = 0; i <= da; i++)
      mul(xp[i], ap[i], t);

   x.normalize();
}

void mul(ZZ_pEX& x, const ZZ_pEX& a, long b)
{
   NTL_ZZ_pRegister(t);
   t = b;
   mul(x, a, t);
}

void sqr(ZZ_pEX& c, const ZZ_pEX& a)
{
   if (IsZero(a)) {
      clear(c);
      return;
   }

   if (deg(a) == 0) {
      ZZ_pE res;
      sqr(res, ConstTerm(a));
      conv(c, res);
      return;
   }

   // general case...Kronecker subst

   ZZ_pX A, C;

   long da = deg(a);

   long n = ZZ_pE::degree();
   long n2 = 2*n-1;

   if (NTL_OVERFLOW(2*da+1, n2, 0))
      ResourceError("overflow in ZZ_pEX sqr");

   long i, j;

   A.rep.SetLength((da+1)*n2);

   for (i = 0; i <= da; i++) {
      const ZZ_pX& coeff = rep(a.rep[i]);
      long dcoeff = deg(coeff);
      for (j = 0; j <= dcoeff; j++)
         A.rep[n2*i + j] = coeff.rep[j];
   }

   A.normalize();

   sqr(C, A);

   long Clen = C.rep.length();
   long lc = (Clen + n2 - 1)/n2;
   long dc = lc - 1;

   c.rep.SetLength(dc+1);

   ZZ_pX tmp;

   for (i = 0; i <= dc; i++) {
      tmp.rep.SetLength(n2);
      for (j = 0; j < n2 && n2*i + j < Clen; j++)
         tmp.rep[j] = C.rep[n2*i + j];
      for (; j < n2; j++)
         clear(tmp.rep[j]);
      tmp.normalize();
      conv(c.rep[i], tmp);
   }

   c.normalize();
}

void MulTrunc(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& b, long n)
{
   if (n < 0) LogicError("MulTrunc: bad args");

   ZZ_pEX t;
   mul(t, a, b);
   trunc(x, t, n);
}

void SqrTrunc(ZZ_pEX& x, const ZZ_pEX& a, long n)
{
   if (n < 0) LogicError("SqrTrunc: bad args");

   ZZ_pEX t;
   sqr(t, a);
   trunc(x, t, n);
}

void CopyReverse(ZZ_pEX& x, const ZZ_pEX& a, long hi)

   // x[0..hi] = reverse(a[0..hi]), with zero fill
   // input may not alias output

{
   long i, j, n, m;

   n = hi+1;
   m = a.rep.length();

   x.rep.SetLength(n);

   const ZZ_pE* ap = a.rep.elts();
   ZZ_pE* xp = x.rep.elts();

   for (i = 0; i < n; i++) {
      j = hi-i;
      if (j < 0 || j >= m)
         clear(xp[i]);
      else
         xp[i] = ap[j];
   }

   x.normalize();
}

void trunc(ZZ_pEX& x, const ZZ_pEX& a, long m)

// x = a % X^m, output may alias input

{
   if (m < 0) LogicError("trunc: bad args");

   if (&x == &a) {
      if (x.rep.length() > m) {
         x.rep.SetLength(m);
         x.normalize();
      }
   }
   else {
      long n;
      long i;
      ZZ_pE* xp;
      const ZZ_pE* ap;

      n = min(a.rep.length(), m);
      x.rep.SetLength(n);

      xp = x.rep.elts();
      ap = a.rep.elts();

      for (i = 0; i < n; i++) xp[i] = ap[i];

      x.normalize();
   }
}

void random(ZZ_pEX& x, long n)
{
   long i;

   x.rep.SetLength(n);

   for (i = 0; i < n; i++)
      random(x.rep[i]);

   x.normalize();
}

void negate(ZZ_pEX& x, const ZZ_pEX& a)
{
   long n = a.rep.length();
   x.rep.SetLength(n);

   const ZZ_pE* ap = a.rep.elts();
   ZZ_pE* xp = x.rep.elts();
   long i;

   for (i = n; i; i--, ap++, xp++)
      negate((*xp), (*ap));
}

static
void MulByXModAux(ZZ_pEX& h, const ZZ_pEX& a, const ZZ_pEX& f)
{
   long i, n, m;
   ZZ_pE* hh;
   const ZZ_pE *aa, *ff;

   ZZ_pE t, z;

   n = deg(f);
   m = deg(a);

   if (m >= n || n == 0) LogicError("MulByXMod: bad args");

   if (m < 0) {
      clear(h);
      return;
   }

   if (m < n-1) {
      h.rep.SetLength(m+2);
      hh = h.rep.elts();
      aa = a.rep.elts();
      for (i = m+1; i >= 1; i--)
         hh[i] = aa[i-1];
      clear(hh[0]);
   }
   else {
      h.rep.SetLength(n);
      hh = h.rep.elts();
      aa = a.rep.elts();
      ff = f.rep.elts();
      negate(z, aa[n-1]);
      if (!IsOne(ff[n]))
         div(z, z, ff[n]);
      for (i = n-1; i >= 1; i--) {
         mul(t, z, ff[i]);
         add(hh[i], aa[i-1], t);
      }
      mul(hh[0], z, ff[0]);
      h.normalize();
   }
}

void MulByXMod(ZZ_pEX& h, const ZZ_pEX& a, const ZZ_pEX& f)
{
   if (&h == &f) {
      ZZ_pEX hh;
      MulByXModAux(hh, a, f);
      h = hh;
   }
   else
      MulByXModAux(h, a, f);
}

void PlainMul(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long da = deg(a);
   long db = deg(b);

   if (da < 0 || db < 0) {
      clear(x);
      return;
   }

   long d = da+db;

   const ZZ_pE *ap, *bp;
   ZZ_pE *xp;

   ZZ_pEX la, lb;

   if (&x == &a) {
      la = a;
      ap = la.rep.elts();
   }
   else
      ap = a.rep.elts();

   if (&x == &b) {
      lb = b;
      bp = lb.rep.elts();
   }
   else
      bp = b.rep.elts();

   x.rep.SetLength(d+1);

   xp = x.rep.elts();

   long i, j, jmin, jmax;
   ZZ_pX t, accum;

   for (i = 0; i <= d; i++) {
      jmin = max(0, i-db);
      jmax = min(da, i);
      clear(accum);
      for (j = jmin; j <= jmax; j++) {
         mul(t, rep(ap[j]), rep(bp[i-j]));
         add(accum, accum, t);
      }
      conv(xp[i], accum);
   }
   x.normalize();
}

void SetSize(vec_ZZ_pX& x, long n, long m)
{
   x.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      x[i].rep.SetMaxLength(m);
}

void PlainDivRem(ZZ_pEX& q, ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long da, db, dq, i, j, LCIsOne;
   const ZZ_pE *bp;
   ZZ_pE *qp;
   ZZ_pX *xp;

   ZZ_pE LCInv, t;
   ZZ_pX s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("ZZ_pEX: division by zero");

   if (da < db) {
      r = a;
      clear(q);
      return;
   }

   ZZ_pEX lb;

   if (&q == &b) {
      lb = b;
      bp = lb.rep.elts();
   }
   else
      bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   vec_ZZ_pX x;

   SetSize(x, da+1, 2*ZZ_pE::degree());

   for (i = 0; i <= da; i++)
      x[i] = rep(a.rep[i]);

   xp = x.elts();

   dq = da - db;
   q.rep.SetLength(dq+1);
   qp = q.rep.elts();

   for (i = dq; i >= 0; i--) {
      conv(t, xp[i+db]);
      if (!LCIsOne)
         mul(t, t, LCInv);
      qp[i] = t;
      negate(t, t);

      for (j = db-1; j >= 0; j--) {
         mul(s, rep(t), rep(bp[j]));
         add(xp[i+j], xp[i+j], s);
      }
   }

   r.rep.SetLength(db);
   for (i = 0; i < db; i++)
      conv(r.rep[i], xp[i]);
   r.normalize();
}

void PlainRem(ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b, vec_ZZ_pX& x)
{
   long da, db, dq, i, j, LCIsOne;
   const ZZ_pE *bp;
   ZZ_pX *xp;

   ZZ_pE LCInv, t;
   ZZ_pX s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("ZZ_pEX: division by zero");

   if (da < db) {
      r = a;
      return;
   }

   bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   for (i = 0; i <= da; i++)
      x[i] = rep(a.rep[i]);

   xp = x.elts();

   dq = da - db;

   for (i = dq; i >= 0; i--) {
      conv(t, xp[i+db]);
      if (!LCIsOne)
         mul(t, t, LCInv);
      negate(t, t);

      for (j = db-1; j >= 0; j--) {
         mul(s, rep(t), rep(bp[j]));
         add(xp[i+j], xp[i+j], s);
      }
   }

   r.rep.SetLength(db);
   for (i = 0; i < db; i++)
      conv(r.rep[i], xp[i]);
   r.normalize();
}

void PlainDivRem(ZZ_pEX& q, ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b,
     vec_ZZ_pX& x)
{
   long da, db, dq, i, j, LCIsOne;
   const ZZ_pE *bp;
   ZZ_pE *qp;
   ZZ_pX *xp;

   ZZ_pE LCInv, t;
   ZZ_pX s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("ZZ_pEX: division by zero");

   if (da < db) {
      r = a;
      clear(q);
      return;
   }

   ZZ_pEX lb;

   if (&q == &b) {
      lb = b;
      bp = lb.rep.elts();
   }
   else
      bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   for (i = 0; i <= da; i++)
      x[i] = rep(a.rep[i]);

   xp = x.elts();

   dq = da - db;
   q.rep.SetLength(dq+1);
   qp = q.rep.elts();

   for (i = dq; i >= 0; i--) {
      conv(t, xp[i+db]);
      if (!LCIsOne)
         mul(t, t, LCInv);
      qp[i] = t;
      negate(t, t);

      for (j = db-1; j >= 0; j--) {
         mul(s, rep(t), rep(bp[j]));
         add(xp[i+j], xp[i+j], s);
      }
   }

   r.rep.SetLength(db);
   for (i = 0; i < db; i++)
      conv(r.rep[i], xp[i]);
   r.normalize();
}

void PlainDiv(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long da, db, dq, i, j, LCIsOne;
   const ZZ_pE *bp;
   ZZ_pE *qp;
   ZZ_pX *xp;

   ZZ_pE LCInv, t;
   ZZ_pX s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("ZZ_pEX: division by zero");

   if (da < db) {
      clear(q);
      return;
   }

   ZZ_pEX lb;

   if (&q == &b) {
      lb = b;
      bp = lb.rep.elts();
   }
   else
      bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   vec_ZZ_pX x;
   SetSize(x, da+1-db, 2*ZZ_pE::degree());

   for (i = db; i <= da; i++)
      x[i-db] = rep(a.rep[i]);

   xp = x.elts();

   dq = da - db;
   q.rep.SetLength(dq+1);
   qp = q.rep.elts();

   for (i = dq; i >= 0; i--) {
      conv(t, xp[i]);
      if (!LCIsOne)
         mul(t, t, LCInv);
      qp[i] = t;
      negate(t, t);

      long lastj = max(0, db-i);

      for (j = db-1; j >= lastj; j--) {
         mul(s, rep(t), rep(bp[j]));
         add(xp[i+j-db], xp[i+j-db], s);
      }
   }
}

void PlainRem(ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long da, db, dq, i, j, LCIsOne;
   const ZZ_pE *bp;
   ZZ_pX *xp;

   ZZ_pE LCInv, t;
   ZZ_pX s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("ZZ_pEX: division by zero");

   if (da < db) {
      r = a;
      return;
   }

   bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   vec_ZZ_pX x;
   SetSize(x, da + 1, 2*ZZ_pE::degree());

   for (i = 0; i <= da; i++)
      x[i] = rep(a.rep[i]);

   xp = x.elts();

   dq = da - db;

   for (i = dq; i >= 0; i--) {
      conv(t, xp[i+db]);
      if (!LCIsOne)
         mul(t, t, LCInv);
      negate(t, t);

      for (j = db-1; j >= 0; j--) {
         mul(s, rep(t), rep(bp[j]));
         add(xp[i+j], xp[i+j], s);
      }
   }

   r.rep.SetLength(db);
   for (i = 0; i < db; i++)
      conv(r.rep[i], xp[i]);
   r.normalize();
}

void RightShift(ZZ_pEX& x, const ZZ_pEX& a, long n)
{
   if (IsZero(a)) {
      clear(x);
      return;
   }

   if (n < 0) {
      if (n < -NTL_MAX_LONG) ResourceError("overflow in RightShift");
      LeftShift(x, a, -n);
      return;
   }

   long da = deg(a);
   long i;

   if (da < n) {
      clear(x);
      return;
   }

   if (&x != &a)
      x.rep.SetLength(da-n+1);

   for (i = 0; i <= da-n; i++)
      x.rep[i] = a.rep[i+n];

   if (&x == &a)
      x.rep.SetLength(da-n+1);

   x.normalize();
}

void LeftShift(ZZ_pEX& x, const ZZ_pEX& a, long n)
{
   if (IsZero(a)) {
      clear(x);
      return;
   }

   if (n < 0) {
      if (n < -NTL_MAX_LONG)
         clear(x);
      else
         RightShift(x, a, -n);
      return;
   }

   if (NTL_OVERFLOW(n, 1, 0))
      ResourceError("overflow in LeftShift");

   long m = a.rep.length();

   x.rep.SetLength(m+n);

   long i;
   for (i = m-1; i >= 0; i--)
      x.rep[i+n] = a.rep[i];

   for (i = 0; i < n; i++)
      clear(x.rep[i]);
}

void NewtonInv(ZZ_pEX& c, const ZZ_pEX& a, long e)
{
   ZZ_pE x;

   inv(x, ConstTerm(a));

   if (e == 1) {
      conv(c, x);
      return;
   }

   vec_long E;
   E.SetLength(0);
   append(E, e);
   while (e > 1) {
      e = (e+1)/2;
      append(E, e);
   }

   long L = E.length();

   ZZ_pEX g, g0, g1, g2;

   g.rep.SetMaxLength(E[0]);
   g0.rep.SetMaxLength(E[0]);
   g1.rep.SetMaxLength((3*E[0]+1)/2);
   g2.rep.SetMaxLength(E[0]);

   conv(g, x);

   long i;

   for (i = L-1; i > 0; i--) {
      // lift from E[i] to E[i-1]

      long k = E[i];
      long l = E[i-1]-E[i];

      trunc(g0, a, k+l);

      mul(g1, g0, g);
      RightShift(g1, g1, k);
      trunc(g1, g1, l);

      mul(g2, g1, g);
      trunc(g2, g2, l);
      LeftShift(g2, g2, k);

      sub(g, g, g2);
   }

   c = g;
}

void InvTrunc(ZZ_pEX& c, const ZZ_pEX& a, long e)
{
   if (e < 0) LogicError("InvTrunc: bad args");
   if (e == 0) {
      clear(c);
      return;
   }

   if (NTL_OVERFLOW(e, 1, 0))
      ResourceError("overflow in InvTrunc");

   NewtonInv(c, a, e);
}

const long ZZ_pEX_MOD_PLAIN = 0;
const long ZZ_pEX_MOD_MUL = 1;

void build(ZZ_pEXModulus& F, const ZZ_pEX& f)
{
   long n = deg(f);

   if (n <= 0) LogicError("build(ZZ_pEXModulus,ZZ_pEX): deg(f) <= 0");

   if (NTL_OVERFLOW(n, ZZ_pE::degree(), 0))
      ResourceError("build(ZZ_pEXModulus,ZZ_pEX): overflow");

   F.tracevec.make();

   F.f = f;
   F.n = n;

   if (F.n < ZZ_pE::ModCross()) {
      F.method = ZZ_pEX_MOD_PLAIN;
   }
   else {
      F.method = ZZ_pEX_MOD_MUL;
      ZZ_pEX P1;
      ZZ_pEX P2;

      CopyReverse(P1, f, n);
      InvTrunc(P2, P1, n-1);
      CopyReverse(P1, P2, n-2);
      trunc(F.h0, P1, n-2);
      trunc(F.f0, f, n);
      F.hlc = ConstTerm(P2);
   }
}

ZZ_pEXModulus::ZZ_pEXModulus()
{
   n = -1;
   method = ZZ_pEX_MOD_PLAIN;
}

ZZ_pEXModulus::~ZZ_pEXModulus()
{
}

ZZ_pEXModulus::ZZ_pEXModulus(const ZZ_pEX& ff)
{
   n = -1;
   method = ZZ_pEX_MOD_PLAIN;

   build(*this, ff);
}

void UseMulRem21(ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   ZZ_pEX P1;
   ZZ_pEX P2;

   RightShift(P1, a, F.n);
   mul(P2, P1, F.h0);
   RightShift(P2, P2, F.n-2);
   if (!IsOne(F.hlc)) mul(P1, P1, F.hlc);
   add(P2, P2, P1);
   mul(P1, P2, F.f0);
   trunc(P1, P1, F.n);
   trunc(r, a, F.n);
   sub(r, r, P1);
}

void UseMulDivRem21(ZZ_pEX& q, ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   ZZ_pEX P1;
   ZZ_pEX P2;

   RightShift(P1, a, F.n);
   mul(P2, P1, F.h0);
   RightShift(P2, P2, F.n-2);
   if (!IsOne(F.hlc)) mul(P1, P1, F.hlc);
   add(P2, P2, P1);
   mul(P1, P2, F.f0);
   trunc(P1, P1, F.n);
   trunc(r, a, F.n);
   sub(r, r, P1);
   q = P2;
}

void UseMulDiv21(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   ZZ_pEX P1;
   ZZ_pEX P2;

   RightShift(P1, a, F.n);
   mul(P2, P1, F.h0);
   RightShift(P2, P2, F.n-2);
   if (!IsOne(F.hlc)) mul(P1, P1, F.hlc);
   add(P2, P2, P1);
   q = P2;

}

void rem(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   if (F.method == ZZ_pEX_MOD_PLAIN) {
      PlainRem(x, a, F.f);
      return;
   }

   long da = deg(a);
   long n = F.n;

   if (da <= 2*n-2) {
      UseMulRem21(x, a, F);
      return;
   }

   ZZ_pEX buf(INIT_SIZE, 2*n-1);

   long a_len = da+1;

   while (a_len > 0) {
      long old_buf_len = buf.rep.length();
      long amt = min(2*n-1-old_buf_len, a_len);

      buf.rep.SetLength(old_buf_len+amt);

      long i;

      for (i = old_buf_len+amt-1; i >= amt; i--)
         buf.rep[i] = buf.rep[i-amt];

      for (i = amt-1; i >= 0; i--)
         buf.rep[i] = a.rep[a_len-amt+i];

      buf.normalize();

      UseMulRem21(buf, buf, F);

      a_len -= amt;
   }

   x = buf;
}

void DivRem(ZZ_pEX& q, ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   if (F.method == ZZ_pEX_MOD_PLAIN) {
      PlainDivRem(q, r, a, F.f);
      return;
   }

   long da = deg(a);
   long n = F.n;

   if (da <= 2*n-2) {
      UseMulDivRem21(q, r, a, F);
      return;
   }

   ZZ_pEX buf(INIT_SIZE, 2*n-1);
   ZZ_pEX qbuf(INIT_SIZE, n-1);

   ZZ_pEX qq;
   qq.rep.SetLength(da-n+1);

   long a_len = da+1;
   long q_hi = da-n+1;

   while (a_len > 0) {
      long old_buf_len = buf.rep.length();
      long amt = min(2*n-1-old_buf_len, a_len);

      buf.rep.SetLength(old_buf_len+amt);

      long i;

      for (i = old_buf_len+amt-1; i >= amt; i--)
         buf.rep[i] = buf.rep[i-amt];

      for (i = amt-1; i >= 0; i--)
         buf.rep[i] = a.rep[a_len-amt+i];

      buf.normalize();

      UseMulDivRem21(qbuf, buf, buf, F);
      long dl = qbuf.rep.length();
      a_len = a_len - amt;
      for(i = 0; i < dl; i++)
         qq.rep[a_len+i] = qbuf.rep[i];
      for(i = dl+a_len; i < q_hi; i++)
         clear(qq.rep[i]);
      q_hi = a_len;
   }

   r = buf;

   qq.normalize();
   q = qq;
}

void div(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   if (F.method == ZZ_pEX_MOD_PLAIN) {
      PlainDiv(q, a, F.f);
      return;
   }

   long da = deg(a);
   long n = F.n;

   if (da <= 2*n-2) {
      UseMulDiv21(q, a, F);
      return;
   }

   ZZ_pEX buf(INIT_SIZE, 2*n-1);
   ZZ_pEX qbuf(INIT_SIZE, n-1);

   ZZ_pEX qq;
   qq.rep.SetLength(da-n+1);

   long a_len = da+1;
   long q_hi = da-n+1;

   while (a_len > 0) {
      long old_buf_len = buf.rep.length();
      long amt = min(2*n-1-old_buf_len, a_len);

      buf.rep.SetLength(old_buf_len+amt);

      long i;

      for (i = old_buf_len+amt-1; i >= amt; i--)
         buf.rep[i] = buf.rep[i-amt];

      for (i = amt-1; i >= 0; i--)
         buf.rep[i] = a.rep[a_len-amt+i];

      buf.normalize();

      a_len = a_len - amt;
      if (a_len > 0)
         UseMulDivRem21(qbuf, buf, buf, F);
      else
         UseMulDiv21(qbuf, buf, F);

      long dl = qbuf.rep.length();
      for(i = 0; i < dl; i++)
         qq.rep[a_len+i] = qbuf.rep[i];
      for(i = dl+a_len; i < q_hi; i++)
         clear(qq.rep[i]);
      q_hi = a_len;
   }

   qq.normalize();
   q = qq;
}

void MulMod(ZZ_pEX& c, const ZZ_pEX& a, const ZZ_pEX& b, const ZZ_pEXModulus& F)
{
   if (deg(a) >= F.n || deg(b) >= F.n) LogicError("MulMod: bad args");

   ZZ_pEX t;
   mul(t, a, b);
   rem(c, t, F);
}

void SqrMod(ZZ_pEX& c, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   if (deg(a) >= F.n) LogicError("MulMod: bad args");

   ZZ_pEX t;
   sqr(t, a);
   rem(c, t, F);
}

void UseMulRem(ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b)
{
   ZZ_pEX P1;
   ZZ_pEX P2;

   long da = deg(a);
   long db = deg(b);

   CopyReverse(P1, b, db);
   InvTrunc(P2, P1, da-db+1);
   CopyReverse(P1, P2, da-db);

   RightShift(P2, a, db);
   mul(P2, P1, P2);
   RightShift(P2, P2, da-db);
   mul(P1, P2, b);
   sub(P1, a, P1);

   r = P1;
}

void UseMulDivRem(ZZ_pEX& q, ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b)
{
   ZZ_pEX P1;
   ZZ_pEX P2;

   long da = deg(a);
   long db = deg(b);

   CopyReverse(P1, b, db);
   InvTrunc(P2, P1, da-db+1);
   CopyReverse(P1, P2, da-db);

   RightShift(P2, a, db);
   mul(P2, P1, P2);
   RightShift(P2, P2, da-db);
   mul(P1, P2, b);
   sub(P1, a, P1);

   r = P1;
   q = P2;
}

void UseMulDiv(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_pEX& b)
{
   ZZ_pEX P1;
   ZZ_pEX P2;

   long da = deg(a);
   long db = deg(b);

   CopyReverse(P1, b, db);
   InvTrunc(P2, P1, da-db+1);
   CopyReverse(P1, P2, da-db);

   RightShift(P2, a, db);
   mul(P2, P1, P2);
   RightShift(P2, P2, da-db);

   q = P2;
}

void DivRem(ZZ_pEX& q, ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long sa = a.rep.length();
   long sb = b.rep.length();

   if (sb < ZZ_pE::DivCross() || sa-sb < ZZ_pE::DivCross())
      PlainDivRem(q, r, a, b);
   else if (sa < 4*sb)
      UseMulDivRem(q, r, a, b);
   else {
      ZZ_pEXModulus B;
      build(B, b);
      DivRem(q, r, a, B);
   }
}

void div(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long sa = a.rep.length();
   long sb = b.rep.length();

   if (sb < ZZ_pE::DivCross() || sa-sb < ZZ_pE::DivCross())
      PlainDiv(q, a, b);
   else if (sa < 4*sb)
      UseMulDiv(q, a, b);
   else {
      ZZ_pEXModulus B;
      build(B, b);
      div(q, a, B);
   }
}

void div(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_pE& b)
{
   ZZ_pE T;
   inv(T, b);
   mul(q, a, T);
}

void div(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_p& b)
{
   NTL_ZZ_pRegister(T);
   inv(T, b);
   mul(q, a, T);
}

void div(ZZ_pEX& q, const ZZ_pEX& a, long b)
{
   NTL_ZZ_pRegister(T);
   T = b;
   inv(T, T);
   mul(q, a, T);
}

void rem(ZZ_pEX& r, const ZZ_pEX& a, const ZZ_pEX& b)
{
   long sa = a.rep.length();
   long sb = b.rep.length();

   if (sb < ZZ_pE::DivCross() || sa-sb < ZZ_pE::DivCross())
      PlainRem(r, a, b);
   else if (sa < 4*sb)
      UseMulRem(r, a, b);
   else {
      ZZ_pEXModulus B;
      build(B, b);
      rem(r, a, B);
   }
}

void GCD(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& b)
{
   ZZ_pE t;

   if (IsZero(b))
      x = a;
   else if (IsZero(a))
      x = b;
   else {
      long n = max(deg(a),deg(b)) + 1;
      ZZ_pEX u(INIT_SIZE, n), v(INIT_SIZE, n);

      vec_ZZ_pX tmp;
      SetSize(tmp, n, 2*ZZ_pE::degree());

      u = a;
      v = b;
      do {
         PlainRem(u, u, v, tmp);
         swap(u, v);
      } while (!IsZero(v));

      x = u;
   }

   if (IsZero(x)) return;
   if (IsOne(LeadCoeff(x))) return;

   /* make gcd monic */

   inv(t, LeadCoeff(x));
   mul(x, x, t);
}

void XGCD(ZZ_pEX& d, ZZ_pEX& s, ZZ_pEX& t, const ZZ_pEX& a, const ZZ_pEX& b)
{
   ZZ_pE z;

   if (IsZero(b)) {
      set(s);
      clear(t);
      d = a;
   }
   else if (IsZero(a)) {
      clear(s);
      set(t);
      d = b;
   }
   else {
      long e = max(deg(a), deg(b)) + 1;

      ZZ_pEX temp(INIT_SIZE, e), u(INIT_SIZE, e), v(INIT_SIZE, e),
            u0(INIT_SIZE, e), v0(INIT_SIZE, e),
            u1(INIT_SIZE, e), v1(INIT_SIZE, e),
            u2(INIT_SIZE, e), v2(INIT_SIZE, e), q(INIT_SIZE, e);

      set(u1); clear(v1);
      clear(u2); set(v2);
      u = a; v = b;

      do {
         DivRem(q, u, u, v);
         swap(u, v);
         u0 = u2;
         v0 = v2;
         mul(temp, q, u2);
         sub(u2, u1, temp);
         mul(temp, q, v2);
         sub(v2, v1, temp);
         u1 = u0;
         v1 = v0;
      } while (!IsZero(v));

      d = u;
      s = u1;
      t = v1;
   }

   if (IsZero(d)) return;
   if (IsOne(LeadCoeff(d))) return;

   /* make gcd monic */

   inv(z, LeadCoeff(d));
   mul(d, d, z);
   mul(s, s, z);
   mul(t, t, z);
}

void IterBuild(ZZ_pE* a, long n)
{
   long i, k;
   ZZ_pE b, t;

   if (n <= 0) return;

   negate(a[0], a[0]);

   for (k = 1; k <= n-1; k++) {
      negate(b, a[k]);
      add(a[k], b, a[k-1]);
      for (i = k-1; i >= 1; i--) {
         mul(t, a[i], b);
         add(a[i], t, a[i-1]);
      }
      mul(a[0], a[0], b);
   }
}

void BuildFromRoots(ZZ_pEX& x, const vec_ZZ_pE& a)
{
   long n = a.length();

   if (n == 0) {
      set(x);
      return;
   }

   x.rep.SetMaxLength(n+1);
   x.rep = a;
   IterBuild(&x.rep[0], n);
   x.rep.SetLength(n+1);
   SetCoeff(x, n);
}

void eval(ZZ_pE& b, const ZZ_pEX& f, const ZZ_pE& a)
// does a Horner evaluation
{
   ZZ_pE acc;
   long i;

   clear(acc);
   for (i = deg(f); i >= 0; i--) {
      mul(acc, acc, a);
      add(acc, acc, f.rep[i]);
   }

   b = acc;
}

void eval(vec_ZZ_pE& b, const ZZ_pEX& f, const vec_ZZ_pE& a)
// naive algorithm:  repeats Horner
{
   if (&b == &f.rep) {
      vec_ZZ_pE bb;
      eval(bb, f, a);
      b = bb;
      return;
   }

   long m = a.length();
   b.SetLength(m);
   long i;
   for (i = 0; i < m; i++)
      eval(b[i], f, a[i]);
}

void interpolate(ZZ_pEX& f, const vec_ZZ_pE& a, const vec_ZZ_pE& b)
{
   long m = a.length();
   if (b.length() != m) LogicError("interpolate: vector length mismatch");

   if (m == 0) {
      clear(f);
      return;
   }

   vec_ZZ_pE prod;
   prod = a;

   ZZ_pE t1, t2;

   long k, i;

   vec_ZZ_pE res;
   res.SetLength(m);

   for (k = 0; k < m; k++) {

      const ZZ_pE& aa = a[k];

      set(t1);
      for (i = k-1; i >= 0; i--) {
         mul(t1, t1, aa);
         add(t1, t1, prod[i]);
      }

      clear(t2);
      for (i = k-1; i >= 0; i--) {
         mul(t2, t2, aa);
         add(t2, t2, res[i]);
      }

      inv(t1, t1);
      sub(t2, b[k], t2);
      mul(t1, t1, t2);

      for (i = 0; i < k; i++) {
         mul(t2, prod[i], t1);
         add(res[i], res[i], t2);
      }

      res[k] = t1;

      if (k < m-1) {
         if (k == 0)
            negate(prod[0], prod[0]);
         else {
            negate(t1, a[k]);
            add(prod[k], t1, prod[k-1]);
            for (i = k-1; i >= 1; i--) {
               mul(t2, prod[i], t1);
               add(prod[i], t2, prod[i-1]);
            }
            mul(prod[0], prod[0], t1);
         }
      }
   }

   while (m > 0 && IsZero(res[m-1])) m--;
   res.SetLength(m);
   f.rep = res;
}

void InnerProduct(ZZ_pEX& x, const vec_ZZ_pE& v, long low, long high,
                   const vec_ZZ_pEX& H, long n, vec_ZZ_pX& t)
{
   ZZ_pX s;
   long i, j;

   for (j = 0; j < n; j++)
      clear(t[j]);

   high = min(high, v.length()-1);
   for (i = low; i <= high; i++) {
      const vec_ZZ_pE& h = H[i-low].rep;
      long m = h.length();
      const ZZ_pX& w = rep(v[i]);

      for (j = 0; j < m; j++) {
         mul(s, w, rep(h[j]));
         add(t[j], t[j], s);
      }
   }

   x.rep.SetLength(n);
   for (j = 0; j < n; j++)
      conv(x.rep[j], t[j]);
   x.normalize();
}

void CompMod(ZZ_pEX& x, const ZZ_pEX& g, const ZZ_pEXArgument& A,
             const ZZ_pEXModulus& F)
{
   if (deg(g) <= 0) {
      x = g;
      return;
   }

   ZZ_pEX s, t;
   vec_ZZ_pX scratch;
   SetSize(scratch, deg(F), 2*ZZ_pE::degree());

   long m = A.H.length() - 1;
   long l = ((g.rep.length()+m-1)/m) - 1;

   const ZZ_pEX& M = A.H[m];

   InnerProduct(t, g.rep, l*m, l*m + m - 1, A.H, F.n, scratch);
   for (long i = l-1; i >= 0; i--) {
      InnerProduct(s, g.rep, i*m, i*m + m - 1, A.H, F.n, scratch);
      MulMod(t, t, M, F);
      add(t, t, s);
   }

   x = t;
}

void build(ZZ_pEXArgument& A, const ZZ_pEX& h, const ZZ_pEXModulus& F, long m)
{
   long i;

   if (m <= 0 || deg(h) >= F.n)
      LogicError("build: bad args");

   if (m > F.n) m = F.n;

   if (ZZ_pEXArgBound > 0) {
      double sz = ZZ_p::storage();
      sz = sz*ZZ_pE::degree();
      sz = sz + NTL_VECTOR_HEADER_SIZE + sizeof(vec_ZZ_p);
      sz = sz*F.n;
      sz = sz + NTL_VECTOR_HEADER_SIZE + sizeof(vec_ZZ_pE);
      sz = sz/1024;
      m = min(m, long(ZZ_pEXArgBound/sz));
      m = max(m, 1);
   }

   A.H.SetLength(m+1);

   set(A.H[0]);
   A.H[1] = h;
   for (i = 2; i <= m; i++)
      MulMod(A.H[i], A.H[i-1], h, F);
}

NTL_CHEAP_THREAD_LOCAL long ZZ_pEXArgBound = 0;

void CompMod(ZZ_pEX& x, const ZZ_pEX& g, const ZZ_pEX& h, const ZZ_pEXModulus& F)
   // x = g(h) mod f
{
   long m = SqrRoot(g.rep.length());

   if (m == 0) {
      clear(x);
      return;
   }

   ZZ_pEXArgument A;

   build(A, h, F, m);

   CompMod(x, g, A, F);
}

void Comp2Mod(ZZ_pEX& x1, ZZ_pEX& x2, const ZZ_pEX& g1, const ZZ_pEX& g2,
              const ZZ_pEX& h, const ZZ_pEXModulus& F)

{
   long m = SqrRoot(g1.rep.length() + g2.rep.length());

   if (m == 0) {
      clear(x1);
      clear(x2);
      return;
   }

   ZZ_pEXArgument A;

   build(A, h, F, m);

   ZZ_pEX xx1, xx2;

   CompMod(xx1, g1, A, F);
   CompMod(xx2, g2, A, F);

   x1 = xx1;
   x2 = xx2;
}

void Comp3Mod(ZZ_pEX& x1, ZZ_pEX& x2, ZZ_pEX& x3,
              const ZZ_pEX& g1, const ZZ_pEX& g2, const ZZ_pEX& g3,
              const ZZ_pEX& h, const ZZ_pEXModulus& F)

{
   long m = SqrRoot(g1.rep.length() + g2.rep.length() + g3.rep.length());

   if (m == 0) {
      clear(x1);
      clear(x2);
      clear(x3);
      return;
   }

   ZZ_pEXArgument A;

   build(A, h, F, m);

   ZZ_pEX xx1, xx2, xx3;

   CompMod(xx1, g1, A, F);
   CompMod(xx2, g2, A, F);
   CompMod(xx3, g3, A, F);

   x1 = xx1;
   x2 = xx2;
   x3 = xx3;
}

void build(ZZ_pEXTransMultiplier& B, const ZZ_pEX& b, const ZZ_pEXModulus& F)
{
   long db = deg(b);

   if (db >= F.n) LogicError("build TransMultiplier: bad args");

   ZZ_pEX t;

   LeftShift(t, b, F.n-1);
   div(t, t, F);

   // we optimize for low degree b

   long d;

   d = deg(t);
   if (d < 0)
      B.shamt_fbi = 0;
   else
      B.shamt_fbi = F.n-2 - d;

   CopyReverse(B.fbi, t, d);

   // The following code optimizes the case when
   // f = X^n + low degree poly

   trunc(t, F.f, F.n);
   d = deg(t);
   if (d < 0)
      B.shamt = 0;
   else
      B.shamt = d;

   CopyReverse(B.f0, t, d);

   if (db < 0)
      B.shamt_b = 0;
   else
      B.shamt_b = db;

   CopyReverse(B.b, b, db);
}

void TransMulMod(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEXTransMultiplier& B,
               const ZZ_pEXModulus& F)
{
   if (deg(a) >= F.n) LogicError("TransMulMod: bad args");

   ZZ_pEX t1, t2;

   mul(t1, a, B.b);
   RightShift(t1, t1, B.shamt_b);

   mul(t2, a, B.f0);
   RightShift(t2, t2, B.shamt);
   trunc(t2, t2, F.n-1);

   mul(t2, t2, B.fbi);
   if (B.shamt_fbi > 0) LeftShift(t2, t2, B.shamt_fbi);
   trunc(t2, t2, F.n-1);
   LeftShift(t2, t2, 1);

   sub(x, t1, t2);
}

void ShiftSub(ZZ_pEX& U, const ZZ_pEX& V, long n)
// assumes input does not alias output
{
   if (IsZero(V))
      return;

   long du = deg(U);
   long dv = deg(V);

   long d = max(du, n+dv);

   U.rep.SetLength(d+1);
   long i;

   for (i = du+1; i <= d; i++)
      clear(U.rep[i]);

   for (i = 0; i <= dv; i++)
      sub(U.rep[i+n], U.rep[i+n], V.rep[i]);

   U.normalize();
}

void UpdateMap(vec_ZZ_pE& x, const vec_ZZ_pE& a,
         const ZZ_pEXTransMultiplier& B, const ZZ_pEXModulus& F)
{
   ZZ_pEX xx;
   TransMulMod(xx, to_ZZ_pEX(a), B, F);
   x = xx.rep;
}

static
void ProjectPowers(vec_ZZ_pE& x, const ZZ_pEX& a, long k,
                   const ZZ_pEXArgument& H, const ZZ_pEXModulus& F)
{
   if (k < 0 || deg(a) >= F.n)
      LogicError("ProjectPowers: bad args");

   if (NTL_OVERFLOW(k, 1, 0))
      ResourceError("ProjectPowers: excessive args");

   long m = H.H.length()-1;
   long l = (k+m-1)/m - 1;

   ZZ_pEXTransMultiplier M;
   build(M, H.H[m], F);

   ZZ_pEX s;
   s = a;

   x.SetLength(k);

   long i;

   for (i = 0; i <= l; i++) {
      long m1 = min(m, k-i*m);
      for (long j = 0; j < m1; j++)
         InnerProduct(x[i*m+j], H.H[j].rep, s.rep);
      if (i < l)
         TransMulMod(s, s, M, F);
   }
}

static
void ProjectPowers(vec_ZZ_pE& x, const ZZ_pEX& a, long k, const ZZ_pEX& h,
                   const ZZ_pEXModulus& F)
{
   if (k < 0 || deg(a) >= F.n || deg(h) >= F.n)
      LogicError("ProjectPowers: bad args");

   if (k == 0) {
      x.SetLength(0);;
      return;
   }

   long m = SqrRoot(k);

   ZZ_pEXArgument H;
   build(H, h, F, m);

   ProjectPowers(x, a, k, H, F);
}

void ProjectPowers(vec_ZZ_pE& x, const vec_ZZ_pE& a, long k,
                   const ZZ_pEXArgument& H, const ZZ_pEXModulus& F)
{
   ProjectPowers(x, to_ZZ_pEX(a), k, H, F);
}

void ProjectPowers(vec_ZZ_pE& x, const vec_ZZ_pE& a, long k,
                   const ZZ_pEX& h, const ZZ_pEXModulus& F)
{
   ProjectPowers(x, to_ZZ_pEX(a), k, h, F);
}

void BerlekampMassey(ZZ_pEX& h, const vec_ZZ_pE& a, long m)
{
   ZZ_pEX Lambda, Sigma, Temp;
   long L;
   ZZ_pE Delta, Delta1, t1;
   long shamt;

   // cerr << "*** " << m << "\n";

   Lambda.SetMaxLength(m+1);
   Sigma.SetMaxLength(m+1);
   Temp.SetMaxLength(m+1);

   L = 0;
   set(Lambda);
   clear(Sigma);
   set(Delta);
   shamt = 0;

   long i, r, dl;

   for (r = 1; r <= 2*m; r++) {
      // cerr << r << "--";
      clear(Delta1);
      dl = deg(Lambda);
      for (i = 0; i <= dl; i++) {
         mul(t1, Lambda.rep[i], a[r-i-1]);
         add(Delta1, Delta1, t1);
      }

      if (IsZero(Delta1)) {
         shamt++;
         // cerr << "case 1: " << deg(Lambda) << " " << deg(Sigma) << " " << shamt << "\n";
      }
      else if (2*L < r) {
         div(t1, Delta1, Delta);
         mul(Temp, Sigma, t1);
         Sigma = Lambda;
         ShiftSub(Lambda, Temp, shamt+1);
         shamt = 0;
         L = r-L;
         Delta = Delta1;
         // cerr << "case 2: " << deg(Lambda) << " " << deg(Sigma) << " " << shamt << "\n";
      }
      else {
         shamt++;
         div(t1, Delta1, Delta);
         mul(Temp, Sigma, t1);
         ShiftSub(Lambda, Temp, shamt);
         // cerr << "case 3: " << deg(Lambda) << " " << deg(Sigma) << " " << shamt << "\n";
      }
   }

   // cerr << "finished: " << L << " " << deg(Lambda) << "\n";

   dl = deg(Lambda);
   h.rep.SetLength(L + 1);

   for (i = 0; i < L - dl; i++)
      clear(h.rep[i]);

   for (i = L - dl; i <= L; i++)
      h.rep[i] = Lambda.rep[L - i];
}

void MinPolySeq(ZZ_pEX& h, const vec_ZZ_pE& a, long m)
{
   if (m < 0 || NTL_OVERFLOW(m, 1, 0)) LogicError("MinPoly: bad args");
   if (a.length() < 2*m) LogicError("MinPoly: sequence too short");

   BerlekampMassey(h, a, m);
}

void DoMinPolyMod(ZZ_pEX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F, long m,
               const ZZ_pEX& R)
{
   vec_ZZ_pE x;

   ProjectPowers(x, R, 2*m, g, F);
   MinPolySeq(h, x, m);
}

void ProbMinPolyMod(ZZ_pEX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F, long m)
{
   long n = F.n;
   if (m < 1 || m > n) LogicError("ProbMinPoly: bad args");

   ZZ_pEX R;
   random(R, n);

   DoMinPolyMod(h, g, F, m, R);
}

void ProbMinPolyMod(ZZ_pEX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F)
{
   ProbMinPolyMod(h, g, F, F.n);
}

void MinPolyMod(ZZ_pEX& hh, const ZZ_pEX& g, const ZZ_pEXModulus& F, long m)
{
   ZZ_pEX h, h1;
   long n = F.n;
   if (m < 1 || m > n) LogicError("MinPoly: bad args");

   /* probabilistically compute min-poly */

   ProbMinPolyMod(h, g, F, m);
   if (deg(h) == m) { hh = h; return; }
   CompMod(h1, h, g, F);
   if (IsZero(h1)) { hh = h; return; }

   /* not completely successful...must iterate */

   ZZ_pEX h2, h3;
   ZZ_pEX R;
   ZZ_pEXTransMultiplier H1;

   for (;;) {
      random(R, n);
      build(H1, h1, F);
      TransMulMod(R, R, H1, F);
      DoMinPolyMod(h2, g, F, m-deg(h), R);

      mul(h, h, h2);
      if (deg(h) == m) { hh = h; return; }
      CompMod(h3, h2, g, F);
      MulMod(h1, h3, h1, F);
      if (IsZero(h1)) { hh = h; return; }
   }
}

void IrredPolyMod(ZZ_pEX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F, long m)
{
   if (m < 1 || m > F.n) LogicError("IrredPoly: bad args");

   ZZ_pEX R;
   set(R);

   DoMinPolyMod(h, g, F, m, R);
}

void IrredPolyMod(ZZ_pEX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F)
{
   IrredPolyMod(h, g, F, F.n);
}

void MinPolyMod(ZZ_pEX& hh, const ZZ_pEX& g, const ZZ_pEXModulus& F)
{
   MinPolyMod(hh, g, F, F.n);
}

void diff(ZZ_pEX& x, const ZZ_pEX& a)
{
   long n = deg(a);
   long i;

   if (n <= 0) {
      clear(x);
      return;
   }

   if (&x != &a)
      x.rep.SetLength(n);

   for (i = 0; i <= n-1; i++) {
      mul(x.rep[i], a.rep[i+1], i+1);
   }

   if (&x == &a)
      x.rep.SetLength(n);

   x.normalize();
}

void MakeMonic(ZZ_pEX& x)
{
   if (IsZero(x))
      return;

   if (IsOne(LeadCoeff(x)))
      return;

   ZZ_pE t;

   inv(t, LeadCoeff(x));
   mul(x, x, t);
}

long divide(ZZ_pEX& q, const ZZ_pEX& a, const ZZ_pEX& b)
{
   if (IsZero(b)) {
      if (IsZero(a)) {
         clear(q);
         return 1;
      }
      else
         return 0;
   }

   ZZ_pEX lq, r;
   DivRem(lq, r, a, b);
   if (!IsZero(r)) return 0;
   q = lq;
   return 1;
}

long divide(const ZZ_pEX& a, const ZZ_pEX& b)
{
   if (IsZero(b)) return IsZero(a);
   ZZ_pEX lq, r;
   DivRem(lq, r, a, b);
   if (!IsZero(r)) return 0;
   return 1;
}

static
long OptWinSize(long n)
// finds k that minimizes n/(k+1) + 2^{k-1}

{
   long k;
   double v, v_new;

   v = n/2.0 + 1.0;
   k = 1;

   for (;;) {
      v_new = n/(double(k+2)) + double(1L << k);
      if (v_new >= v) break;
      v = v_new;
      k++;
   }

   return k;
}

void PowerMod(ZZ_pEX& h, const ZZ_pEX& g, const ZZ& e, const ZZ_pEXModulus& F)
// h = g^e mod f using "sliding window" algorithm
{
   if (deg(g) >= F.n) LogicError("PowerMod: bad args");

   if (e == 0) {
      set(h);
      return;
   }

   if (e == 1) {
      h = g;
      return;
   }

   if (e == -1) {
      InvMod(h, g, F);
      return;
   }

   if (e == 2) {
      SqrMod(h, g, F);
      return;
   }

   if (e == -2) {
      SqrMod(h, g, F);
      InvMod(h, h, F);
      return;
   }

   long n = NumBits(e);

   ZZ_pEX res;
   res.SetMaxLength(F.n);
   set(res);

   long i;

   if (n < 16) {
      // plain square-and-multiply algorithm

      for (i = n - 1; i >= 0; i--) {
         SqrMod(res, res, F);
         if (bit(e, i))
            MulMod(res, res, g, F);
      }

      if (e < 0) InvMod(res, res, F);

      h = res;
      return;
   }

   long k = OptWinSize(n);
   k = min(k, 3);

   vec_ZZ_pEX v;

   v.SetLength(1L << (k-1));

   v[0] = g;

   if (k > 1) {
      ZZ_pEX t;
      SqrMod(t, g, F);

      for (i = 1; i < (1L << (k-1)); i++)
         MulMod(v[i], v[i-1], t, F);
   }

   long val;
   long cnt;
   long m;

   val = 0;
   for (i = n-1; i >= 0; i--) {
      val = (val << 1) | bit(e, i);
      if (val == 0)
         SqrMod(res, res, F);
      else if (val >= (1L << (k-1)) || i == 0) {
         cnt = 0;
         while ((val & 1) == 0) {
            val = val >> 1;
            cnt++;
         }

         m = val;
         while (m > 0) {
            SqrMod(res, res, F);
            m = m >> 1;
         }

         MulMod(res, res, v[val >> 1], F);

         while (cnt > 0) {
            SqrMod(res, res, F);
            cnt--;
         }

         val = 0;
      }
   }

   if (e < 0) InvMod(res, res, F);

   h = res;
}

void InvMod(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& f)
{
   if (deg(a) >= deg(f) || deg(f) == 0) LogicError("InvMod: bad args");

   ZZ_pEX d, xx, t;

   XGCD(d, xx, t, a, f);
   if (!IsOne(d))
      InvModError("ZZ_pEX InvMod: can't compute multiplicative inverse");

   x = xx;
}

long InvModStatus(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& f)
{
   if (deg(a) >= deg(f) || deg(f) == 0) LogicError("InvModStatus: bad args");
   ZZ_pEX d, t;

   XGCD(d, x, t, a, f);
   if (!IsOne(d)) {
      x = d;
      return 1;
   }
   else
      return 0;
}

void MulMod(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& b, const ZZ_pEX& f)
{
   if (deg(a) >= deg(f) || deg(b) >= deg(f) || deg(f) == 0)
      LogicError("MulMod: bad args");

   ZZ_pEX t;

   mul(t, a, b);
   rem(x, t, f);
}

void SqrMod(ZZ_pEX& x, const ZZ_pEX& a, const ZZ_pEX& f)
{
   if (deg(a) >= deg(f) || deg(f) == 0) LogicError("SqrMod: bad args");

   ZZ_pEX t;

   sqr(t, a);
   rem(x, t, f);
}

void PowerXMod(ZZ_pEX& hh, const ZZ& e, const ZZ_pEXModulus& F)
{
   if (F.n < 0) LogicError("PowerXMod: uninitialized modulus");

   if (IsZero(e)) {
      set(hh);
      return;
   }

   long n = NumBits(e);
   long i;

   ZZ_pEX h;

   h.SetMaxLength(F.n);
   set(h);

   for (i = n - 1; i >= 0; i--) {
      SqrMod(h, h, F);
      if (bit(e, i))
         MulByXMod(h, h, F.f);
   }

   if (e < 0) InvMod(h, h, F);

   hh = h;
}

void reverse(ZZ_pEX& x, const ZZ_pEX& a, long hi)
{
   if (hi < 0) { clear(x); return; }
   if (NTL_OVERFLOW(hi, 1, 0))
      ResourceError("overflow in reverse");

   if (&x == &a) {
      ZZ_pEX tmp;
      CopyReverse(tmp, a, hi);
      x = tmp;
   }
   else
      CopyReverse(x, a, hi);
}

void power(ZZ_pEX& x, const ZZ_pEX& a, long e)
{
   if (e < 0) {
      ArithmeticError("power: negative exponent");
   }

   if (e == 0) {
      x = 1;
      return;
   }

   if (a == 0 || a == 1) {
      x = a;
      return;
   }

   long da = deg(a);

   if (da == 0) {
      x = power(ConstTerm(a), e);
      return;
   }

   if (da > (NTL_MAX_LONG-1)/e)
      ResourceError("overflow in power");

   ZZ_pEX res;
   res.SetMaxLength(da*e + 1);
   res = 1;

   long k = NumBits(e);
   long i;

   for (i = k - 1; i >= 0; i--) {
      sqr(res, res);
      if (bit(e, i))
         mul(res, res, a);
   }

   x = res;
}

static
void FastTraceVec(vec_ZZ_pE& S, const ZZ_pEXModulus& f)
{
   long n = deg(f);

   ZZ_pEX x = reverse(-LeftShift(reverse(diff(reverse(f)), n-1), n-1)/f, n-1);

   S.SetLength(n);
   S[0] = n;

   long i;
   for (i = 1; i < n; i++)
      S[i] = coeff(x, i);
}

void PlainTraceVec(vec_ZZ_pE& S, const ZZ_pEX& ff)
{
   if (deg(ff) <= 0)
      LogicError("TraceVec: bad args");

   ZZ_pEX f;
   f = ff;

   MakeMonic(f);

   long n = deg(f);

   S.SetLength(n);

   if (n == 0)
      return;

   long k, i;
   ZZ_pX acc, t;
   ZZ_pE t1;

   S[0] = n;

   for (k = 1; k < n; k++) {
      mul(acc, rep(f.rep[n-k]), k);

      for (i = 1; i < k; i++) {
         mul(t, rep(f.rep[n-i]), rep(S[k-i]));
         add(acc, acc, t);
      }

      conv(t1, acc);
      negate(S[k], t1);
   }
}

void TraceVec(vec_ZZ_pE& S, const ZZ_pEX& f)
{
   if (deg(f) < ZZ_pE::DivCross())
      PlainTraceVec(S, f);
   else
      FastTraceVec(S, f);
}

static

void ComputeTraceVec(vec_ZZ_pE& S, const ZZ_pEXModulus& F)
{
   if (F.method == ZZ_pEX_MOD_PLAIN) {
      PlainTraceVec(S, F.f);
   }
   else {
      FastTraceVec(S, F);
   }
}

void TraceMod(ZZ_pE& x, const ZZ_pEX& a, const ZZ_pEXModulus& F)
{
   long n = F.n;

   if (deg(a) >= n)
      LogicError("trace: bad args");

   do { // NOTE: thread safe lazy init
      Lazy<vec_ZZ_pE>::Builder builder(F.tracevec.val());
      if (!builder()) break;
      UniquePtr<vec_ZZ_pE> p;
      p.make();
      ComputeTraceVec(*p, F);
      builder.move(p);
   } while (0);

   InnerProduct(x, a.rep, *F.tracevec.val());
}

void TraceMod(ZZ_pE& x, const ZZ_pEX& a, const ZZ_pEX& f)
{
   if (deg(a) >= deg(f) || deg(f) <= 0)
      LogicError("trace: bad args");

   project(x, TraceVec(f), a);
}

void PlainResultant(ZZ_pE& rres, const ZZ_pEX& a, const ZZ_pEX& b)
{
   ZZ_pE res;

   if (IsZero(a) || IsZero(b))
      clear(res);
   else if (deg(a) == 0 && deg(b) == 0)
      set(res);
   else {
      long d0, d1, d2;
      ZZ_pE lc;
      set(res);

      long n = max(deg(a),deg(b)) + 1;
      ZZ_pEX u(INIT_SIZE, n), v(INIT_SIZE, n);
      vec_ZZ_pX tmp;
      SetSize(tmp, n, 2*ZZ_pE::degree());

      u = a;
      v = b;

      for (;;) {
         d0 = deg(u);
         d1 = deg(v);
         lc = LeadCoeff(v);

         PlainRem(u, u, v, tmp);
         swap(u, v);

         d2 = deg(v);
         if (d2 >= 0) {
            power(lc, lc, d0-d2);
            mul(res, res, lc);
            if (d0 & d1 & 1) negate(res, res);
         }
         else {
            if (d1 == 0) {
               power(lc, lc, d0);
               mul(res, res, lc);
            }
            else
               clear(res);

            break;
         }
      }
   }
   rres = res;
}

void resultant(ZZ_pE& rres, const ZZ_pEX& a, const ZZ_pEX& b)
{
   PlainResultant(rres, a, b);
}

void NormMod(ZZ_pE& x, const ZZ_pEX& a, const ZZ_pEX& f)
{
   if (deg(f) <= 0 || deg(a) >= deg(f))
      LogicError("norm: bad args");

   if (IsZero(a)) {
      clear(x);
      return;
   }

   ZZ_pE t;
   resultant(t, f, a);
   if (!IsOne(LeadCoeff(f))) {
      ZZ_pE t1;
      power(t1, LeadCoeff(f), deg(a));
      inv(t1, t1);
      mul(t, t, t1);
   }

   x = t;
}

// tower stuff...

void InnerProduct(ZZ_pEX& x, const vec_ZZ_p& v, long low, long high,
                   const vec_ZZ_pEX& H, long n, vec_ZZ_pE& t)
{
   ZZ_pE s;
   long i, j;

   for (j = 0; j < n; j++)
      clear(t[j]);

   high = min(high, v.length()-1);
   for (i = low; i <= high; i++) {
      const vec_ZZ_pE& h = H[i-low].rep;
      long m = h.length();
      const ZZ_p& w = v[i];

      for (j = 0; j < m; j++) {
         mul(s, h[j], w);
         add(t[j], t[j], s);
      }
   }

   x.rep.SetLength(n);
   for (j = 0; j < n; j++)
      x.rep[j] = t[j];

   x.normalize();
}

void CompTower(ZZ_pEX& x, const ZZ_pX& g, const ZZ_pEXArgument& A,
             const ZZ_pEXModulus& F)
{
   if (deg(g) <= 0) {
      conv(x, g);
      return;
   }

   ZZ_pEX s, t;
   vec_ZZ_pE scratch;
   scratch.SetLength(deg(F));

   long m = A.H.length() - 1;
   long l = ((g.rep.length()+m-1)/m) - 1;

   const ZZ_pEX& M = A.H[m];

   InnerProduct(t, g.rep, l*m, l*m + m - 1, A.H, F.n, scratch);
   for (long i = l-1; i >= 0; i--) {
      InnerProduct(s, g.rep, i*m, i*m + m - 1, A.H, F.n, scratch);
      MulMod(t, t, M, F);
      add(t, t, s);
   }
   x = t;
}

void CompTower(ZZ_pEX& x, const ZZ_pX& g, const ZZ_pEX& h,
             const ZZ_pEXModulus& F)
   // x = g(h) mod f
{
   long m = SqrRoot(g.rep.length());

   if (m == 0) {
      clear(x);
      return;
   }

   ZZ_pEXArgument A;

   build(A, h, F, m);

   CompTower(x, g, A, F);
}

void PrepareProjection(vec_vec_ZZ_p& tt, const vec_ZZ_pE& s,
                       const vec_ZZ_p& proj)
{
   long l = s.length();
   tt.SetLength(l);

   ZZ_pXMultiplier M;
   long i;

   for (i = 0; i < l; i++) {
      build(M, rep(s[i]), ZZ_pE::modulus());
      UpdateMap(tt[i], proj, M, ZZ_pE::modulus());
   }
}

void ProjectedInnerProduct(ZZ_p& x, const vec_ZZ_pE& a,
                           const vec_vec_ZZ_p& b)
{
   long n = min(a.length(), b.length());

   ZZ_p t, res;

   res = 0;

   long i;
   for (i = 0; i < n; i++) {
      project(t, b[i], rep(a[i]));
      res += t;
   }

   x = res;
}

void PrecomputeProj(vec_ZZ_p& proj, const ZZ_pX& f)
{
   long n = deg(f);

   if (n <= 0) LogicError("PrecomputeProj: bad args");

   if (ConstTerm(f) != 0) {
      proj.SetLength(1);
      proj[0] = 1;
   }
   else {
      proj.SetLength(n);
      clear(proj);
      proj[n-1] = 1;
   }
}

void ProjectPowersTower(vec_ZZ_p& x, const vec_ZZ_pE& a, long k,
                   const ZZ_pEXArgument& H, const ZZ_pEXModulus& F,
                   const vec_ZZ_p& proj)

{
   long n = F.n;

   if (a.length() > n || k < 0)
      LogicError("ProjectPowers: bad args");
   if (NTL_OVERFLOW(k, 1, 0))
      ResourceError("ProjectPowers: excessive args");

   long m = H.H.length()-1;
   long l = (k+m-1)/m - 1;

   ZZ_pEXTransMultiplier M;
   build(M, H.H[m], F);

   vec_ZZ_pE s(INIT_SIZE, n);
   s = a;

   x.SetLength(k);

   vec_vec_ZZ_p tt;

   for (long i = 0; i <= l; i++) {
      long m1 = min(m, k-i*m);
      ZZ_p* w = &x[i*m];

      PrepareProjection(tt, s, proj);

      for (long j = 0; j < m1; j++)
         ProjectedInnerProduct(w[j], H.H[j].rep, tt);
      if (i < l)
         UpdateMap(s, s, M, F);
   }
}

void ProjectPowersTower(vec_ZZ_p& x, const vec_ZZ_pE& a, long k,
                   const ZZ_pEX& h, const ZZ_pEXModulus& F,
                   const vec_ZZ_p& proj)

{
   if (a.length() > F.n || k < 0) LogicError("ProjectPowers: bad args");

   if (k == 0) {
      x.SetLength(0);
      return;
   }

   long m = SqrRoot(k);

   ZZ_pEXArgument H;

   build(H, h, F, m);
   ProjectPowersTower(x, a, k, H, F, proj);
}

void DoMinPolyTower(ZZ_pX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F, long m,
               const vec_ZZ_pE& R, const vec_ZZ_p& proj)
{
   vec_ZZ_p x;

   ProjectPowersTower(x, R, 2*m, g, F, proj);

   MinPolySeq(h, x, m);
}

void ProbMinPolyTower(ZZ_pX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F,
                      long m)
{
   long n = F.n;
   if (m < 1 || m > n*ZZ_pE::degree())
      LogicError("MinPoly: bad args");

   vec_ZZ_pE R;
   R.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      random(R[i]);

   vec_ZZ_p proj;
   PrecomputeProj(proj, ZZ_pE::modulus());

   DoMinPolyTower(h, g, F, m, R, proj);
}

void ProbMinPolyTower(ZZ_pX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F,
                      long m, const vec_ZZ_p& proj)
{
   long n = F.n;
   if (m < 1 || m > n*ZZ_pE::degree())
      LogicError("MinPoly: bad args");

   vec_ZZ_pE R;
   R.SetLength(n);
   long i;
   for (i = 0; i < n; i++)
      random(R[i]);

   DoMinPolyTower(h, g, F, m, R, proj);
}

void MinPolyTower(ZZ_pX& hh, const ZZ_pEX& g, const ZZ_pEXModulus& F, long m)
{
   ZZ_pX h;
   ZZ_pEX h1;
   long n = F.n;

   if (m < 1 || m > n*ZZ_pE::degree())
      LogicError("MinPoly: bad args");

   vec_ZZ_p proj;
   PrecomputeProj(proj, ZZ_pE::modulus());

   /* probabilistically compute min-poly */

   ProbMinPolyTower(h, g, F, m, proj);
   if (deg(h) == m) { hh = h; return; }
   CompTower(h1, h, g, F);
   if (IsZero(h1)) { hh = h; return; }

   /* not completely successful...must iterate */

   long i;

   ZZ_pX h2;
   ZZ_pEX h3;
   vec_ZZ_pE R;
   ZZ_pEXTransMultiplier H1;

   for (;;) {
      R.SetLength(n);
      for (i = 0; i < n; i++) random(R[i]);
      build(H1, h1, F);
      UpdateMap(R, R, H1, F);
      DoMinPolyTower(h2, g, F, m-deg(h), R, proj);

      mul(h, h, h2);
      if (deg(h) == m) { hh = h; return; }
      CompTower(h3, h2, g, F);
      MulMod(h1, h3, h1, F);
      if (IsZero(h1)) { hh = h; return; }
   }
}

void IrredPolyTower(ZZ_pX& h, const ZZ_pEX& g, const ZZ_pEXModulus& F, long m)
{
   if (m < 1 || m > deg(F)*ZZ_pE::degree())
      LogicError("IrredPoly: bad args");

   vec_ZZ_pE R;
   R.SetLength(1);
   R[0] = 1;

   vec_ZZ_p proj;
   proj.SetLength(1);
   proj[0] = 1;

   DoMinPolyTower(h, g, F, m, R, proj);
}

NTL_END_IMPL


// Created by Dinies on 19/11/2018.
#pragma once
#include <unistd.h>
#include <Eigen/Core>
#include "opencv2/opencv.hpp"

namespace MultiRobot{

  typedef cv::Mat_< cv::Vec3b > RGBImage;

  class Drawer {

    private:
      double m_scale;
    public:
      Drawer(const double scale);

      void drawHollowPoligon(
          const RGBImage &t_drawing,
          const std::vector< cv::Point2d> &t_consecPoints,
          const cv::Scalar &t_color );

      void drawSegmentSeries(
          const RGBImage &t_drawing,
          const std::vector< cv::Point2d> &t_consecPoints,
          const cv::Scalar &t_color );

      void drawPatch(
          RGBImage &t_drawing,
          const cv::Point2d &t_point,
          const cv::Scalar &t_color);

      void drawLine(
          const RGBImage &t_drawing,
          const cv::Point2d &t_firstP,
          const cv::Point2d &t_secondP,
          const cv::Scalar &t_color);

      cv::Point2d convertFromWorldToImg(
          const RGBImage &t_drawing,
          const cv::Point2d &t_point);

      cv::Point2d bringPointInsideBoundaries(
          const RGBImage &t_drawing,
          const cv::Point2d &t_insidePoint,
          const cv::Point2d &t_outsidePoint);

      bool isInsideBoundaries(
          const RGBImage &t_drawing,
          const cv::Point2d &t_point);

      double crossProd2D(
          const Eigen::Vector2d &v1,
          const Eigen::Vector2d &v2);

      bool findIntersectionBetweenSegments(
          const cv::Point2d &p1,
          const cv::Point2d &p2,
          const cv::Point2d &q1,
          const cv::Point2d &q2,
          cv::Point2d &result);

  };
}


#include "../../src/rf/include/RfMetricInterface.h"
#include "MatcherOrTools.h"
#include <RfAlgorithmInterface.h>
#include <boost/log/attributes/constant.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/math/special_functions/factorials.hpp>
#include <catch2/catch.hpp>
#include <filesystem>
#include <iostream>
#include <ortools/linear_solver/linear_solver.h>
#include <random>
#include <rf/helpers/Util.h>
static void BasicExample();
TEST_CASE("execute or-tools example", "[OR_TOOLS]") {
	// forward declare to put function at end of file
	BasicExample();
}
TEST_CASE("perform matching of specific dst matrix", "[OR_TOOLS]") {
	RectMatrix<double> dst_mtx = Util::parse_mtx_from_r("/rf_metrics/smallmtx", '\n', ' ');
	// maybe only correct results when elements are applied with log2(el)? -> No
	//	for(size_t row = 0; row < dst_mtx.size(); ++row){
	//		for(size_t col = 0; col <= row; ++col){
	//			dst_mtx.set_at(row, col, std::log2(dst_mtx.at(row,col)));
	//		}
	//	}
	MatcherOrTools matcher;
	RfAlgorithmInterface::SplitScores scores(std::move(dst_mtx));
	double res = matcher.solve(scores);
	REQUIRE(res > 0);
	// std::stringstream out;
	//	for (size_t i = 0; i < res_matching.size(); ++i) {
	//		out << res_matching[i] << ", ";
	//	}
	//	std::cout << out.str() << std::endl << "summed score: " << res;
}
TEST_CASE("matching between sample", "[OR_TOOLS]") {
	const size_t dim_size = 9;
	std::cout << "number of possible matchings: " << dim_size
	          << "! = " << boost::math::double_factorial<double>(dim_size) << "\n";
	RectMatrix<double> dst_mtx = Util::create_random_mtx(dim_size);
	MatcherOrTools matcher;
	RfAlgorithmInterface::SplitScores scores(std::move(dst_mtx));
	double res = matcher.solve(scores);
	INFO(res)
	REQUIRE(std::abs(res - 1.) >= 1e-5); // TODO: whaaa?
	// create multiple random mappings
	double closest_guess = 0;
	for (size_t rnd_it = 0; rnd_it < 50000; ++rnd_it) {
		std::vector<size_t> bad_mapping;
		std::random_device rnd;
		std::mt19937 mt(rnd());
		for (size_t i = 0; i < dim_size; ++i) {
			bad_mapping.push_back(i);
		}
		std::shuffle(bad_mapping.begin(), bad_mapping.end(), mt);
		// verify that res is (probably) best mapping
		double oth_score = 0;
		for (size_t i = 0; i < dim_size; ++i) {
			oth_score += dst_mtx.at(i, bad_mapping[i]);
		}
		if (oth_score > closest_guess) {
			closest_guess = oth_score;
		}
		REQUIRE(oth_score <= res);
	}
	std::cout << "best random guess: " << closest_guess;
}
std::size_t number_of_files_in_directory(std::filesystem::path path);
double get_pairwise_tree_score(std::filesystem::path path);
static std::string getRefPath(const RfMetricInterface::Metric &metric);
TEST_CASE("validate matching on reference pairwise scores", "[OR_TOOLS][REF]")
{
	boost::log::sources::severity_logger<lg::SeverityLevel> logger;

	std::string base_path = "../test/samples/";
	RfMetricInterface::Metric metric =
	    GENERATE(RfMetricInterface::SPI, RfMetricInterface::MSI, RfMetricInterface::MCI);
	std::string reference_path = getRefPath(metric);
	BOOST_LOG_SEV(logger, lg::normal) << "Checking inside metric path " << reference_path;
	// init randomness for later
	std::random_device rnd;
	std::mt19937 mt(rnd());
	// now iterate over all inner directories, and pick ONE random tree score to validate against
	for (const auto &sample_folder : std::filesystem::directory_iterator(reference_path)) {
		// skip irrelevant files
		if (!sample_folder.is_directory()) {
			continue;
		}
		int num_pairwise_scores =
		    static_cast<int>(number_of_files_in_directory(sample_folder.path())) - 1;
		std::uniform_int_distribution<int> distr(0, num_pairwise_scores);
		// pick random pairwise score file
		int file_idx = distr(mt);
		auto score_file_iter = std::filesystem::directory_iterator(sample_folder.path());
		std::advance(score_file_iter, file_idx);
		BOOST_LOG_SEV(logger, lg::normal)
		    << "Processing split file " << score_file_iter->path().filename() << " in folder "
		    << sample_folder.path().filename();
		double solution = get_pairwise_tree_score(score_file_iter->path());

		// calculate own solution by matching from pairwise split score matrix
		auto split_scores = Util::parse_mtx_from_r(score_file_iter->path().string(), '\n', ' ');
		MatcherOrTools matcher;
		RfAlgorithmInterface::SplitScores scores(std::move(split_scores));
		auto our_solution = matcher.solve(scores);
		double difference = std::abs(our_solution - solution);
		bool correct = difference <= 1e-3;
		CHECK(correct);
		BOOST_LOG_SEV(logger, lg::normal) << (correct ? "correct." : "FAIL");
	}
}
static std::string getRefPath(const RfMetricInterface::Metric &metric) {
	std::string reference_path = "/rf_metrics/";
	switch (metric) {
		case RfMetricInterface::SPI:
			reference_path += "SPI_10/";
			break;
		case RfMetricInterface::MSI:
			reference_path += "MSI_10/";
			break;
		case RfMetricInterface::MCI:
			reference_path += "MCI_10/";
			break;
		case RfMetricInterface::RF:
			throw std::invalid_argument("softwipe...");
	}
	return reference_path;
}

TEST_CASE("matcher arc creation", "[OR_TOOLS]") {
	MatcherOrTools matcher;
	constexpr size_t mtx_dim = 3;
	auto mtx = Util::create_random_mtx(mtx_dim);
	auto graph = matcher.getGraphCopy(mtx);

	CHECK(graph.num_nodes() == mtx_dim * 2);
	CHECK(graph.num_arcs() == mtx_dim * mtx_dim);
	size_t counter = 0;
	// Check if outdegree is always equal at left nodes
	for (const auto node : graph.AllNodes()) {
		CHECK(graph.OutDegree(node) == mtx_dim);
		++counter;
		if (counter == mtx_dim) {
			break;
		}
	}
}

std::size_t number_of_files_in_directory(std::filesystem::path path) {
	using std::filesystem::directory_iterator;
	return static_cast<size_t>(std::distance(directory_iterator(path), directory_iterator{}));
}
double get_pairwise_tree_score(std::filesystem::path path) {
	// use filename to figure out pairwise tree comparison
	std::string split_score_name = path.filename();
	auto splitted = Util::split(split_score_name, '_');
	REQUIRE(splitted.size() == 2);
	// R is a 1-starting language...
	size_t row = std::stoul(splitted[0]) - 1;
	size_t col = std::stoul(splitted[1]) - 1;

	// open pairwise score file, which is in the same directory
	path.remove_filename();
	std::string tree_score_name = path.string() + "pairwise_trees";
	auto res_mtx = Util::parse_mtx_from_r(tree_score_name, '\n', ',');
	return res_mtx.at(row, col);
}

static void BasicExample() {
	using namespace operations_research;
	// Create the linear solver with the GLOP backend.
	std::unique_ptr<MPSolver> solver(MPSolver::CreateSolver("GLOP"));

	// Create the variables x and y.
	MPVariable *const x = solver->MakeNumVar(0.0, 1, "x");
	MPVariable *const y = solver->MakeNumVar(0.0, 2, "y");

	std::cout << "Number of variables = " << solver->NumVariables() << std::endl;

	// Create a linear constraint, 0 <= x + y <= 2.
	MPConstraint *const ct = solver->MakeRowConstraint(0.0, 2.0, "ct");
	ct->SetCoefficient(x, 1);
	ct->SetCoefficient(y, 1);

	std::cout << "Number of constraints = " << solver->NumConstraints() << std::endl;

	// Create the objective function, 3 * x + y.
	MPObjective *const objective = solver->MutableObjective();
	objective->SetCoefficient(x, 3);
	objective->SetCoefficient(y, 1);
	objective->SetMaximization();

	solver->Solve();

	std::cout << "Solution:" << std::endl;
	std::cout << "Objective value = " << objective->Value() << std::endl;
	std::cout << "x = " << x->solution_value() << std::endl;
	std::cout << "y = " << y->solution_value() << std::endl;
}


/* test_generate_canonical.cpp
 *
 * Copyright Steven Watanabe 2011
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * $Id: test_generate_canonical.cpp 71018 2011-04-05 21:27:52Z steven_watanabe $
 *
 */

#include <boost/random/generate_canonical.hpp>

#include <boost/random/linear_congruential.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/lagged_fibonacci.hpp>
#include <boost/cstdint.hpp>

#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>

typedef boost::mpl::vector<
    boost::random::minstd_rand,
    boost::random::mt19937,
    boost::random::lagged_fibonacci607
> engines;

BOOST_AUTO_TEST_CASE_TEMPLATE(test_float, Engine, engines)
{
    Engine eng;
    Engine expected;
    for(int i = 0; i < 1000; ++i) {
        float val = boost::random::generate_canonical<float, 64>(eng);
        BOOST_CHECK_GE(val, 0);
        BOOST_CHECK_LT(val, 1);
    }
    expected.discard(1000);
    BOOST_CHECK_EQUAL(eng, expected);
    for(int i = 0; i < 1000; ++i) {
        float val = boost::random::generate_canonical<float, 12>(eng);
        BOOST_CHECK_GE(val, 0);
        BOOST_CHECK_LT(val, 1);
    }
    expected.discard(1000);
    BOOST_CHECK_EQUAL(eng, expected);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(test_double, Engine, engines)
{
    Engine eng;
    Engine expected;
    for(int i = 0; i < 1000; ++i) {
        double val = boost::random::generate_canonical<double, 64>(eng);
        BOOST_CHECK_GE(val, 0);
        BOOST_CHECK_LT(val, 1);
    }
    expected.discard(2000);
    BOOST_CHECK_EQUAL(eng, expected);
    for(int i = 0; i < 1000; ++i) {
        double val = boost::random::generate_canonical<double, 12>(eng);
        BOOST_CHECK_GE(val, 0);
        BOOST_CHECK_LT(val, 1);
    }
    expected.discard(1000);
    BOOST_CHECK_EQUAL(eng, expected);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(test_long_double, Engine, engines)
{
    Engine eng;
    Engine expected;
    for(int i = 0; i < 1000; ++i) {
        long double val = boost::random::generate_canonical<long double, 60>(eng);
        BOOST_CHECK_GE(val, 0);
        BOOST_CHECK_LT(val, 1);
    }
    expected.discard(2000);
    BOOST_CHECK_EQUAL(eng, expected);
    for(int i = 0; i < 1000; ++i) {
        long double val = boost::random::generate_canonical<long double, 12>(eng);
        BOOST_CHECK_GE(val, 0);
        BOOST_CHECK_LT(val, 1);
    }
    expected.discard(1000);
    BOOST_CHECK_EQUAL(eng, expected);
}

struct max_engine
{
    typedef boost::uint32_t result_type;
    static result_type min BOOST_PREVENT_MACRO_SUBSTITUTION () { return 0; }
    static result_type max BOOST_PREVENT_MACRO_SUBSTITUTION ()
    { return ~boost::uint32_t(0); }
    result_type operator()() { return (max)(); }
};

BOOST_AUTO_TEST_CASE(test_max)
{
    max_engine eng;
    BOOST_CHECK_LT((boost::random::generate_canonical<float, 64>(eng)), 1);
    BOOST_CHECK_LT((boost::random::generate_canonical<double, 64>(eng)), 1);
    BOOST_CHECK_LT((boost::random::generate_canonical<long double, 64>(eng)), 1);
}


// Copyright (C) 2014 The Regents of the University of California (Regents).
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//
//     * Neither the name of The Regents or University of California nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Please contact the author of this library if you have any questions.
// Author: Chris Sweeney (cmsweeney@cs.ucsb.edu)

#include "theia/sfm/estimate_twoview_info.h"

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <glog/logging.h>

#include <vector>

#include "theia/matching/feature_correspondence.h"
#include "theia/sfm/camera/camera.h"
#include "theia/sfm/camera_intrinsics_prior.h"
#include "theia/sfm/estimators/estimate_relative_pose.h"
#include "theia/sfm/estimators/estimate_uncalibrated_relative_pose.h"
#include "theia/sfm/pose/util.h"
#include "theia/sfm/reconstruction_estimator_utils.h"
#include "theia/sfm/set_camera_intrinsics_from_priors.h"
#include "theia/sfm/triangulation/triangulation.h"
#include "theia/sfm/twoview_info.h"
#include "theia/sfm/types.h"
#include "theia/sfm/visibility_pyramid.h"
#include "theia/solvers/sample_consensus_estimator.h"

namespace theia {

using Eigen::AngleAxisd;
using Eigen::Matrix3d;
using Eigen::Vector2d;
using Eigen::Vector3d;

namespace {

// Normalizes the image features by the camera intrinsics.
void NormalizeFeatures(
    const CameraIntrinsicsPrior& prior1,
    const CameraIntrinsicsPrior& prior2,
    const std::vector<FeatureCorrespondence>& correspondences,
    std::vector<FeatureCorrespondence>* normalized_correspondences) {
  CHECK_NOTNULL(normalized_correspondences)->clear();

  Camera camera1, camera2;
  camera1.SetFromCameraIntrinsicsPriors(prior1);
  camera2.SetFromCameraIntrinsicsPriors(prior2);
  // If no focal length prior is given, the SetFromCameraIntrinsicsPrior method
  // will set the focal length to a reasonable guess. However, for cameras with
  // no focal length priors we DO NOT want the feature normalization below to
  // divide by the focal length, so we must reset the focal lengths to 1.0 so
  // that the feature normalization is unaffected.
  if (!prior1.focal_length.is_set || !prior2.focal_length.is_set) {
    camera1.SetFocalLength(1.0);
    camera2.SetFocalLength(1.0);
  }

  normalized_correspondences->reserve(correspondences.size());
  for (const FeatureCorrespondence& correspondence : correspondences) {
    FeatureCorrespondence normalized_correspondence;
    const Eigen::Vector3d normalized_feature1 =
        camera1.PixelToNormalizedCoordinates(correspondence.feature1);
    normalized_correspondence.feature1 = normalized_feature1.hnormalized();

    const Eigen::Vector3d normalized_feature2 =
        camera2.PixelToNormalizedCoordinates(correspondence.feature2);
    normalized_correspondence.feature2 = normalized_feature2.hnormalized();

    normalized_correspondences->emplace_back(normalized_correspondence);
  }
}

// Compute the visibility score of the inliers in the images.
int ComputeVisibilityScoreOfInliers(
    const CameraIntrinsicsPrior& intrinsics1,
    const CameraIntrinsicsPrior& intrinsics2,
    const std::vector<FeatureCorrespondence>& correspondences,
    const std::vector<int>& inlier_indices) {
  static const int kNumPyramidLevels = 6;
  // If the image dimensions are not available, do not make any assumptions
  // about what they might be. Instead, we return the number of inliers as a
  // default.
  if (intrinsics1.image_width == 0 || intrinsics1.image_height == 0 ||
      intrinsics2.image_width == 0 || intrinsics2.image_height == 0) {
    return inlier_indices.size();
  }

  // Compute the visibility score for all inliers.
  VisibilityPyramid pyramid1(
      intrinsics1.image_width, intrinsics1.image_height, kNumPyramidLevels);
  VisibilityPyramid pyramid2(
      intrinsics2.image_width, intrinsics2.image_height, kNumPyramidLevels);
  for (const int i : inlier_indices) {
    const FeatureCorrespondence& match = correspondences[i];
    pyramid1.AddPoint(match.feature1);
    pyramid2.AddPoint(match.feature2);
  }
  // Return the summed score.
  return pyramid1.ComputeScore() + pyramid2.ComputeScore();
}

bool EstimateTwoViewInfoCalibrated(
    const EstimateTwoViewInfoOptions& options,
    const CameraIntrinsicsPrior& intrinsics1,
    const CameraIntrinsicsPrior& intrinsics2,
    const std::vector<FeatureCorrespondence>& correspondences,
    TwoViewInfo* twoview_info,
    std::vector<int>* inlier_indices) {
  // Normalize features w.r.t focal length.
  std::vector<FeatureCorrespondence> normalized_correspondences;
  NormalizeFeatures(
      intrinsics1, intrinsics2, correspondences, &normalized_correspondences);

  // Set the ransac parameters.
  RansacParameters ransac_options;
  ransac_options.rng = options.rng;
  ransac_options.failure_probability = 1.0 - options.expected_ransac_confidence;
  ransac_options.min_iterations = options.min_ransac_iterations;
  ransac_options.max_iterations = options.max_ransac_iterations;

  // Compute the sampson error threshold to account for the resolution of the
  // images.
  const double max_sampson_error_pixels1 =
      ComputeResolutionScaledThreshold(options.max_sampson_error_pixels,
                                       intrinsics1.image_width,
                                       intrinsics1.image_height);
  const double max_sampson_error_pixels2 =
      ComputeResolutionScaledThreshold(options.max_sampson_error_pixels,
                                       intrinsics2.image_width,
                                       intrinsics2.image_height);
  ransac_options.error_thresh =
      max_sampson_error_pixels1 * max_sampson_error_pixels2 /
      (intrinsics1.focal_length.value[0] * intrinsics2.focal_length.value[0]);
  ransac_options.use_mle = options.use_mle;

  RelativePose relative_pose;
  RansacSummary summary;
  if (!EstimateRelativePose(ransac_options,
                            options.ransac_type,
                            normalized_correspondences,
                            &relative_pose,
                            &summary)) {
    return false;
  }

  AngleAxisd rotation(relative_pose.rotation);

  // Set the twoview info.
  twoview_info->rotation_2 = rotation.angle() * rotation.axis();
  twoview_info->position_2 = relative_pose.position;
  twoview_info->focal_length_1 = intrinsics1.focal_length.value[0];
  twoview_info->focal_length_2 = intrinsics2.focal_length.value[0];
  twoview_info->num_verified_matches = summary.inliers.size();
  twoview_info->visibility_score = ComputeVisibilityScoreOfInliers(
      intrinsics1, intrinsics2, correspondences, *inlier_indices);

  *inlier_indices = summary.inliers;

  return true;
}

bool EstimateTwoViewInfoUncalibrated(
    const EstimateTwoViewInfoOptions& options,
    const CameraIntrinsicsPrior& intrinsics1,
    const CameraIntrinsicsPrior& intrinsics2,
    const std::vector<FeatureCorrespondence>& correspondences,
    TwoViewInfo* twoview_info,
    std::vector<int>* inlier_indices) {
  // Normalize features w.r.t principal point.
  std::vector<FeatureCorrespondence> centered_correspondences;
  NormalizeFeatures(
      intrinsics1, intrinsics2, correspondences, &centered_correspondences);

  // Set the ransac parameters.
  RansacParameters ransac_options;
  ransac_options.rng = options.rng;
  ransac_options.failure_probability = 1.0 - options.expected_ransac_confidence;
  ransac_options.min_iterations = options.min_ransac_iterations;
  ransac_options.max_iterations = options.max_ransac_iterations;

  // Compute the sampson error threshold to account for the resolution of the
  // images.
  const double max_sampson_error_pixels1 =
      ComputeResolutionScaledThreshold(options.max_sampson_error_pixels,
                                       intrinsics1.image_width,
                                       intrinsics1.image_height);
  const double max_sampson_error_pixels2 =
      ComputeResolutionScaledThreshold(options.max_sampson_error_pixels,
                                       intrinsics2.image_width,
                                       intrinsics2.image_height);
  ransac_options.error_thresh =
      max_sampson_error_pixels1 * max_sampson_error_pixels2;

  UncalibratedRelativePose relative_pose;
  RansacSummary summary;
  if (!EstimateUncalibratedRelativePose(ransac_options,
                                        options.ransac_type,
                                        centered_correspondences,
                                        &relative_pose,
                                        &summary)) {
    return false;
  }

  AngleAxisd rotation(relative_pose.rotation);

  // Set the twoview info.
  twoview_info->rotation_2 = rotation.angle() * rotation.axis();
  twoview_info->position_2 = relative_pose.position;
  twoview_info->focal_length_1 = relative_pose.focal_length1;
  twoview_info->focal_length_2 = relative_pose.focal_length2;

  // Get the number of verified features.
  twoview_info->num_verified_matches = summary.inliers.size();
  twoview_info->visibility_score = ComputeVisibilityScoreOfInliers(
      intrinsics1, intrinsics2, correspondences, *inlier_indices);
  *inlier_indices = summary.inliers;

  return true;
}

}  // namespace

bool EstimateTwoViewInfo(
    const EstimateTwoViewInfoOptions& options,
    const CameraIntrinsicsPrior& intrinsics1,
    const CameraIntrinsicsPrior& intrinsics2,
    const std::vector<FeatureCorrespondence>& correspondences,
    TwoViewInfo* twoview_info,
    std::vector<int>* inlier_indices) {
  CHECK_NOTNULL(twoview_info);
  CHECK_NOTNULL(inlier_indices)->clear();

  // Case where both views are calibrated.
  if (intrinsics1.focal_length.is_set && intrinsics2.focal_length.is_set) {
    return EstimateTwoViewInfoCalibrated(options,
                                         intrinsics1,
                                         intrinsics2,
                                         correspondences,
                                         twoview_info,
                                         inlier_indices);
  }

  // Only one of the focal lengths is set.
  if (intrinsics1.focal_length.is_set || intrinsics2.focal_length.is_set) {
    LOG(WARNING) << "Solving for two view infos when exactly one view is "
                    "calibrated has not been implemented yet. Treating both "
                    "views as uncalibrated instead.";
    return EstimateTwoViewInfoUncalibrated(options,
                                           intrinsics1,
                                           intrinsics2,
                                           correspondences,
                                           twoview_info,
                                           inlier_indices);
  }

  // Assume both views are uncalibrated.
  return EstimateTwoViewInfoUncalibrated(options,
                                         intrinsics1,
                                         intrinsics2,
                                         correspondences,
                                         twoview_info,
                                         inlier_indices);
}

}  // namespace theia