/**
  * ResidueSolvableWordPolynomial left and right multiplication. Product with exponent vector.
  *
  * @param e exponent.
  * @param f exponent.
  * @return x<sup>e</sup> * this * x<sup>f</sup>, where * denotes solvable multiplication.
  */
 @Override
 public ResidueSolvableWordPolynomial<C> multiply(ExpVector e, ExpVector f) {
   if (e == null || e.isZERO()) {
     return this;
   }
   if (f == null || f.isZERO()) {
     return this;
   }
   WordResidue<C> b = ring.getONECoefficient();
   return multiply(b, e, b, f);
 }
 /**
  * ResidueSolvableWordPolynomial multiplication. Left product with exponent vector.
  *
  * @param e exponent.
  * @return x<sup>e</sup> * this, where * denotes solvable multiplication.
  */
 @Override
 public ResidueSolvableWordPolynomial<C> multiplyLeft(ExpVector e) {
   if (e == null || e.isZERO()) {
     return this;
   }
   ResidueSolvableWordPolynomial<C> Cp = ring.valueOf(e);
   return Cp.multiply(this);
 }
 /**
  * ResidueSolvableWordPolynomial multiplication. Commutative product with exponent vector.
  *
  * @param B solvable polynomial.
  * @param f exponent vector.
  * @return B*f, where * is commutative multiplication.
  */
 protected ResidueSolvableWordPolynomial<C> shift(
     ResidueSolvableWordPolynomial<C> B, ExpVector f) {
   ResidueSolvableWordPolynomial<C> C = ring.getZERO().copy();
   if (B == null || B.isZERO()) {
     return C;
   }
   if (f == null || f.isZERO()) {
     return B;
   }
   Map<ExpVector, WordResidue<C>> Cm = C.val;
   Map<ExpVector, WordResidue<C>> Bm = B.val;
   for (Map.Entry<ExpVector, WordResidue<C>> y : Bm.entrySet()) {
     ExpVector e = y.getKey();
     WordResidue<C> a = y.getValue();
     ExpVector d = e.sum(f);
     if (!a.isZERO()) {
       Cm.put(d, a);
     }
   }
   return C;
 }
  // cannot @Override, @NoOverride
  public ResidueSolvableWordPolynomial<C> multiply(ResidueSolvableWordPolynomial<C> Bp) {
    if (Bp == null || Bp.isZERO()) {
      return ring.getZERO();
    }
    if (this.isZERO()) {
      return this;
    }
    assert (ring.nvar == Bp.ring.nvar);
    if (debug) {
      logger.debug("ring = " + ring);
    }
    ExpVector Z = ring.evzero;
    ResidueSolvableWordPolynomial<C> Dp = ring.getZERO().copy();
    ResidueSolvableWordPolynomial<C> zero = ring.getZERO().copy();
    WordResidue<C> one = ring.getONECoefficient();

    Map<ExpVector, WordResidue<C>> A = val;
    Map<ExpVector, WordResidue<C>> B = Bp.val;
    Set<Map.Entry<ExpVector, WordResidue<C>>> Bk = B.entrySet();
    for (Map.Entry<ExpVector, WordResidue<C>> y : A.entrySet()) {
      WordResidue<C> a = y.getValue();
      ExpVector e = y.getKey();
      if (debug) logger.info("e = " + e + ", a = " + a);
      for (Map.Entry<ExpVector, WordResidue<C>> x : Bk) {
        WordResidue<C> b = x.getValue();
        ExpVector f = x.getKey();
        if (debug) logger.info("f = " + f + ", b = " + b);
        int[] fp = f.dependencyOnVariables();
        int fl1 = 0;
        if (fp.length > 0) {
          fl1 = fp[fp.length - 1];
        }
        int fl1s = ring.nvar + 1 - fl1;
        // polynomial/residue coefficient multiplication
        ResidueSolvableWordPolynomial<C> Cps = ring.getZERO().copy();
        if (ring.polCoeff.coeffTable.isEmpty() || b.isConstant() || e.isZERO()) { // symmetric
          Cps = new ResidueSolvableWordPolynomial<C>(ring, b, e);
          if (debug) logger.info("symmetric coeff: b = " + b + ", e = " + e);
        } else { // unsymmetric
          if (debug) logger.info("unsymmetric coeff: b = " + b + ", e = " + e);
          // recursive polynomial coefficient multiplication : e * b.val
          RecSolvableWordPolynomial<C> rsp1 = new RecSolvableWordPolynomial<C>(ring.polCoeff, e);
          RecSolvableWordPolynomial<C> rsp2 =
              new RecSolvableWordPolynomial<C>(ring.polCoeff, b.val);
          RecSolvableWordPolynomial<C> rsp3 = rsp1.multiply(rsp2);
          Cps = ring.fromPolyCoefficients(rsp3);
        }
        if (debug) {
          logger.info("coeff-poly: Cps = " + Cps);
        }
        // polynomial multiplication
        ResidueSolvableWordPolynomial<C> Dps = ring.getZERO().copy();
        ResidueSolvableWordPolynomial<C> Ds = null;
        ResidueSolvableWordPolynomial<C> D1, D2;
        if (ring.table.isEmpty() || Cps.isConstant() || f.isZERO()) { // symmetric
          if (debug) logger.info("symmetric poly: b = " + b + ", e = " + e);
          ExpVector g = e.sum(f);
          if (Cps.isConstant()) {
            Ds =
                new ResidueSolvableWordPolynomial<C>(
                    ring, Cps.leadingBaseCoefficient(), g); // symmetric!
          } else {
            Ds = shift(Cps, f); // symmetric
          }
        } else { // eventually unsymmetric
          if (debug) logger.info("unsymmetric poly: Cps = " + Cps + ", f = " + f);
          for (Map.Entry<ExpVector, WordResidue<C>> z : Cps.val.entrySet()) {
            // split g = g1 * g2, f = f1 * f2
            WordResidue<C> c = z.getValue();
            ExpVector g = z.getKey();
            if (debug) logger.info("g = " + g + ", c = " + c);
            int[] gp = g.dependencyOnVariables();
            int gl1 = ring.nvar + 1;
            if (gp.length > 0) {
              gl1 = gp[0];
            }
            int gl1s = ring.nvar + 1 - gl1;
            if (gl1s <= fl1s) { // symmetric
              ExpVector h = g.sum(f);
              if (debug) logger.info("disjoint poly: g = " + g + ", f = " + f + ", h = " + h);
              Ds = (ResidueSolvableWordPolynomial<C>) zero.sum(one, h); // symmetric!
            } else {
              ExpVector g1 = g.subst(gl1, 0);
              ExpVector g2 = Z.subst(gl1, g.getVal(gl1)); // bug el1, gl1
              ExpVector g4;
              ExpVector f1 = f.subst(fl1, 0);
              ExpVector f2 = Z.subst(fl1, f.getVal(fl1));
              if (debug) logger.info("poly, g1 = " + g1 + ", f1 = " + f1 + ", Dps = " + Dps);
              if (debug) logger.info("poly, g2 = " + g2 + ", f2 = " + f2);
              TableRelation<WordResidue<C>> rel = ring.table.lookup(g2, f2);
              if (debug) logger.info("poly, g  = " + g + ", f  = " + f + ", rel = " + rel);
              Ds = new ResidueSolvableWordPolynomial<C>(ring, rel.p); // ring.copy(rel.p);
              if (rel.f != null) {
                D2 = new ResidueSolvableWordPolynomial<C>(ring, one, rel.f);
                Ds = Ds.multiply(D2);
                if (rel.e == null) {
                  g4 = g2;
                } else {
                  g4 = g2.subtract(rel.e);
                }
                ring.table.update(g4, f2, Ds);
              }
              if (rel.e != null) {
                D1 = new ResidueSolvableWordPolynomial<C>(ring, one, rel.e);
                Ds = D1.multiply(Ds);
                ring.table.update(g2, f2, Ds);
              }
              if (!f1.isZERO()) {
                D2 = new ResidueSolvableWordPolynomial<C>(ring, one, f1);
                Ds = Ds.multiply(D2);
                // ring.table.update(?,f1,Ds)
              }
              if (!g1.isZERO()) {
                D1 = new ResidueSolvableWordPolynomial<C>(ring, one, g1);
                Ds = D1.multiply(Ds);
                // ring.table.update(e1,?,Ds)
              }
            }
            Ds = Ds.multiplyLeft(c); // assume c commutes with Cs
            Dps = (ResidueSolvableWordPolynomial<C>) Dps.sum(Ds);
          } // end Dps loop
          Ds = Dps;
        }
        Ds = Ds.multiplyLeft(a); // multiply(a,b); // non-symmetric
        if (debug) logger.debug("Ds = " + Ds);
        Dp = (ResidueSolvableWordPolynomial<C>) Dp.sum(Ds);
      } // end B loop
    } // end A loop
    return Dp;
  }