import {Transform, ingest} from 'vega-dataflow';
import {inherits, isArray} from 'vega-util';
import {transform} from './Isocontour.js';
import {params} from './KDE2D.js';
import contours from './util/contours.js';
import density2D from './util/density2D.js';
import quantize from './util/quantize.js';

/**
 * Generate contours based on kernel-density estimation of point data.
 * @constructor
 * @param {object} params - The parameters for this operator.
 * @param {Array<number>} params.size - The dimensions [width, height] over which to compute contours.
 *  If the values parameter is provided, this must be the dimensions of the input data.
 *  If density estimation is performed, this is the output view dimensions in pixels.
 * @param {Array<number>} [params.values] - An array of numeric values representing an
 *  width x height grid of values over which to compute contours. If unspecified, this
 *  transform will instead attempt to compute contours for the kernel density estimate
 *  using values drawn from data tuples in the input pulse.
 * @param {function(object): number} [params.x] - The pixel x-coordinate accessor for density estimation.
 * @param {function(object): number} [params.y] - The pixel y-coordinate accessor for density estimation.
 * @param {function(object): number} [params.weight] - The data point weight accessor for density estimation.
 * @param {number} [params.cellSize] - Contour density calculation cell size.
 * @param {number} [params.bandwidth] - Kernel density estimation bandwidth.
 * @param {Array<number>} [params.thresholds] - Contour threshold array. If
 *   this parameter is set, the count and nice parameters will be ignored.
 * @param {number} [params.count] - The desired number of contours.
 * @param {boolean} [params.nice] - Boolean flag indicating if the contour
 *   threshold values should be automatically aligned to "nice"
 *   human-friendly values. Setting this flag may cause the number of
 *   thresholds to deviate from the specified count.
 * @param {boolean} [params.smooth] - Boolean flag indicating if the contour
 *   polygons should be smoothed using linear interpolation. The default is
 *   true. The parameter is ignored when using density estimation.
 */
export default function Contour(params) {
  Transform.call(this, null, params);
}

Contour.Definition = {
  'type': 'Contour',
  'metadata': {'generates': true},
  'params': [
    { 'name': 'size', 'type': 'number', 'array': true, 'length': 2, 'required': true },
    { 'name': 'values', 'type': 'number', 'array': true },
    { 'name': 'x', 'type': 'field' },
    { 'name': 'y', 'type': 'field' },
    { 'name': 'weight', 'type': 'field' },
    { 'name': 'cellSize', 'type': 'number' },
    { 'name': 'bandwidth', 'type': 'number' },
    { 'name': 'count', 'type': 'number' },
    { 'name': 'nice', 'type': 'boolean', 'default': false },
    { 'name': 'thresholds', 'type': 'number', 'array': true },
    { 'name': 'smooth', 'type': 'boolean', 'default': true }
  ]
};

inherits(Contour, Transform, {
  transform(_, pulse) {
    if (this.value && !pulse.changed() && !_.modified()) {
      return pulse.StopPropagation;
    }

    var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
        contour = contours().smooth(_.smooth !== false),
        values = _.values,
        thresh = _.thresholds || quantize(_.count || 10, _.nice, !!values),
        size = _.size, grid, post;

    if (!values) {
      values = pulse.materialize(pulse.SOURCE).source;
      grid = params(density2D(), _)(values, true);
      post = transform(grid, grid.scale || 1, grid.scale || 1, 0, 0);
      size = [grid.width, grid.height];
      values = grid.values;
    }

    thresh = isArray(thresh) ? thresh : thresh(values);
    values = contour.size(size)(values, thresh);
    if (post) values.forEach(post);

    if (this.value) out.rem = this.value;
    this.value = out.source = out.add = (values || []).map(ingest);

    return out;
  }
});
