/*
 * see http://rsb.info.nih.gov/ij/developer/source/ij/process/AutoThresholder.java.html
 * The method is present in: An iterative implementation of Kittler and Illingworth's Minimum Error
 * thresholding:Kittler, J & Illingworth, J (1986), "Minimum error thresholding", Pattern Recognition 19: 41-47
 * @param histogram - the histogram of the image
 * @param total - the number of pixels in the image
 * @returns {number} - the threshold
 */

/**
 * Return a threshold for a histogram using Li algorithm.
 * @param histogram - Image histogram.
 * @param total - Number of pixels in the image.
 * @returns The threshold.
 */
export default function minError(
  histogram: Uint32Array,
  total: number,
): number {
  let threshold;
  let Tprev = -2;
  let mu, nu, p, q, sigma2, tau2, w0, w1, w2, sqterm, temp;

  /* Calculate the mean gray-level */
  let mean = 0;
  for (let ih = 0; ih < histogram.length; ih++) {
    mean += ih * histogram[ih];
  }

  mean /= total;

  threshold = mean;

  while (threshold !== Tprev) {
    // Calculate some statistics.
    const sumA1 = sumA(histogram, threshold);
    const sumA2 = sumA(histogram, histogram.length - 1);
    const sumB1 = sumB(histogram, threshold);
    const sumB2 = sumB(histogram, histogram.length - 1);
    const sumC1 = sumC(histogram, threshold);
    const sumC2 = sumC(histogram, histogram.length - 1);

    mu = sumB1 / sumA1;
    nu = (sumB2 - sumB1) / (sumA2 - sumA1);
    p = sumA1 / sumA2;
    q = (sumA2 - sumA1) / sumA2;
    sigma2 = sumC1 / sumA1 - mu * mu;
    tau2 = (sumC2 - sumC1) / (sumA2 - sumA1) - nu * nu;

    // The terms of the quadratic equation to be solved.
    w0 = 1 / sigma2 - 1 / tau2;
    w1 = mu / sigma2 - nu / tau2;
    w2 =
      (mu * mu) / sigma2 -
      (nu * nu) / tau2 +
      Math.log10((sigma2 * (q * q)) / (tau2 * (p * p)));

    // If the next threshold would be imaginary, return with the current one.
    sqterm = w1 * w1 - w0 * w2;
    if (sqterm < 0) {
      return threshold;
    }

    // The updated threshold is the integer part of the solution of the quadratic equation.
    Tprev = threshold;
    temp = (w1 + Math.sqrt(sqterm)) / w0;

    if (Number.isNaN(temp)) {
      threshold = Tprev;
    } else {
      threshold = Math.floor(temp);
    }
  }
  return threshold;
}

// aux func

function sumA(y: Uint32Array, j: number): number {
  let x = 0;
  for (let i = 0; i <= j; i++) {
    x += y[i];
  }
  return x;
}

function sumB(y: Uint32Array, j: number): number {
  let x = 0;
  for (let i = 0; i <= j; i++) {
    x += i * y[i];
  }
  return x;
}

function sumC(y: Uint32Array, j: number): number {
  let x = 0;
  for (let i = 0; i <= j; i++) {
    x += i * i * y[i];
  }
  return x;
}
