import {describe, test, assert} from 'vitest';
import {Point3D} from '../../src/math/point.js';
import {Size} from '../../src/image/size.js';
import {Spacing} from '../../src/image/spacing.js';
import {Geometry} from '../../src/image/geometry.js';
import {LabelingFilter} from '../../src/image/labelingFilter.js';
import {generateWorkerMessage} from '../../src/image/labelingThread.js';

/**
 * Tests for the 'image/labelingFilter.js' file.
 */

describe('image', () => {

  /**
   * Check if a number is within epsilon of another number.
   *
   * @param {number} A First number.
   * @param {number} B Number to compare.
   * @param {number} epsilon Max difference.
   * @returns {boolean} Are A and B close?
   */
  function closeTo(A, B, epsilon) {
    return Math.abs(A - B) <= epsilon;
  }

  /**
   * Tests for {@link LabelingFilter}.
   *
   * @function module:tests/image~labelingfilter-class
   */
  test('LabelingFilter class', () => {
    const labelingFilter = new LabelingFilter();

    // Unused, only needed to construct the geometry
    const imgOrigins = new Point3D(0, 0, 0);
    const imgSpacing = new Spacing([1, 1, 1]);

    // Basic labels
    const imgSize0 = new Size([3, 3, 2]);
    const imgGeometry0 =
      new Geometry(
        imgOrigins,
        imgSize0,
        imgSpacing
      );

    /* eslint-disable @stylistic/js/array-element-newline */
    const imgBuffer0 = new Uint8Array([
      0, 0, 0,
      0, 1, 1,
      0, 1, 1,
      0, 0, 0,
      0, 1, 1,
      0, 1, 1,
    ]);
    /* eslint-enable @stylistic/js/array-element-newline */

    const imgEvent0 = generateWorkerMessage(imgBuffer0, imgGeometry0);
    const labelEvent0 = labelingFilter.run(imgEvent0);
    const labels0 = labelEvent0.labels;

    assert.notStrictEqual(
      typeof labels0,
      'undefined',
      'Expected labels returned for basic label'
    );
    assert.equal(
      labels0.length,
      1,
      'Expected number of labels for basic label'
    );
    assert.equal(
      labels0[0].id,
      1,
      'Expected id #1 for basic label'
    );
    assert.equal(
      labels0[0].count,
      8,
      'Expected id #1 count for basic label'
    );
    assert.deepEqual(
      labels0[0].centroidIndex,
      [1.5, 1.5, 0.5],
      'Expected id #1 centroidIndex for basic label'
    );

    // Touching labels of different ids
    const imgSize1 = new Size([3, 3, 2]);
    const imgGeometry1 =
      new Geometry(
        imgOrigins,
        imgSize1,
        imgSpacing
      );
    /* eslint-disable @stylistic/js/array-element-newline */
    const imgBuffer1 = new Uint8Array([
      2, 2, 2,
      3, 1, 1,
      3, 1, 1,
      2, 2, 2,
      3, 1, 1,
      3, 1, 1,
    ]);
    /* eslint-enable @stylistic/js/array-element-newline */

    const imgEvent1 = generateWorkerMessage(imgBuffer1, imgGeometry1);
    const labelEvent1 = labelingFilter.run(imgEvent1);
    const labels1 = labelEvent1.labels;

    assert.notStrictEqual(
      typeof labels1,
      'undefined',
      'Expected labels returned for multiple labels'
    );
    assert.equal(
      labels1.length,
      3,
      'Expected number of labels for multiple labels'
    );

    assert.equal(
      labels1[0].id,
      2,
      'Expected id #2 for multiple labels'
    );
    assert.equal(
      labels1[0].count,
      6,
      'Expected id #2 count for multiple labels'
    );
    assert.deepEqual(
      labels1[0].centroidIndex,
      [1, 0, 0.5],
      'Expected id #2 centroidIndex for multiple labels'
    );

    assert.equal(
      labels1[1].id,
      3,
      'Expected id #3 for multiple labels'
    );
    assert.equal(
      labels1[1].count,
      4,
      'Expected id #3 count for multiple labels'
    );
    assert.deepEqual(
      labels1[1].centroidIndex,
      [0, 1.5, 0.5],
      'Expected id #3 centroidIndex for multiple labels'
    );

    assert.equal(
      labels1[2].id,
      1,
      'Expected id #1 for multiple labels'
    );
    assert.equal(
      labels1[2].count,
      8,
      'Expected id #1 count for multiple labels'
    );
    assert.deepEqual(
      labels1[2].centroidIndex,
      [1.5, 1.5, 0.5],
      'Expected id #1 centroidIndex for multiple labels'
    );


    // Larger labels for diameter calculation
    // (Diameters are weird on very small segments)
    const imgSize2 = new Size([6, 6, 3]);
    const imgGeometry2 =
      new Geometry(
        imgOrigins,
        imgSize2,
        imgSpacing
      );
    /* eslint-disable @stylistic/js/array-element-newline */
    const imgBuffer2 = new Uint8Array([
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 1, 1, 1, 0,
      0, 0, 1, 1, 1, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,

      0, 0, 0, 0, 0, 0,
      0, 1, 1, 1, 1, 0,
      0, 1, 1, 1, 1, 0,
      0, 0, 1, 1, 1, 0,
      0, 0, 1, 1, 1, 0,
      0, 0, 0, 0, 0, 0,

      0, 0, 0, 0, 0, 0,
      0, 1, 1, 1, 1, 0,
      0, 1, 1, 1, 1, 0,
      0, 0, 0, 1, 1, 0,
      0, 0, 0, 1, 1, 0,
      0, 0, 0, 0, 0, 0,
    ]);
    /* eslint-enable @stylistic/js/array-element-newline */

    const imgEvent2 = generateWorkerMessage(imgBuffer2, imgGeometry2);
    const labelEvent2 = labelingFilter.run(imgEvent2);
    const labels2 = labelEvent2.labels;

    assert.notStrictEqual(
      typeof labels2,
      'undefined',
      'Expected labels returned for label diameters'
    );
    assert.equal(
      labels2.length,
      1,
      'Expected number of labels for label diameters'
    );
    assert.equal(
      labels2[0].id,
      1,
      'Expected id #1 for label diameters'
    );
    assert.equal(
      labels2[0].count,
      32,
      'Expected id #1 count for label diameters'
    );

    assert.notStrictEqual(
      typeof labels2[0].diameters.minor,
      'undefined',
      'Expected minor diameter returned for label diameters'
    );

    const major2 = labels2[0].diameters.major;
    const minor2 = labels2[0].diameters.minor;

    assert.ok(
      closeTo(major2.diameter, 4.2426, 0.0001),
      'Expected major diameter value for label diameters'
    );
    assert.ok(
      closeTo(minor2.diameter, 2.8284, 0.0001),
      'Expected minor diameter value for label diameters'
    );
    assert.equal(
      labels2[0].height,
      3,
      'Expected height for label diameters'
    );
    assert.equal(
      labels2[0].largestSliceZ,
      1,
      'Expected largest slice Z for label diameters'
    );
    assert.equal(
      major2.offset1,
      43,
      'Expected major offset 1 for label diameters'
    );
    assert.equal(
      major2.offset2,
      64,
      'Expected major offset 2 for label diameters'
    );
    assert.equal(
      minor2.offset1,
      56,
      'Expected minor offset 1 for label diameters'
    );
    assert.equal(
      minor2.offset2,
      46,
      'Expected minor offset 2 for label diameters'
    );
  });

});