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

import { EventEmitter } from '@angular/core';
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { Params } from '@angular/router';
import Collection from 'ol/Collection';
import Geometry from 'ol/geom/Geometry';
import Layer from 'ol/layer/Layer';
import type { ProjectionLike } from 'ol/proj';
import { BehaviorSubject, Observable } from 'rxjs';
import { GenericMapsModule } from '../../generic-maps/generic-maps.module';
import { OpenLayersMapComponent } from '../../generic-maps/open-layers-map/open-layers-map.component';
import { Projections, projectionTo } from '../../generic-maps/projection.utils';
import { LocationService } from '../location.service';
import { LsLocation } from '../model/lsLocation';
import { LsPosition } from '../model/lsPosition';
import { LocationMapDirective } from './location-map.directive';
import { LocationMapService } from './location-map.service';
import { LocationRendererDirective } from './location-renderer.directive';
import { LocationStyleService } from './location-style.service';

/**
 * @jest-environment node
 */
describe('LocationMapDirective', () => {
  class LocationServiceMock extends LocationService {
    locations: LsLocation[] = [
      {
        id: '4',
        name: 'Fe710 EG',
        type: 'floor',
        geoCoordinate: <LsPosition>{
          longitude: 9.17055,
          latitude: 48.81445,
          altitude: 270,
        },
        boundary: {
          type: 'Polygon',
          coordinates: [
            [
              [9.17029693722725, 48.8142892815782],
              [9.170848665081524, 48.81481394670166],
              [9.171023815870287, 48.81473790438878],
              [9.170382767915726, 48.814135618511386],
              [9.17029693722725, 48.8142892815782],
            ],
          ],
        },
      },
      {
        id: '5',
        name: 'Fe710 1. OG',
        type: 'floor',
        geoCoordinate: <LsPosition>{
          longitude: 9.17059,
          latitude: 48.81448,
          altitude: 274,
        },
        boundary: {
          type: 'Polygon',
          coordinates: [
            [
              [9.17029693722725, 48.8142892815782],
              [9.170848665081524, 48.81481394670166],
              [9.171023815870287, 48.81473790438878],
              [9.170382767915726, 48.814135618511386],
              [9.17029693722725, 48.8142892815782],
            ],
          ],
        },
      },
    ];
    private readonly _items = new BehaviorSubject<LsLocation[]>([]);

    constructor() {
      super();
      this.items = this._items.asObservable();
    }

    /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
    queryAll(params?: Params): Observable<LsLocation[]> {
      const selectedItems = this.locations;
      this.itemCount = selectedItems.length;
      setTimeout(() => this._items.next(selectedItems));
      return this.items;
    }
  }

  let mapComponentMock: OpenLayersMapComponent;
  let rendererMock: LocationRendererDirective;

  let directive: LocationMapDirective;

  beforeEach(
    waitForAsync(() => {
      rendererMock = <any>(<Partial<LocationRendererDirective>>{
        render: jest.fn(),
        updateStyles: jest.fn(),
      });

      const layerCollection: Collection<Layer> = new Collection();
      layerCollection.push(new Layer({}));
      const mapMock = {
        getLayers: jest.fn(),
        addInteraction: jest.fn(),
        removeInteraction: jest.fn(),
      };
      mapMock.getLayers.mockImplementation(() => layerCollection);
      mapComponentMock = <any>Object.assign(
        {
          addLayer: jest.fn(),
          addOsm: jest.fn(),
          map: jest.fn(),
          projectionFrom: jest.fn(),
        },
        {
          map: mapMock,
          addOSM: () => void 0,
          projectionFrom: <T extends Geometry>(sourceProjection: ProjectionLike) => {
            return projectionTo<T>(Projections.EPSG3857_WGS84PROJECTED).from(sourceProjection);
          },
          loaded: new EventEmitter<boolean>(),
        }
      );

      return TestBed.configureTestingModule({
        imports: [GenericMapsModule],
        providers: [
          LocationMapDirective,
          { provide: LocationService, useClass: LocationServiceMock },
          LocationMapService,
          LocationStyleService,
          { provide: OpenLayersMapComponent, useValue: mapComponentMock },
          { provide: LocationRendererDirective, useValue: rendererMock },
        ],
      }).compileComponents();
    })
  );

  beforeEach(fakeAsync(() => {
    directive = TestBed.inject(LocationMapDirective);
    directive.ngOnInit();
    directive.ngAfterViewInit();
    tick(1000);
  }));

  afterEach(
    waitForAsync(() => {
      directive.ngOnDestroy();
    })
  );

  it('should create an instance', () => {
    expect(directive).toBeTruthy();
  });

  it('should create a layer and render locations', () => {
    expect(mapComponentMock.addLayer).toHaveBeenCalledTimes(1);
    expect(rendererMock.render).toHaveBeenCalledTimes(1);
    expect(rendererMock.updateStyles).toHaveBeenCalledTimes(2); // render + initial selection
  });

  it('should render updated locations', fakeAsync(() => {
    expect(rendererMock.render).toHaveBeenCalledTimes(1);
    expect(rendererMock.updateStyles).toHaveBeenCalledTimes(2); // render + initial selection

    const locationService: LocationService = TestBed.inject(LocationService);
    locationService.queryAll();
    tick();

    expect(rendererMock.render).toHaveBeenCalledTimes(2);
    expect(rendererMock.updateStyles).toHaveBeenCalledTimes(3); // render + initial selection
  }));

  it('should style according to selection without rerendering', fakeAsync(() => {
    expect(rendererMock.render).toHaveBeenCalledTimes(1);
    expect(rendererMock.updateStyles).toHaveBeenCalledTimes(2); // render + initial selection

    const locationService: LocationServiceMock = TestBed.inject(LocationService) as any;
    const locationMapService: LocationMapService = TestBed.inject(LocationMapService);
    locationMapService.selectedLocations = [locationService.locations[0]];
    tick();

    expect(rendererMock.render).toHaveBeenCalledTimes(1);
    expect(rendererMock.updateStyles).toHaveBeenCalledTimes(3); // render + initial selection
  }));
});
