/**
  * Takes a word and translates it into the internally used form, an array of integers.
  *
  * @param word the input word.
  * @return the translated word.
  */
 private int[] translateWord(FreeWord word) {
   final int res[] = new int[word.size()];
   for (int i = 0; i < word.size(); ++i) {
     final FreeWord g = word.subword(i, i + 1);
     final Integer idx = (Integer) this.gen2idx.get(g);
     res[i] = idx.intValue();
   }
   return res;
 }
 public Object apply(final Object x, final FreeWord w) {
   if (!(x instanceof Coset)) {
     return null;
   }
   final Coset coset = (Coset) x;
   if (coset.getAction() != this) {
     return null;
   }
   int current = coset.getIndex();
   if (current < 1 || current > size()) {
     return null;
   }
   for (int i = 0; i < w.length(); ++i) {
     final Integer j = (Integer) this.gen2idx.get(w.subword(i, i + 1));
     if (j == null) {
       return null;
     } else {
       final int row[] = (int[]) this.table.get(current);
       current = row[j.intValue()];
     }
   }
   return new Coset(current);
 }
  /**
   * Constructs a CosetTable instance with a limit on the number of table rows that may be present
   * at any stage of the construction process. Note that this number may be much higher than the
   * actual number of cosets, because multiple rows may be found correspond to a single coset long
   * after their construction.
   *
   * @param group the group.
   * @param subgroupGenerators generators of the subgroup.
   * @param sizeLimit the limit on the number of coset rows.
   */
  public CosetAction(final FpGroup group, final List subgroupGenerators, final int sizeLimit) {

    // --- copy parameters to fields
    this.group = group;
    this.subgroupGenerators = Collections.unmodifiableList(subgroupGenerators);
    this.sizeLimit = sizeLimit;

    // --- extract generators and relators for group
    final List groupGenerators = group.getGenerators();
    final List groupRelators = group.getRelators();

    // --- set up translation tables
    this.ngens = 2 * groupGenerators.size();
    this.idx2gen = new FreeWord[this.ngens];
    this.gen2idx = new HashMap();
    this.idx2invidx = new int[this.ngens];

    int nu = 0;
    for (Iterator iter = groupGenerators.iterator(); iter.hasNext(); ) {
      final FreeWord g = (FreeWord) iter.next();
      this.idx2gen[nu] = g;
      this.gen2idx.put(g, new Integer(nu));
      this.idx2gen[nu + 1] = g.inverse();
      this.gen2idx.put(g.inverse(), new Integer(nu + 1));
      this.idx2invidx[nu] = nu + 1;
      this.idx2invidx[nu + 1] = nu;
      nu += 2;
    }

    // --- translate relators
    final List rels = new ArrayList();
    for (Iterator iter = groupRelators.iterator(); iter.hasNext(); ) {
      final FreeWord r = (FreeWord) iter.next();
      final int n = r.length();
      for (int i = 0; i < n; ++i) {
        final FreeWord w = r.subword(i, n).times(r.subword(0, i));
        rels.add(translateWord(w));
        rels.add(translateWord(w.inverse()));
      }
    }

    // --- translate subgroup generators
    final List subgens = new ArrayList();
    for (Iterator iter = subgroupGenerators.iterator(); iter.hasNext(); ) {
      final FreeWord generator = (FreeWord) iter.next();
      subgens.add(translateWord(generator));
      subgens.add(translateWord(generator.inverse()));
    }

    // --- set up a coset table with one dummy row and one row for the trivial coset
    this.table = new ArrayList();
    this.table.add(new int[this.ngens]);
    this.table.add(new int[this.ngens]);

    // --- holds classes of rows known to correspond to the same coset
    IntPartition equivalences = new IntPartition();

    // --- number of rows made invalid but not yet deleted
    int invalidRows = 0;

    // --- scan the table row by row, creating and deleting rows on the fly
    int i = 1;
    while (i < this.table.size()) {
      // --- proceed only if the current row is valid
      if (equivalences.find(i) != i) {
        ++i;
        continue;
      }

      // --- scan the current row
      final int row[] = (int[]) this.table.get(i);
      for (int j = 0; j < this.ngens; ++j) {
        // --- proceed only if the current column is empty
        if (row[j] != 0) {
          continue;
        }

        // --- log the result of this construction step
        if (LOGGING) {
          System.out.println("\ni = " + i + ", j = " + j);
        }
        // --- make a new row for the product with g
        final int newRow[] = new int[this.ngens];
        this.table.add(newRow);

        // --- set the correspondences
        final int n = this.table.size();
        row[j] = n - 1;
        newRow[this.idx2invidx[j]] = i;

        if (LOGGING) {
          System.out.println("after setting item:");
          dumpTable();
        }

        // --- scan relations to identify equivalent rows
        final LinkedList identify = new LinkedList();
        for (Iterator iter = rels.iterator(); iter.hasNext(); ) {
          final int rel[] = (int[]) iter.next();
          scanRelation(rel, n - 1, identify);
        }

        // --- scan subgroup generators
        final int one = equivalences.find(1);
        for (Iterator iter = subgens.iterator(); iter.hasNext(); ) {
          final int gen[] = (int[]) iter.next();
          scanRelation(gen, one, identify);
        }

        if (LOGGING) {
          System.out.println("after scanning:");
          dumpTable();
        }

        // --- perform pending identifications
        invalidRows += performIdentifications(identify, equivalences);

        if (LOGGING) {
          System.out.println("after identifying:");
          dumpTable();
        }

        // --- quit if the limit on the number of rows is reached
        if (this.sizeLimit > 0 && n - invalidRows > this.sizeLimit) {
          throw new RuntimeException("table limit reached");
        }

        // --- compress the table if necessary
        if (invalidRows > n / 2) {
          final int old2new[] = compressTable(equivalences);
          equivalences = new IntPartition();
          i = old2new[i];
          invalidRows = 0;
          if (LOGGING) {
            System.out.println("after compressing:");
            dumpTable();
          }
        }
      }

      // --- look at next row
      ++i;
    }

    // --- make a final cleanup
    compressTable(equivalences);
    if (LOGGING) {
      System.out.println("\nAfter final compression:");
      dumpTable();
    }

    // --- some finishing touches
    this.numberOfCosets = this.table.size() - 1;
    this.cosetRepresentatives = computeCosetRepresentatives();
  }