/*
 * 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 { cloneDeep } from 'lodash-es';
import Collection from 'ol/Collection';
import Feature from 'ol/Feature';
import Geometry from 'ol/geom/Geometry';
import Layer from 'ol/layer/Layer';
import type { ProjectionLike } from 'ol/proj';
import { GenericMapsModule } from '../generic-maps/generic-maps.module';
import { OpenLayersLayerFactory, SelectionInteractionMode } from '../generic-maps/index';
import { OpenLayersMapComponent } from '../generic-maps/open-layers-map/open-layers-map.component';
import { Projections, projectionTo } from '../generic-maps/projection.utils';
import { SELECT_INTERACTION } from './interaction.tokens';
import { LocationEditingDirective } from './location-editing.directive';
import { LocationService } from './location.service';
import { LocationMapService } from './map/location-map.service';
import { LocationStyleService } from './map/location-style.service';
import { LsLocation } from './model/lsLocation';
import { LsPosition } from './model/lsPosition';

describe('LocationEditingDirective', () => {
  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],
          ],
        ],
      },
    },
  ];

  let directive: LocationEditingDirective;

  let mapComponentMock: OpenLayersMapComponent;
  let locationMapService: LocationMapService;
  let selectionInteractionMode;

  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>(),
        }
      );

      selectionInteractionMode = new SelectionInteractionMode();

      return TestBed.configureTestingModule({
        imports: [GenericMapsModule],
        providers: [
          LocationService,
          LocationMapService,
          LocationStyleService,
          OpenLayersLayerFactory,
          { provide: OpenLayersMapComponent, useValue: mapComponentMock },
          { provide: SELECT_INTERACTION, useValue: () => selectionInteractionMode },
          LocationEditingDirective,
        ],
      }).compileComponents();
    })
  );

  beforeEach(fakeAsync(() => {
    locationMapService = TestBed.inject(LocationMapService);
    directive = TestBed.inject(LocationEditingDirective);
    directive.ngAfterViewInit();

    // fill feature-to-entity map
    locations.forEach((l) => {
      const features = [];
      {
        const feature = new Feature({});
        feature.setId('' + l.id + '.boundary');
        features.push(feature);
      }
      {
        const feature = new Feature({});
        feature.setId('' + l.id + '.position');
        features.push(feature);
      }
      locationMapService.featureToEntityMap.set(features, l);
    });

    mapComponentMock.loaded.next(true);
    tick(1000);
  }));

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

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

  it('should remove boundary', () => {
    locationMapService.locationBeingEdited = locations[0];
    expect(locationMapService.locationBeingEdited.boundary).toBeDefined();

    directive.removeCurrentBoundary();

    expect(locationMapService.locationBeingEdited).not.toEqual(locations[0]);
    expect(locationMapService.locationBeingEdited.boundary).toBeUndefined();
  });

  it('should not fail ot remove boundary when none exists', () => {
    const testLocation = cloneDeep(locations[0]);
    delete testLocation.boundary;
    locationMapService.locationBeingEdited = testLocation;

    directive.removeCurrentBoundary();

    expect(locationMapService.locationBeingEdited.boundary).toBeUndefined();
  });

  it('should set boundary', () => {
    const testLocation = cloneDeep(locations[0]);
    delete testLocation.boundary;
    locationMapService.locationBeingEdited = testLocation;
    expect(locationMapService.locationBeingEdited.boundary).toBeUndefined();

    directive.setCurrentBoundary({
      type: 'Polygon',
      coordinates: [
        [
          [11, 22],
          [11, 23],
          [12, 23],
          [11, 22],
        ],
      ],
    });

    expect(locationMapService.locationBeingEdited.boundary).toBeDefined();
  });

  it('should unset boundary', () => {
    locationMapService.locationBeingEdited = cloneDeep(locations[0]);
    expect(locationMapService.locationBeingEdited.boundary).toBeDefined();
    jest.spyOn(directive, 'removeBoundary');

    directive.setCurrentBoundary(undefined);

    expect(locationMapService.locationBeingEdited.boundary).toBeUndefined();
    expect(directive.removeBoundary).toHaveBeenCalled();
  });

  it('should remove position keeping the altitude', () => {
    locationMapService.locationBeingEdited = locations[0];
    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.latitude).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.altitude).toBeDefined();

    directive.removeCurrentPosition();

    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toBeUndefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.latitude).toBeUndefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.altitude).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.altitude).toEqual(locations[0].geoCoordinate.altitude);
    expect(locations[0].geoCoordinate.longitude).toBeDefined();
    expect(locations[0].geoCoordinate.latitude).toBeDefined();
  });

  it('should remove position keeping the altitude 0', () => {
    const testLocation = cloneDeep(locations[0]);
    testLocation.geoCoordinate.altitude = 0;
    locationMapService.locationBeingEdited = testLocation;

    directive.removeCurrentPosition();

    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toBeUndefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.latitude).toBeUndefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.altitude).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.altitude).toEqual(testLocation.geoCoordinate.altitude);
    expect(testLocation.geoCoordinate.longitude).toBeDefined();
    expect(testLocation.geoCoordinate.latitude).toBeDefined();
  });

  it('should set position keeping the altitude 0', () => {
    const testLocation = cloneDeep(locations[0]);
    testLocation.geoCoordinate.altitude = 0;
    delete testLocation.geoCoordinate.longitude;
    delete testLocation.geoCoordinate.latitude;
    locationMapService.locationBeingEdited = testLocation;

    directive.setCurrentPosition({ longitude: 11, latitude: 22 });

    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toEqual(11);
    expect(locationMapService.locationBeingEdited.geoCoordinate.latitude).toEqual(22);
    expect(locationMapService.locationBeingEdited.geoCoordinate.altitude).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.altitude).toEqual(testLocation.geoCoordinate.altitude);
    expect(testLocation.geoCoordinate.longitude).toBeUndefined();
    expect(testLocation.geoCoordinate.latitude).toBeUndefined();
  });

  it('should unset position keeping altitude', () => {
    locationMapService.locationBeingEdited = cloneDeep(locations[0]);
    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeDefined();
    jest.spyOn(directive, 'removePosition');

    directive.setCurrentPosition(undefined);

    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeDefined();
    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toBeUndefined();
    expect(directive.removePosition).toHaveBeenCalled();
  });

  it('should unset position without altitude', () => {
    const testLocation = cloneDeep(locations[0]);
    delete testLocation.geoCoordinate.altitude;
    locationMapService.locationBeingEdited = testLocation;
    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeDefined();
    jest.spyOn(directive, 'removePosition');

    directive.setCurrentPosition(undefined);

    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeUndefined();
    expect(directive.removePosition).toHaveBeenCalled();
  });

  it('should not fail to remove position when none exists', () => {
    const testLocation = cloneDeep(locations[0]);
    delete testLocation.geoCoordinate;
    locationMapService.locationBeingEdited = testLocation;

    directive.removeCurrentPosition();

    expect(locationMapService.locationBeingEdited.geoCoordinate).toBeUndefined();
  });

  it('should set geometries', () => {
    locationMapService.locationBeingEdited = cloneDeep(locations[0]);

    directive.setCurrentGeometries({
      geoCoordinate: { longitude: 11.24535, latitude: 22 },
      boundary: { type: 'Polygon', coordinates: [[[11.246454, 22]]] },
    });

    expect(locationMapService.locationBeingEdited.boundary.coordinates[0][0][0]).toEqual(11.246454);
    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toEqual(11.24535);
  });

  it('should set geometries consisting only of position', () => {
    locationMapService.locationBeingEdited = cloneDeep(locations[0]);
    jest.spyOn(directive, 'removeBoundary');

    directive.setCurrentGeometries({ geoCoordinate: { longitude: 11.24535, latitude: 22 } });

    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toEqual(11.24535);
    expect(locationMapService.locationBeingEdited.boundary).toBeUndefined();
    expect(directive.removeBoundary).toHaveBeenCalled();
  });

  it('should set geometries consisting only of boundary', () => {
    locationMapService.locationBeingEdited = cloneDeep(locations[0]);
    jest.spyOn(directive, 'removePosition');

    directive.setCurrentGeometries({ boundary: { type: 'Polygon', coordinates: [[[11.246454, 22]]] } });

    expect(locationMapService.locationBeingEdited.boundary.coordinates[0][0][0]).toEqual(11.246454);
    expect(locationMapService.locationBeingEdited.geoCoordinate.longitude).toBeUndefined();
    expect(directive.removePosition).toHaveBeenCalled();
  });
});
