/**
 * Runs in a worker thread started by `node-match` demo app
 *
 */

const threads = require('worker_threads');

let debug = false;

/** @type SharedArrayBuffer */
let buffer;
/** @type Float32Array */
let view;
let threshold = 0;
let records = 0;

const descLength = 1024; // descriptor length in bytes

function distance(descBuffer, index, options = { order: 2, multiplier: 20 }) {
  const descriptor = new Float32Array(descBuffer);
  let sum = 0;
  for (let i = 0; i < descriptor.length; i++) {
    const diff = (options.order === 2) ? (descriptor[i] - view[index * descLength + i]) : (Math.abs(descriptor[i] - view[index * descLength + i]));
    sum += (options.order === 2) ? (diff * diff) : (diff ** options.order);
  }
  return (options.multiplier || 20) * sum;
}

function match(descBuffer, options = { order: 2, multiplier: 20 }) {
  let best = Number.MAX_SAFE_INTEGER;
  let index = -1;
  for (let i = 0; i < records; i++) {
    const res = distance(descBuffer, i, { order: options.order, multiplier: options.multiplier });
    if (res < best) {
      best = res;
      index = i;
    }
    if (best < threshold || best === 0) break; // short circuit
  }
  best = (options.order === 2) ? Math.sqrt(best) : best ** (1 / options.order);
  const similarity = Math.round(100 * Math.max(0, 100 - best) / 100.0) / 100;
  return { index, distance: best, similarity };
}

threads.parentPort?.on('message', (msg) => {
  if (typeof msg.descriptor !== 'undefined') { // actual work order to find a match
    const t0 = performance.now();
    const result = match(msg.descriptor);
    const t1 = performance.now();
    threads.parentPort?.postMessage({ request: msg.request, time: Math.trunc(t1 - t0), ...result });
    return; // short circuit
  }
  if (msg instanceof SharedArrayBuffer) { // called only once to receive reference to shared array buffer
    buffer = msg;
    view = new Float32Array(buffer); // initialize f64 view into buffer
    if (debug) threads.parentPort?.postMessage(`buffer: ${buffer.byteLength}`);
  }
  if (typeof msg.records !== 'undefined') { // recived every time when number of records changes
    records = msg.records;
    if (debug) threads.parentPort?.postMessage(`records: ${records}`);
  }
  if (typeof msg.debug !== 'undefined') { // set verbose logging
    debug = msg.debug;
    // if (debug) threads.parentPort?.postMessage(`debug: ${debug}`);
  }
  if (typeof msg.threshold !== 'undefined') { // set minimum similarity threshold
    threshold = msg.threshold;
    // if (debug) threads.parentPort?.postMessage(`threshold: ${threshold}`);
  }
  if (typeof msg.shutdown !== 'undefined') { // got message to close worker
    if (debug) threads.parentPort?.postMessage('shutting down');
    process.exit(0); // eslint-disable-line no-process-exit
  }
});

if (debug) threads.parentPort?.postMessage('started');
