/**
   * 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);
  }
  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);
  }