/*
 * 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 VectorSource from 'ol/source/Vector';
import type { StyleFunction } from 'ol/style/Style';
import { BehaviorSubject, Observable } from 'rxjs';
import { GenericMapsModule } from '../../generic-maps/generic-maps.module';
import { OpenLayersLayerFactory } from '../../generic-maps/open-layers-map/open-layers-layer-factory';
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 { LocationMapService } from './location-map.service';
import { LocationRendererDirective } from './location-renderer.directive';
import { LocationStyleService } from './location-style.service';

describe('LocationRendererDirective', () => {
  let directive: LocationRendererDirective;

  let mapComponentMock: OpenLayersMapComponent;
  let locationMapService: LocationMapService;
  let locationStyleService: LocationStyleService;
  let locationVectorSource: VectorSource;
  let openLayersLayerFactory: OpenLayersLayerFactory;

  const 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],
          ],
        ],
      },
    },
  ];

  class LocationServiceMock extends LocationService {
    private readonly _items: BehaviorSubject<LsLocation[]>;
    private _lastQuery: Params | undefined;
    private readonly latencyInMs = 1000;

    private data = locations;

    constructor() {
      super();
      this._items = new BehaviorSubject<LsLocation[]>([]);
      this.items = this._items.asObservable();
    }

    queryAll(params?: Params): Observable<LsLocation[]> {
      this._lastQuery = params;
      const searchTerm = !!params && params.term ? params.term.trim().toLowerCase() : undefined;
      const selectedItems = this.data.filter((location) => {
        return !!searchTerm
          ? location.name.toLowerCase().indexOf(searchTerm) >= 0 || location.type.toLowerCase().indexOf(searchTerm) >= 0
          : true;
      });
      this.itemCount = selectedItems.length;
      setTimeout(() => this._items.next(selectedItems), this.latencyInMs);
      return this.items;
    }

    remove(entities: LsLocation[]) {
      this.data = this.data.filter((entity) => entities.indexOf(entity) < 0);
      this.queryAll();
    }
  }

  beforeEach(
    waitForAsync(() => {
      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: [
          LocationRendererDirective,
          { provide: LocationService, useClass: LocationServiceMock },
          LocationMapService,
          LocationStyleService,
          OpenLayersLayerFactory,
          { provide: OpenLayersMapComponent, useValue: mapComponentMock },
        ],
      }).compileComponents();
    })
  );

  beforeEach(fakeAsync(() => {
    locationMapService = TestBed.inject(LocationMapService);
    locationStyleService = TestBed.inject(LocationStyleService);
    openLayersLayerFactory = TestBed.inject(OpenLayersLayerFactory);

    locationVectorSource = openLayersLayerFactory.createVectorSource();
    mapComponentMock.addLayer(
      openLayersLayerFactory.createVectorLayer(locationVectorSource, 10, <any>{
        style: locationStyleService.locationStyleDefault,
        declutter: false,
      })
    );

    directive = TestBed.inject(LocationRendererDirective);
    directive.ngAfterViewInit();
    tick(1000);
  }));

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

  it('should create a boundary feature and a position feature for every location', () => {
    directive.render(locationVectorSource, locations);
    const features = locationVectorSource.getFeatures();
    // 2 locations with boundary and geoPosition, nothing else
    expect(features.length).toBe(4);

    expect(features.filter((f) => f.getId() === '4.position')).toBeTruthy();
    expect(features.filter((f) => f.getId() === '4.boundary')).toBeTruthy();
    expect(features.filter((f) => f.getId() === '5.position')).toBeTruthy();
    expect(features.filter((f) => f.getId() === '5.boundary')).toBeTruthy();
  });

  it('should update styles', () => {
    // rendering is required because it creates the features which are updated by updateStyles()
    directive.render(locationVectorSource, locations);
    directive.updateStyles(locations);

    locations.forEach((location) => {
      const features = locationMapService.featureToEntityMap.getFeaturesOfEntity(location);
      expect(features.length).toBe(2);

      const defaultStyleFunction = locationStyleService.getStyle(location, {
        isSelected: false,
      });
      expect(defaultStyleFunction).toBeDefined();
      expect(typeof defaultStyleFunction === 'function').toBeTruthy();
      const unselectedStyle = (defaultStyleFunction as StyleFunction)(features[0], 1.0);
      expect(unselectedStyle).toBe(locationStyleService.locationStyleDefault);

      const selectedStyleFunction = locationStyleService.getStyle(location, {
        isSelected: true,
      });
      // if updateStyles() worked properly we can see same styleFunction on the feature now
      expect(features[0].getStyle()).toBe(selectedStyleFunction);
    });
  });
});
