/*
 * Copyright (C) 2018 Robert Bosch GmbH Copyright (C) 2018 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { fakeAsync, tick } from '@angular/core/testing';
import Feature from 'ol/Feature';
import Polygon from 'ol/geom/Polygon';
import { SelectEvent } from 'ol/interaction/Select';
import Map from 'ol/Map';
import MapBrowserEvent from 'ol/MapBrowserEvent';
import { tap } from 'rxjs/operators';
import { SelectionInteractionMode } from './selection-interaction-mode';

/**
 * @jest-environment node
 */
describe('SelectionInteractionMode', () => {
  let mode: SelectionInteractionMode;
  let map: Map;

  const selectEvent = function (selected: Feature[], deselected: Feature[]) {
    const collectionOfSelectedFeatures = mode.select.getFeatures();
    deselected.forEach((f) => collectionOfSelectedFeatures.remove(f));
    selected.forEach((f) => collectionOfSelectedFeatures.push(f));
    const mapBrowserEvent: MapBrowserEvent = <MapBrowserEvent>{
      originalEvent: <Event>{},
    };
    mode.select.dispatchEvent(<SelectEvent>{
      type: 'select',
      selected: selected,
      deselected: deselected,
      mapBrowserEvent: <MapBrowserEvent>mapBrowserEvent,
    });
  };

  beforeEach(() => {
    map = <any>(<Partial<Map>>{
      addInteraction: jest.fn(),
      removeInteraction: jest.fn(),
    });

    mode = new SelectionInteractionMode();
    mode.setMap(map);
    mode.activate();
  });

  it('should be created', () => {
    expect(mode).toBeTruthy();
  });

  it('should emit change when collection of selected features is changed', fakeAsync(() => {
    let selection = [];
    mode.selected.subscribe((selected) => (selection = selected));

    const selectionBeingSet: Feature[] = [new Feature()];
    mode.selectedFeaturesCollection.extend(selectionBeingSet);
    mode.selectedFeaturesCollection.changed();

    tick(500);
    expect(selection).toEqual(selectionBeingSet);
  }));

  it('should emit change when selection is set', fakeAsync(() => {
    let selection = [];
    mode.selected.subscribe((selected) => (selection = selected));

    const selectionBeingSet: Feature[] = [new Feature()];
    mode.selectedFeatures = selectionBeingSet;

    tick(500);
    expect(selection).toEqual(selectionBeingSet);
  }));

  it('should not emit change debounced', fakeAsync(() => {
    let selection = [];
    let count = 0;
    mode.selected.pipe(tap(() => count++)).subscribe((selected) => (selection = selected));

    mode.selectedFeatures = [new Feature()];
    mode.selectedFeatures = [new Feature(), new Feature()];
    const selectionBeingSet: Feature[] = [new Feature()];
    mode.selectedFeatures = selectionBeingSet;

    tick(500);
    expect(selection).toEqual(selectionBeingSet);
    expect(count).toEqual(1);
  }));

  describe('in Round-Robin mode', () => {
    it('should emit when deselecting features', fakeAsync(() => {
      /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
      let selection = [];
      let selectionChangedCount = 0;
      mode.selected.pipe(tap(() => selectionChangedCount++)).subscribe((selected) => (selection = selected));
      const rrCondition = mode.roundRobinCondition;
      let rrCheckedCount = 0;
      mode.roundRobinCondition = (event) => {
        const result = rrCondition(event);
        if (result) {
          rrCheckedCount++;
        }
        return result;
      };

      const feature = new Feature();
      selectEvent([feature], []);
      tick(500);
      selectEvent([], [feature]);
      tick(500);

      expect(rrCheckedCount).toEqual(1);
      expect(selectionChangedCount).toEqual(2);
    }));

    it('should order features from smalles to largest', fakeAsync(() => {
      let selection = [];
      let selectionChangedCount = 0;
      mode.selected.pipe(tap(() => selectionChangedCount++)).subscribe((selected) => (selection = selected));

      const feature = new Feature(
        new Polygon([
          [
            [0, 0],
            [1, 1],
            [1, 2],
            [0, 0],
          ],
        ])
      );
      const featureLarger = new Feature(
        new Polygon([
          [
            [0, 0],
            [10, 1],
            [10, 2],
            [0, 0],
          ],
        ])
      );
      const featureAlsoLarger = new Feature(
        new Polygon([
          [
            [0, 0],
            [10, 1],
            [10, 2],
            [0, 0],
          ],
        ])
      );
      selectEvent([featureLarger, feature, featureAlsoLarger], []);
      tick(500);

      expect(selectionChangedCount).toEqual(1);
      expect(selection).toEqual([feature]);
    }));

    it('should select the first of the selected features upon first selection', fakeAsync(() => {
      let selection = [];
      let selectionChangedCount = 0;
      mode.selected.pipe(tap(() => selectionChangedCount++)).subscribe((selected) => (selection = selected));

      const feature = new Feature();
      selectEvent([feature, new Feature()], []);
      tick(500);

      expect(selectionChangedCount).toEqual(1);
      expect(selection).toEqual([feature]);
    }));

    it('should select the second of the selected features upon second selection', fakeAsync(() => {
      let selection = [];
      let selectionChangedCount = 0;
      mode.selected.pipe(tap(() => selectionChangedCount++)).subscribe((selected) => (selection = selected));

      const feature = new Feature();
      const feature2 = new Feature();
      selectEvent([feature, feature2], []);
      tick(500);
      selectEvent([feature2], []); // 'feature2' is not yet selected due to round robin
      tick(500);

      expect(selectionChangedCount).toEqual(2);
      expect(selection).toEqual([feature2]);
    }));

    it('should select the first of the selected features after the last one has been selected', fakeAsync(() => {
      let selection = [];
      let selectionChangedCount = 0;
      mode.selected.pipe(tap(() => selectionChangedCount++)).subscribe((selected) => (selection = selected));

      const feature = new Feature();
      const feature2 = new Feature();
      selectEvent([feature, feature2], []);
      tick(500);
      selectEvent([feature2], []); // 'feature2' is not yet selected due to round robin
      tick(500);
      selectEvent([feature], []); // 'feature' is not selected any more due to round robin
      tick(500);

      expect(selectionChangedCount).toEqual(3);
      expect(selection).toEqual([feature]);
    }));

    it('should clear selection memory upon deselection', fakeAsync(() => {
      let selection = [];
      let selectionChangedCount = 0;
      mode.selected.pipe(tap(() => selectionChangedCount++)).subscribe((selected) => (selection = selected));

      const feature = new Feature();
      const feature2 = new Feature();
      selectEvent([feature, feature2], []);
      tick(500);
      expect(selection).toEqual([feature]);

      selectEvent([], [feature]);
      tick(500);
      expect(selection).toEqual([]);

      selectEvent([feature, feature2], []);
      tick(500);

      expect(selectionChangedCount).toEqual(3);
      expect(selection).toEqual([feature]);
    }));

    it('should skip selected features after selection has been set externally', fakeAsync(() => {
      let selection = [];
      let selectionChangedCount = 0;
      mode.selected.pipe(tap(() => selectionChangedCount++)).subscribe((selected) => (selection = selected));

      const feature = new Feature();
      const feature2 = new Feature();
      const feature3 = new Feature();
      mode.selectedFeatures = [feature, feature2];
      tick(500);
      selectEvent([feature3], []);
      tick(500);

      expect(selectionChangedCount).toEqual(2);
      expect(selection).toEqual([feature3]);
    }));
  });
});
