/**
   * Computes inverse coefficients
   *
   * @param border
   * @param forward Forward coefficients.
   * @param inverse Inverse used in the inner portion of the data stream.
   * @return
   */
  private static WlBorderCoef<WlCoef_F32> computeBorderCoefficients(
      BorderIndex1D border, WlCoef_F32 forward, WlCoef_F32 inverse) {
    int N = Math.max(forward.getScalingLength(), forward.getWaveletLength());
    N += N % 2;
    N *= 2;
    border.setLength(N);

    // Because the wavelet transform is a linear invertible system the inverse coefficients
    // can be found by creating a matrix and inverting the matrix.  Boundary conditions are then
    // extracted from this inverted matrix.
    DenseMatrix64F A = new DenseMatrix64F(N, N);
    for (int i = 0; i < N; i += 2) {

      for (int j = 0; j < forward.scaling.length; j++) {
        int index = border.getIndex(j + i + forward.offsetScaling);
        A.add(i, index, forward.scaling[j]);
      }

      for (int j = 0; j < forward.wavelet.length; j++) {
        int index = border.getIndex(j + i + forward.offsetWavelet);
        A.add(i + 1, index, forward.wavelet[j]);
      }
    }

    LinearSolver<DenseMatrix64F> solver = LinearSolverFactory.linear(N);
    if (!solver.setA(A) || solver.quality() < 1e-5) {
      throw new IllegalArgumentException("Can't invert matrix");
    }

    DenseMatrix64F A_inv = new DenseMatrix64F(N, N);
    solver.invert(A_inv);

    int numBorder = UtilWavelet.borderForwardLower(inverse) / 2;

    WlBorderCoefFixed<WlCoef_F32> ret = new WlBorderCoefFixed<>(numBorder, numBorder + 1);
    ret.setInnerCoef(inverse);

    // add the lower coefficients first
    for (int i = 0; i < ret.getLowerLength(); i++) {
      computeLowerCoef(inverse, A_inv, ret, i * 2);
    }

    // add upper coefficients
    for (int i = 0; i < ret.getUpperLength(); i++) {
      computeUpperCoef(inverse, N, A_inv, ret, i * 2);
    }

    return ret;
  }
  // todo rename and move to a utility function?
  public static WlBorderCoefFixed<WlCoef_I32> convertToInt(
      WlBorderCoefFixed<WlCoef_F32> orig, WlCoef_I32 inner) {
    WlBorderCoefFixed<WlCoef_I32> ret =
        new WlBorderCoefFixed<>(orig.getLowerLength(), orig.getUpperLength());

    for (int i = 0; i < orig.getLowerLength(); i++) {
      WlCoef_F32 o = orig.getLower(i);
      WlCoef_I32 r = new WlCoef_I32();
      ret.setLower(i, r);
      convertCoef_F32_to_I32(inner.denominatorScaling, inner.denominatorWavelet, o, r);
    }
    for (int i = 0; i < orig.getUpperLength(); i++) {
      WlCoef_F32 o = orig.getUpper(i);
      WlCoef_I32 r = new WlCoef_I32();
      ret.setUpper(i, r);
      convertCoef_F32_to_I32(inner.denominatorScaling, inner.denominatorWavelet, o, r);
    }

    ret.setInnerCoef(inner);

    return ret;
  }