/**
   * DaubJ wavelets have the following properties:<br>
   *
   * <ul>
   *   <li>Conserve the signal's energy
   *   <li>If the signal is approximately polynomial of degree J/2-1 or less within the support then
   *       fluctuations are approximately zero.
   *   <li>The sum of the scaling numbers is sqrt(2)
   *   <li>The sum of the wavelet numbers is 0
   * </ul>
   *
   * @param J The wavelet's degree.
   * @return Description of the DaubJ wavelet.
   */
  public static WaveletDescription<WlCoef_F32> daubJ_F32(int J) {
    if (J != 4) {
      throw new IllegalArgumentException("Only 4 is currently supported");
    }

    WlCoef_F32 coef = new WlCoef_F32();

    coef.offsetScaling = 0;
    coef.offsetWavelet = 0;

    coef.scaling = new float[4];
    coef.wavelet = new float[4];

    double sqrt3 = Math.sqrt(3);
    double div = 4.0 * Math.sqrt(2);
    coef.scaling[0] = (float) ((1 + sqrt3) / div);
    coef.scaling[1] = (float) ((3 + sqrt3) / div);
    coef.scaling[2] = (float) ((3 - sqrt3) / div);
    coef.scaling[3] = (float) ((1 - sqrt3) / div);

    coef.wavelet[0] = coef.scaling[3];
    coef.wavelet[1] = -coef.scaling[2];
    coef.wavelet[2] = coef.scaling[1];
    coef.wavelet[3] = -coef.scaling[0];

    WlBorderCoefStandard<WlCoef_F32> inverse = new WlBorderCoefStandard<>(coef);

    return new WaveletDescription<>(new BorderIndex1D_Wrap(), coef, inverse);
  }
  /**
   * 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;
  }
  private static WlCoef_F32 computeInnerInverseBiorthogonal(WlCoef_F32 coef) {
    WlCoef_F32 ret = new WlCoef_F32();

    // center at zero
    ret.offsetScaling = -coef.wavelet.length / 2;
    // center at one
    ret.offsetWavelet = 1 - coef.scaling.length / 2;

    ret.scaling = new float[coef.wavelet.length];
    ret.wavelet = new float[coef.scaling.length];

    for (int i = 0; i < ret.scaling.length; i++) {
      if (i % 2 == 0) ret.scaling[i] = -coef.wavelet[i];
      else ret.scaling[i] = coef.wavelet[i];
    }
    for (int i = 0; i < ret.wavelet.length; i++) {
      if (i % 2 == 1) ret.wavelet[i] = -coef.scaling[i];
      else ret.wavelet[i] = coef.scaling[i];
    }

    return ret;
  }
  /**
   * Daub J/K biorthogonal wavelets have the following properties:<br>
   *
   * <ul>
   *   <li>DO NOT conserve the signal's energy
   *   <li>If the signal is approximately polynomial of degree (J-1)/2-1 within the support then
   *       fluctuations are approximately zero.
   *   <li>The sum of the scaling numbers is 1
   *   <li>The sum of the wavelet numbers is 0
   * </ul>
   *
   * @param J The wavelet's degree. K = J-2.
   * @param borderType How image borders are handled.
   * @return Description of the Daub J/K wavelet.
   */
  public static WaveletDescription<WlCoef_F32> biorthogonal_F32(int J, BorderType borderType) {
    if (J != 5) {
      throw new IllegalArgumentException("Only 5 is currently supported");
    }

    WlCoef_F32 forward = new WlCoef_F32();

    forward.offsetScaling = -2;
    forward.offsetWavelet = 0;

    forward.scaling = new float[5];
    forward.wavelet = new float[3];

    forward.scaling[0] = (float) (-1.0 / 8.0);
    forward.scaling[1] = (float) (2.0 / 8.0);
    forward.scaling[2] = (float) (6.0 / 8.0);
    forward.scaling[3] = (float) (2.0 / 8.0);
    forward.scaling[4] = (float) (-1.0 / 8.0);

    forward.wavelet[0] = -1.0f / 2.0f;
    forward.wavelet[1] = 1;
    forward.wavelet[2] = -1.0f / 2.0f;

    BorderIndex1D border;
    WlBorderCoef<WlCoef_F32> inverse;

    if (borderType == BorderType.REFLECT) {
      WlCoef_F32 inner = computeInnerInverseBiorthogonal(forward);
      border = new BorderIndex1D_Reflect();
      inverse = computeBorderCoefficients(border, forward, inner);
    } else if (borderType == BorderType.WRAP) {
      WlCoef_F32 inner = computeInnerInverseBiorthogonal(forward);
      inverse = new WlBorderCoefStandard<>(inner);
      border = new BorderIndex1D_Wrap();
    } else {
      throw new IllegalArgumentException("Unsupported border type: " + borderType);
    }
    return new WaveletDescription<>(border, forward, inverse);
  }