/**
   * This operation performs the tile updates needed by the generateTile() operation where
   * interfaces should be considered.
   *
   * @param tile the tile to update
   * @param slabM1 the slab one index less than (so above) the middle slab
   * @param slab the middle slab
   * @param dist the step distance
   */
  private void updateTileByInterface(
      Tile tile, Slab slabM1, Slab slab, double zInt, double rufInt) {

    // Update the tile
    tile.thickness = zInt * slab.interfaceWidth;
    tile.scatteringLength =
        0.5
            * (slab.scatteringLength
                + slabM1.scatteringLength
                + rufInt * (slab.scatteringLength - slabM1.scatteringLength));
    tile.trueAbsLength =
        0.5
            * (slab.trueAbsLength
                + slabM1.trueAbsLength
                + rufInt * (slab.trueAbsLength - slabM1.trueAbsLength));
    tile.incAbsLength =
        0.5
            * (slab.incAbsLength
                + slabM1.incAbsLength
                + rufInt * (slab.incAbsLength - slabM1.incAbsLength));

    return;
  }
  /**
   * This operation performs the tile updates needed by the generateTile() operation.
   *
   * @param tile the tile to update
   * @param slabM1 the slab one index less than (so above) the middle slab
   * @param slab the middle slab
   * @param slabP1 the slab one index greater than (so below) the middle slab
   * @param dist the step distance
   * @throws MathException Thrown if the error function cannot be evaluated
   */
  private void updateTileByLayer(Tile updateSlab, Slab slabM1, Slab slab, Slab slabP1, double dist)
      throws MathException {

    // Compute the exponentials
    double tExpFac = Erf.erf(cE * dist / slab.interfaceWidth);
    double bExpFac = Erf.erf(cE * (dist - slab.thickness) / (slabP1.interfaceWidth));

    // Update the slab properties
    updateSlab.scatteringLength =
        getTileValue(
            slabM1.scatteringLength,
            slab.scatteringLength,
            slabP1.scatteringLength,
            tExpFac,
            bExpFac);
    updateSlab.trueAbsLength =
        getTileValue(
            slabM1.trueAbsLength, slab.trueAbsLength, slabP1.trueAbsLength, tExpFac, bExpFac);
    updateSlab.incAbsLength =
        getTileValue(slabM1.incAbsLength, slab.incAbsLength, slabP1.incAbsLength, tExpFac, bExpFac);

    return;
  }
  /**
   * This operation generates a list of Tiles from the slabs with the corresponding number of
   * ordinate steps.
   *
   * @param slabs the slabs of materials that define the system
   * @param numRough the number of ordinate steps
   * @param zInt FIXME! This array must be preallocated with a size n = maxRoughSize.
   * @param rufInt FIXME! This array must be preallocated with a size n = maxRoughSize.
   * @return the system of generated tiles
   * @throws MathException Thrown if the error function cannot be calculated
   */
  public Tile[] generateTiles(Slab[] slabs, int numRough, double[] zInt, double[] rufInt)
      throws MathException {

    // Local Declarations
    int nGlay = 0;
    double totalThickness = 0.0, gDMid = 0.0, step = 0.0, dist = 0.0;
    // The number of slabs was not defined in the original code. I computed
    // it by counting up the loops. This formula is currently off a little
    // bit.
    int numSlabs = 2 + 2 * (numRough / 2 + 1) + (slabs.length - 2) * (2 + numRough);
    Tile[] generatedSlabs = new Tile[numSlabs];
    // Create the slabs
    for (int i = 0; i < numSlabs; i++) {
      generatedSlabs[i] = new Slab();
    }

    // Evaluate the first half of the vacuum interface. Create the first
    // slab.
    Tile tmpSlab = generatedSlabs[0];
    Slab refSlab = slabs[0], secondRefSlab = slabs[1], thirdRefSlab;
    tmpSlab.scatteringLength = refSlab.scatteringLength;
    tmpSlab.trueAbsLength = refSlab.trueAbsLength;
    tmpSlab.incAbsLength = refSlab.incAbsLength;
    tmpSlab.thickness = refSlab.thickness;
    ++nGlay;
    // Create the other slabs for this half
    for (int i = 0; i < numRough / 2 + 1; i++) {
      tmpSlab = generatedSlabs[nGlay + i];
      updateTileByInterface(tmpSlab, refSlab, secondRefSlab, zInt[i], rufInt[i]);
    }
    nGlay += numRough / 2 + 1;

    // Evaluate the total normalized thickness of the surface
    for (int i = 0; i < numRough + 1; i++) {
      totalThickness += zInt[i];
    }

    // Calculate gradation of layers. Using slabs.length - 1 because the
    // last layer is at index 4.
    for (int i = 1; i < slabs.length - 1; i++) {
      refSlab = slabs[i];
      secondRefSlab = slabs[i + 1];
      thirdRefSlab = slabs[i - 1];
      // FIXME! Review gDMid calculation with John because it can be
      // negative.
      gDMid =
          refSlab.thickness
              - 0.5 * totalThickness * (refSlab.interfaceWidth + secondRefSlab.interfaceWidth);
      if (gDMid <= 1.0e-10) {
        // The interfaces are overlapping. Step through the entire slab
        step = refSlab.thickness / (numRough + 1);
        // Take the first half step
        tmpSlab = generatedSlabs[nGlay];
        tmpSlab.thickness = step / 2.0;
        dist = step / 4.0;
        updateTileByLayer(tmpSlab, thirdRefSlab, refSlab, secondRefSlab, dist);
        ++nGlay;
        dist += 0.75 * step;
        // Take the remaining steps
        for (int j = 0; j < numRough; j++) {
          tmpSlab = generatedSlabs[nGlay + j];
          tmpSlab.thickness = step;
          updateTileByLayer(tmpSlab, thirdRefSlab, refSlab, secondRefSlab, dist);
          dist += step;
        }
        nGlay += numRough;
        // Take final half step
        tmpSlab = generatedSlabs[nGlay];
        tmpSlab.thickness = step / 2.0;
        dist = refSlab.thickness - step / 4.0;
        updateTileByLayer(tmpSlab, thirdRefSlab, refSlab, secondRefSlab, dist);
        ++nGlay;
      } else {
        // Evaluate contributions from interfaces separately.
        // Top interface
        for (int j = numRough / 2 + 1; j < numRough + 1; j++) {
          // Get the next slab and update it by considering the
          // interface contribution.
          tmpSlab = generatedSlabs[nGlay + j - numRough / 2 - 1];
          updateTileByInterface(tmpSlab, thirdRefSlab, refSlab, zInt[j], rufInt[j]);
        }
        nGlay += numRough / 2 + 1;
        // Central, bulk-like portion
        tmpSlab = generatedSlabs[nGlay];
        tmpSlab.scatteringLength = refSlab.scatteringLength;
        tmpSlab.thickness = gDMid;
        tmpSlab.trueAbsLength = refSlab.trueAbsLength;
        tmpSlab.incAbsLength = refSlab.incAbsLength;
        ++nGlay;
        // Bottom interface
        for (int j = 0; j < numRough / 2 + 1; j++) {
          tmpSlab = generatedSlabs[nGlay + j];
          updateTileByInterface(tmpSlab, refSlab, secondRefSlab, zInt[j], rufInt[j]);
        }
        nGlay += numRough / 2 + 1;
      }
    }

    // Evaluate substrate gradation
    refSlab = slabs[slabs.length - 1];
    secondRefSlab = slabs[slabs.length - 2];
    for (int i = numRough / 2 + 1; i < numRough + 1; i++) {
      tmpSlab = generatedSlabs[nGlay + i - numRough / 2 - 1];
      updateTileByInterface(tmpSlab, secondRefSlab, refSlab, zInt[i], rufInt[i]);
    }
    nGlay += numRough / 2 + 1;
    // Handle the last layer
    refSlab = slabs[slabs.length - 1];
    tmpSlab = generatedSlabs[nGlay];
    tmpSlab.scatteringLength = refSlab.scatteringLength;
    tmpSlab.trueAbsLength = refSlab.trueAbsLength;
    tmpSlab.incAbsLength = refSlab.incAbsLength;
    tmpSlab.thickness = refSlab.thickness;
    ++nGlay;

    return generatedSlabs;
  }