/**
 *
 * Dong, Jian, et al. "An algorithm of filtering noises in multi-beam data based on rolling circle transform." 2019 2nd International Conference on Sustainable Energy, Environment and Information Engineering (SEEIE 2019). Atlantis Press, 2019.
 * DONG Jian, PENG Rencan, ZHANG Lihua, WANG Zhijun. An Algorithm of Filtering Noises in Multi-beam Data Based on Rolling Circle Transform[J]. Geomatics and Information Science of Wuhan University, 2016, 41(1): 86-92. DOI: 10.13203/j.whugis20130757
 * @param data - data with x and y arrays.
 * @param options - options.
 */

import type { DataXY } from 'cheminfo-types';

import { xFindClosestIndex } from '../x/xFindClosestIndex.ts';

interface XYRollingCircleTransformOptions {
  /**
   * The radius of the circle used for the rolling circle transform.
   * It should be a positive number and in the 'x' unit
   * @default 1
   */
  radius?: number; // radius of the circle
  /**
   * Should the circle scan the top or the bottom of the XY data ?
   * @default 'top'
   */
  position?: 'top' | 'bottom'; // position of the circle relative to the peak
  /**
   * Should we keep the Y centers of the circles or should we move the centers
   * so that it touches the XY data ?
   * @default: true
   */
  shifted?: boolean; // if true, the y values will be shifted to fit the circle center
}

export function xyRollingCircleTransform(
  data: DataXY,
  options: XYRollingCircleTransformOptions = {},
): Float64Array {
  const { x } = data;
  let { y } = data;
  const { radius = 1, position = 'top', shifted = true } = options;

  if (position !== 'top' && position !== 'bottom') {
    throw new Error(`Invalid position: ${String(position)}`);
  }

  if (position === 'bottom') {
    y = y.slice();
    for (let i = 0; i < y.length; i++) {
      y[i] = -y[i];
    }
  }

  if (x.length === 0 || y.length === 0) {
    return new Float64Array();
  }
  const yCenters = new Float64Array(x.length);
  for (let i = 0; i < x.length; i++) {
    const x0 = x[i]; // x center of the current circle
    const fromX = xFindClosestIndex(x, x0 - radius);
    const toX = xFindClosestIndex(x, x0 + radius);

    // for the circle radius we need to evaluate the minimal vertical shift

    const y0 = y[i] + radius; // y center of the circle on top of peak
    let yShift = y0; // this is the minimal possible shift
    for (let j = fromX; j <= toX; j++) {
      const currentX = x[j];
      if (currentX < x0 - radius || currentX > x0 + radius) {
        continue;
      }
      const currentMinYShift =
        y[j] + Math.sqrt(radius ** 2 - (currentX - x0) ** 2);
      if (currentMinYShift > yShift) {
        yShift = currentMinYShift;
      }
    }
    yCenters[i] = yShift;
  }
  if (!shifted) {
    for (let i = 0; i < yCenters.length; i++) {
      yCenters[i] -= radius;
    }
  }

  if (position === 'bottom') {
    for (let i = 0; i < yCenters.length; i++) {
      yCenters[i] = -yCenters[i];
    }
  }

  return yCenters;
}
