import { expect } from 'chai';
import { DummyManager } from './dummy-manager';
import * as bqplot from '../index';
import { create_figure_scatter } from './widget-utils';

describe('scatter mega >', () => {
  beforeEach(async function () {
    this.manager = new DummyManager({ bqplot: bqplot });
  });

  it('basics', async function () {
    const x_array = new Float32Array([0, 1]);
    const y_array = new Float32Array([2, 3]);
    const x = { dtype: 'float32', value: new DataView(x_array.buffer) };
    const y = { dtype: 'float32', value: new DataView(y_array.buffer) };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;

    expect(scatter.x.array).to.deep.equal(x_array);
    expect(scatter.x.itemSize).to.equal(1);
    expect(scatter.x.meshPerAttribute).to.equal(1);

    expect(scatter.x_previous.array).to.deep.equal(x_array);
    expect(scatter.x_previous.itemSize).to.equal(1);
    expect(scatter.x_previous.meshPerAttribute).to.equal(1);

    expect(scatter.y.array).to.deep.equal(y_array);
    expect(scatter.y.itemSize).to.equal(1);
    expect(scatter.y.meshPerAttribute).to.equal(1);

    expect(scatter.y_previous.array).to.deep.equal(y_array);
    expect(scatter.y_previous.itemSize).to.equal(1);
    expect(scatter.y_previous.meshPerAttribute).to.equal(1);

    const new_x = new Float32Array([1, 2]);
    scatter.model.set('x', new_x);
    expect(scatter.x.array).to.deep.equal(new_x);
    expect(scatter.x_previous.array).to.deep.equal(x_array);

    const new_y = new Float32Array([34, 1]);
    scatter.model.set('y', new_y);
    expect(scatter.y.array).to.deep.equal(new_y);
    expect(scatter.y_previous.array).to.deep.equal(y_array);
  });

  it('size', async function () {
    const x = {
      dtype: 'float32',
      value: new DataView(new Float32Array([0, 1]).buffer),
    };
    const y = {
      dtype: 'float32',
      value: new DataView(new Float32Array([2, 3]).buffer),
    };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;
    const default_size = scatter.model.get('default_size');

    expect(scatter.size.array).to.deep.equal(new Float32Array([default_size]));
    expect(scatter.size.meshPerAttribute).to.equal(2); // Same size fore the two markers
    expect(scatter.size_previous.array).to.deep.equal(scatter.size.array);
    expect(scatter.size_previous.meshPerAttribute).to.equal(2);

    scatter.model.set('default_size', default_size * 2);
    expect(scatter.size.array).to.deep.equal(
      new Float32Array([default_size * 2])
    );
    expect(scatter.size.meshPerAttribute).to.equal(2);
    expect(scatter.size_previous.array).to.deep.equal(
      new Float32Array([default_size])
    );
    expect(scatter.size_previous.meshPerAttribute).to.equal(2);

    // We push an array, the previous should be a scalar
    const new_size = new Float32Array([0, 0.5]);
    scatter.model.set('size', new_size);
    expect(scatter.size.array).to.deep.equal(new_size);
    expect(scatter.size.meshPerAttribute).to.equal(1); // Each marker has its own size
    expect(scatter.size_previous.array).to.deep.equal(
      new Float32Array([default_size * 2])
    );
    expect(scatter.size_previous.meshPerAttribute).to.equal(2);

    // We push an array that is too short, default_size should be used
    const new_size2 = new Float32Array([0]);
    scatter.model.set('size', new_size2);
    expect(scatter.size.array).to.deep.equal(
      new Float32Array([0, default_size * 2])
    );
    expect(scatter.size.meshPerAttribute).to.equal(1);
    expect(scatter.size_previous.array).to.deep.equal(new_size);
    expect(scatter.size_previous.meshPerAttribute).to.equal(1);
  });

  it('opacity', async function () {
    const x = {
      dtype: 'float32',
      value: new DataView(new Float32Array([0, 1, 2]).buffer),
    };
    const y = {
      dtype: 'float32',
      value: new DataView(new Float32Array([2, 3, 3]).buffer),
    };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;

    const opacities = new Float32Array([1]);
    expect(scatter.opacity.array).to.deep.equal(opacities);
    expect(scatter.opacity.meshPerAttribute).to.equal(3);
    expect(scatter.opacity_previous.array).to.deep.equal(opacities);
    expect(scatter.opacity_previous.meshPerAttribute).to.equal(3);

    const opacities2 = new Float32Array([0.5]);
    scatter.model.set('opacities', opacities2);
    expect(scatter.opacity.meshPerAttribute).to.equal(3);
    expect(scatter.opacity.array).to.deep.equal(opacities2);
    expect(scatter.opacity_previous.array).to.deep.equal(opacities);
    expect(scatter.opacity.meshPerAttribute).to.equal(3);

    // We push an array of size 2 for the opacities. We have 3 markers
    // so the opacities should be used periodicly
    scatter.model.set('opacities', new Float32Array([0.5, 1.0]));
    expect(scatter.opacity.array).to.deep.equal(
      new Float32Array([0.5, 1.0, 0.5])
    );
    expect(scatter.opacity.meshPerAttribute).to.equal(1);
    expect(scatter.opacity_previous.meshPerAttribute).to.equal(3);
    expect(scatter.opacity_previous.array).to.deep.equal(opacities2);

    const new_opacity = new Float32Array([0.5, 1.0, 0.8]);
    scatter.model.set('opacity', new_opacity);
    expect(scatter.opacity.array).to.deep.equal(new_opacity);
    expect(scatter.opacity.meshPerAttribute).to.equal(1);
    expect(scatter.opacity_previous.array).to.deep.equal(
      new Float32Array([0.5, 1.0, 0.5])
    );
    expect(scatter.opacity_previous.meshPerAttribute).to.equal(1);

    // We push an array that is too short, opacities should be used
    scatter.model.set('opacity', new Float32Array([0.8]));
    expect(scatter.opacity.array).to.deep.equal(
      new Float32Array([0.8, 1.0, 0.5])
    );
    expect(scatter.opacity.meshPerAttribute).to.equal(1);
    expect(scatter.opacity_previous.array).to.deep.equal(new_opacity);
    expect(scatter.opacity_previous.meshPerAttribute).to.equal(1);
  });

  it('colors', async function () {
    const x = {
      dtype: 'float32',
      value: new DataView(new Float32Array([0, 1, 2, 1]).buffer),
    };
    const y = {
      dtype: 'float32',
      value: new DataView(new Float32Array([2, 3, 4, 3]).buffer),
    };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;

    const red = new Float32Array([1, 0, 0]);
    const blue = new Float32Array([0, 0, 1]);
    const steelblue = new Float32Array([
      0.27450981736183167, 0.5098039507865906, 0.7058823704719543,
    ]);

    expect(scatter.color.array).to.deep.equal(steelblue);
    expect(scatter.color.itemSize).to.equal(3);
    expect(scatter.color.meshPerAttribute).to.equal(4);
    expect(scatter.scatter_material.defines['USE_COLORMAP']).to.be.false;

    scatter.model.set('colors', ['red']);
    expect(scatter.color.array).to.deep.equal(red);
    expect(scatter.color.itemSize).to.equal(3);
    expect(scatter.color.meshPerAttribute).to.equal(4);
    expect(scatter.scatter_material.defines['USE_COLORMAP']).to.be.false;

    // We push an array of two colors. We have 4 markers
    // so the colors should be used periodicly
    scatter.model.set('colors', ['red', 'blue']);
    expect(scatter.color.array).to.deep.equal(
      new Float32Array([
        red[0],
        red[1],
        red[2],
        blue[0],
        blue[1],
        blue[2],
        red[0],
        red[1],
        red[2],
        blue[0],
        blue[1],
        blue[2],
      ])
    );
    expect(scatter.color.itemSize).to.equal(3);
    expect(scatter.color.meshPerAttribute).to.equal(1);
    expect(scatter.scatter_material.defines['USE_COLORMAP']).to.be.false;

    const new_color = new Float32Array([0.5, 1, 0.3, 0.1]);
    scatter.model.set('color', new_color);
    expect(scatter.color.array).to.deep.equal(new_color);
    expect(scatter.color.itemSize).to.equal(1);
    expect(scatter.color.meshPerAttribute).to.equal(1);
    expect(scatter.scatter_material.defines['USE_COLORMAP']).to.be.true;
    expect(
      scatter.scatter_material.uniforms['domain_color'].value
    ).to.deep.equal([new_color[3], new_color[1]]);

    const new_color2 = new Float32Array([0.5, 2, 0.6, -0.3]);
    scatter.model.set('color', new_color2);
    expect(
      scatter.scatter_material.uniforms['domain_color'].value
    ).to.deep.equal([new_color2[3], new_color2[1]]);

    scatter.model.set('color', null);
    // TODO: now the min/max are undefined, or is there a good default?
    scatter.scales.color.model.set('min', 1);
    scatter.scales.color.model.set('max', 3);
    expect(
      scatter.scatter_material.uniforms['domain_color'].value
    ).to.deep.equal([1, 3]);
    scatter.scales.color.model.set('min', -1);
    expect(
      scatter.scatter_material.uniforms['domain_color'].value
    ).to.deep.equal([-1, 3]);
  });

  it('selection', async function () {
    const x = {
      dtype: 'float32',
      value: new DataView(new Float32Array([0, 1]).buffer),
    };
    const y = {
      dtype: 'float32',
      value: new DataView(new Float32Array([2, 3]).buffer),
    };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;

    expect(scatter.selected.array).to.deep.equal(new Float32Array([0]));
    expect(scatter.selected.itemSize).to.equal(1);
    expect(scatter.selected.meshPerAttribute).to.equal(2);
    expect(scatter.scatter_material.uniforms['has_selection'].value).to.be
      .false;

    // 2 is 'out of bounds', we only have two markers
    scatter.model.set('selected', [1, 2]);
    expect(scatter.selected.array).to.deep.equal(new Float32Array([0, 1]));
    expect(scatter.selected.itemSize).to.equal(1);
    expect(scatter.selected.meshPerAttribute).to.equal(1);
    expect(scatter.scatter_material.uniforms['has_selection'].value).to.be.true;

    scatter.model.set('selected', [0, 1]);
    expect(scatter.selected.array).to.deep.equal(new Float32Array([1, 1]));
    expect(scatter.scatter_material.uniforms['has_selection'].value).to.be.true;

    scatter.model.set('selected', [0]);
    expect(scatter.selected.array).to.deep.equal(new Float32Array([1, 0]));
    expect(scatter.scatter_material.uniforms['has_selection'].value).to.be.true;
  });

  it('rotation', async function () {
    const x = {
      dtype: 'float32',
      value: new DataView(new Float32Array([0, 1]).buffer),
    };
    const y = {
      dtype: 'float32',
      value: new DataView(new Float32Array([2, 3]).buffer),
    };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;

    expect(scatter.rotation.array).to.deep.equal(new Float32Array([0]));
    expect(scatter.rotation.itemSize).to.equal(1);
    expect(scatter.rotation.meshPerAttribute).to.equal(2);

    scatter.model.set('rotation', [60, 180]);
    expect(scatter.rotation.array).to.deep.equal(new Float32Array([60, 180]));
    expect(scatter.rotation.itemSize).to.equal(1);
    expect(scatter.rotation.meshPerAttribute).to.equal(1);
  });

  it('markers number increase', async function () {
    const x_array = new Float32Array([0, 1]);
    const y_array = new Float32Array([2, 3]);
    const x = { dtype: 'float32', value: new DataView(x_array.buffer) };
    const y = { dtype: 'float32', value: new DataView(y_array.buffer) };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;

    const steelblue = new Float32Array([
      0.27450981736183167, 0.5098039507865906, 0.7058823704719543,
    ]);
    const default_size = scatter.model.get('default_size');
    const opacities = new Float32Array([1]);

    expect(scatter.markers_number).to.equal(2);
    expect(scatter.instanced_geometry.maxInstancedCount).to.equal(2);

    // Checking size/opacity/color/selected attributes
    expect(scatter.size.array).to.deep.equal(new Float32Array([default_size]));
    expect(scatter.size.meshPerAttribute).to.equal(2);

    expect(scatter.opacity.array).to.deep.equal(opacities);
    expect(scatter.opacity.meshPerAttribute).to.equal(2);

    expect(scatter.color.array).to.deep.equal(steelblue);
    expect(scatter.color.itemSize).to.equal(3);
    expect(scatter.color.meshPerAttribute).to.equal(2);

    expect(scatter.selected.array).to.deep.equal(new Float32Array([0]));
    expect(scatter.selected.itemSize).to.equal(1);
    expect(scatter.selected.meshPerAttribute).to.equal(2);

    // Increasing x length
    // `x_previous` should have the right new size, but the markers
    // should not increase until y has increased
    const new_x = new Float32Array([3, 2, 10]);
    scatter.model.set('x', new_x);
    expect(scatter.x.array).to.deep.equal(new_x);
    expect(scatter.x_previous.array).to.deep.equal(
      new Float32Array([0, 1, 10])
    );

    expect(scatter.markers_number).to.equal(2);
    expect(scatter.instanced_geometry.maxInstancedCount).to.equal(2);

    // Increasing y length
    // the markers number should be updated
    const new_y = new Float32Array([4, 2, 2]);
    scatter.model.set('y', new_y);
    expect(scatter.x.array).to.deep.equal(new_x);
    expect(scatter.x_previous.array).to.deep.equal(
      new Float32Array([0, 1, 10])
    );

    expect(scatter.y.array).to.deep.equal(new_y);
    expect(scatter.y_previous.array).to.deep.equal(new Float32Array([2, 3, 2]));

    expect(scatter.markers_number).to.equal(3);
    expect(scatter.instanced_geometry.maxInstancedCount).to.equal(3);

    // Checking size/opacity/color/selected attributes
    expect(scatter.size.array).to.deep.equal(new Float32Array([default_size]));
    expect(scatter.size.meshPerAttribute).to.equal(3);

    expect(scatter.opacity.array).to.deep.equal(opacities);
    expect(scatter.opacity.meshPerAttribute).to.equal(3);

    expect(scatter.color.array).to.deep.equal(steelblue);
    expect(scatter.color.itemSize).to.equal(3);
    expect(scatter.color.meshPerAttribute).to.equal(3);

    expect(scatter.selected.array).to.deep.equal(new Float32Array([0]));
    expect(scatter.selected.itemSize).to.equal(1);
    expect(scatter.selected.meshPerAttribute).to.equal(3);
  });

  it('markers number decrease', async function () {
    const x_array = new Float32Array([0, 1]);
    const y_array = new Float32Array([2, 3]);
    const x = { dtype: 'float32', value: new DataView(x_array.buffer) };
    const y = { dtype: 'float32', value: new DataView(y_array.buffer) };
    const objects = await create_figure_scatter(this.manager, x, y, true);
    const scatter = objects.scatter;

    const steelblue = new Float32Array([
      0.27450981736183167, 0.5098039507865906, 0.7058823704719543,
    ]);
    const default_size = scatter.model.get('default_size');
    const opacities = new Float32Array([1]);

    expect(scatter.markers_number).to.equal(2);
    expect(scatter.instanced_geometry.maxInstancedCount).to.equal(2);

    // Checking size/opacity/color/selected attributes
    expect(scatter.size.array).to.deep.equal(new Float32Array([default_size]));
    expect(scatter.size.meshPerAttribute).to.equal(2);

    expect(scatter.opacity.array).to.deep.equal(opacities);
    expect(scatter.opacity.meshPerAttribute).to.equal(2);

    expect(scatter.color.array).to.deep.equal(steelblue);
    expect(scatter.color.itemSize).to.equal(3);
    expect(scatter.color.meshPerAttribute).to.equal(2);

    expect(scatter.selected.array).to.deep.equal(new Float32Array([0]));
    expect(scatter.selected.itemSize).to.equal(1);
    expect(scatter.selected.meshPerAttribute).to.equal(2);

    // Decreasing x length
    // it is fine if `x_previous` has not the right size, but the markers number
    // should decrease
    const new_x = new Float32Array([3]);
    scatter.model.set('x', new_x);
    expect(scatter.x.array).to.deep.equal(new_x);
    expect(scatter.x_previous.array).to.deep.equal(new Float32Array([0, 1]));

    expect(scatter.markers_number).to.equal(1);
    expect(scatter.instanced_geometry.maxInstancedCount).to.equal(1);

    // Checking size/opacity/color/selected attributes
    // The meshPerAttribute parameter should not change, as it would trigger
    // a creation of a new attribute (ThreeJS does not support dynamic
    // meshPerAttribute change on an Attribute)
    expect(scatter.size.array).to.deep.equal(new Float32Array([default_size]));
    expect(scatter.size.meshPerAttribute).to.equal(2);

    expect(scatter.opacity.array).to.deep.equal(opacities);
    expect(scatter.opacity.meshPerAttribute).to.equal(2);

    expect(scatter.color.array).to.deep.equal(steelblue);
    expect(scatter.color.itemSize).to.equal(3);
    expect(scatter.color.meshPerAttribute).to.equal(2);

    expect(scatter.selected.array).to.deep.equal(new Float32Array([0]));
    expect(scatter.selected.itemSize).to.equal(1);
    expect(scatter.selected.meshPerAttribute).to.equal(2);

    // Decreasing y length
    const new_y = new Float32Array([4]);
    scatter.model.set('y', new_y);
    expect(scatter.x.array).to.deep.equal(new_x);
    expect(scatter.x_previous.array).to.deep.equal(new Float32Array([0, 1]));

    expect(scatter.y.array).to.deep.equal(new_y);
    expect(scatter.y_previous.array).to.deep.equal(new Float32Array([2, 3]));

    expect(scatter.markers_number).to.equal(1);
    expect(scatter.instanced_geometry.maxInstancedCount).to.equal(1);
  });
});
