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

import { TestBed, waitForAsync } from '@angular/core/testing';
import { BOSCH_DDS_COLORS } from '@bci-web-core/core';
import Feature from 'ol/Feature';
import { Style, Text } from 'ol/style';
import { StyleFunction } from 'ol/style/Style';
import { OpenLayersStyleFactory } from '../../generic-maps/open-layers-map/open-layers-style-factory';
import { LsLocation } from '../model/lsLocation';
import { LsPosition } from '../model/lsPosition';
import { LocationStyleCustomized } from './location-style-customized';
import { LocationStyle, LocationStyleService } from './location-style.service';

describe('LocationStyleService', () => {
  let service: LocationStyleService;

  class TestLocationStyleService extends LocationStyleService {
    constructor() {
      super();
    }

    pushStyles(): LocationStyle[] {
      this.locationStyles.push(
        this.createStyle('building', this.createPlainLocationStyle(BOSCH_DDS_COLORS.boschBlue75, 15, 0.02), 1)
      );
      return this.locationStyles.slice();
    }

    private createStyle(type: string, styleInstance: Style, resolutionLimitInKm = 9999999999) {
      return new LocationStyleCustomized(styleInstance, {
        applies: (location, metadata) => location.type === type && !metadata.isSelected,
        showLabel: (feature, resolution) => {
          return resolution < resolutionLimitInKm;
        },
      });
    }
  }

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [OpenLayersStyleFactory, { provide: LocationStyleService, useClass: TestLocationStyleService }],
    });
    service = TestBed.inject(LocationStyleService);
  });

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

  it(
    'should prefer added styles over fallback styles',
    waitForAsync(() => {
      const locationBuilding: LsLocation = {
        id: '2',
        name: 'Fe710',
        type: 'building',
        geoCoordinate: <LsPosition>{
          longitude: 9.1706,
          latitude: 48.8144,
          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],
            ],
          ],
        },
      };

      // get the fallback StyleFunction
      const styleBeforeFunc = service.getStyle(locationBuilding, { isSelected: false });

      // call the fallback StyleFunction to get the style
      const styleBefore = (styleBeforeFunc as StyleFunction)(new Feature(), 15) as Style;
      expect(styleBefore).toEqual(service.locationStyleDefault);

      // add a style that should be used for locations of type 'building'
      const locationStyles: LocationStyle[] = (<TestLocationStyleService>service).pushStyles();

      // get the added StyleFunction
      const styleAfterFunc = service.getStyle(locationBuilding, { isSelected: false });

      // check if the StyleFunction is the one we added
      expect(styleAfterFunc).toEqual(locationStyles[0].style);

      // call the added StyleFunction to get the style
      const styleAfterDefinition = (locationStyles[0].style as StyleFunction)(new Feature(), 15);

      // call the returned StyleFunction to get the style
      const styleAfter = (styleAfterFunc as StyleFunction)(new Feature(), 15);

      // now compare the both styles
      expect(styleAfter).toEqual(styleAfterDefinition);
    })
  );

  it('should return first applicable style in definition order', async () => {
    // create a StyleFunction for locations of type 'building'
    const locationStyles1: LocationStyle[] = (<TestLocationStyleService>service).pushStyles();
    expect(locationStyles1.length).toEqual(1);

    const locationBuilding: LsLocation = {
      id: '2',
      name: 'Fe710',
      type: 'building',
      geoCoordinate: <LsPosition>{
        longitude: 9.1706,
        latitude: 48.8144,
        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],
          ],
        ],
      },
    };

    // get the StyleFunction
    const styleBeforeFunc = service.getStyle(locationBuilding, { isSelected: false });

    // add a second StyleFunction that applies to locations of type 'building'
    const locationStyles2: LocationStyle[] = (<TestLocationStyleService>service).pushStyles();
    expect(locationStyles2.length).toEqual(2);

    // get the StyleFunction a second time
    const styleAfterFunc = service.getStyle(locationBuilding, { isSelected: false });

    // it should be the same as before and not the second one
    expect(styleBeforeFunc).toEqual(styleAfterFunc);
  });

  it('should customize style', async () => {
    const defaultStyle = new Style({ zIndex: 0 });

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(defaultStyle, {
      applies: () => true,
      getZIndex: () => 13,
    });
    // call the created StyleFunction that is defined on createdLocationStyle.style by setting the style property with defaultStyle
    // the zIndex should now be as defined by the function getZIndex() and not as defined in defaultStyle
    expect(((createdLocationStyle.style as StyleFunction)(new Feature(), 0) as Style).getZIndex()).toEqual(13);
  });

  it('should customize style function', async () => {
    const createdStyle = new Style({ zIndex: 1 });

    /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
    const providedStyleFunction = (feature: Feature, resolution: number) => {
      return createdStyle;
    };

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(providedStyleFunction, {
      applies: () => true,
      getZIndex: () => 13,
    });

    // get the StyleFunction from the created LocationStyle
    const styleFunction: StyleFunction = createdLocationStyle.style as StyleFunction;

    // call the function, expecting that the createdStyle is returned
    const evaluatedStyle = styleFunction(new Feature(), 0);
    expect(evaluatedStyle).toEqual(createdStyle);
  });

  it('should customize array of style instance', async () => {
    const style1 = new Style({ zIndex: 5 });
    const style2 = new Style({ zIndex: 10 });

    const definedStyles = [style1, style2];

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(definedStyles, {
      applies: () => true,
      getZIndex: () => 13,
    });
    // call the created StyleFunction that is defined on createdLocationStyle.style by setting the style property with defaultStyle
    // it should return the definedStyles array
    const evaluatedStyles = (createdLocationStyle.style as StyleFunction)(new Feature({ label: 'myFeature' }), 0);
    expect(evaluatedStyles).toEqual(definedStyles);
  });

  it('should only set label on first element of customized array of style instance', async () => {
    const textStyle1 = new Text({ font: 'normal 15px "Bosch-Sans", "Arial", "sans-serif"', textAlign: 'left' });
    const textStyle2 = new Text({ font: 'normal 35px "Bosch-Sans", "Arial", "sans-serif"', textAlign: 'center' });

    const style1 = new Style({ zIndex: 5, text: textStyle1 });
    const style2 = new Style({ zIndex: 5, text: textStyle2 });

    const definedStyles = [style1, style2];

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(definedStyles, {
      applies: () => true,
      getZIndex: () => 0,
    });
    // call the created StyleFunction that is defined on createdLocationStyle.style by setting the style property with defaultStyle
    // it should return the definedStyles array
    const evaluatedStyles = (createdLocationStyle.style as StyleFunction)(new Feature({ label: 'MyLabel' }), 0);
    expect(Array.isArray(evaluatedStyles)).toBeTruthy();

    const evaluatedStyle1 = (evaluatedStyles as Array<Style>)[0];
    expect(evaluatedStyle1 instanceof Style).toBeTruthy();

    const evaluatedStyle2 = (evaluatedStyles as Array<Style>)[1];
    expect(evaluatedStyle2 instanceof Style).toBeTruthy();

    expect(evaluatedStyle1.getText().getText()).toEqual('MyLabel');
    expect(evaluatedStyle2.getText().getText()).toBeUndefined();
  });

  it('should set zIndex on all elements of customized array of style instance', async () => {
    const style1 = new Style({ zIndex: 3 });
    const style2 = new Style({ zIndex: 5 });
    const definedStyles = [style1, style2];

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(definedStyles, {
      applies: () => true,
      getZIndex: () => 7,
    });
    // call the created StyleFunction that is defined on createdLocationStyle.style by setting the style property with defaultStyle
    // it should return the definedStyles array
    const evaluatedStyles = (createdLocationStyle.style as StyleFunction)(new Feature(), 0);
    expect(Array.isArray(evaluatedStyles)).toBeTruthy();

    const evaluatedStyle1 = (evaluatedStyles as Array<Style>)[0];
    expect(evaluatedStyle1 instanceof Style).toBeTruthy();

    const evaluatedStyle2 = (evaluatedStyles as Array<Style>)[1];
    expect(evaluatedStyle2 instanceof Style).toBeTruthy();

    expect(evaluatedStyle1.getZIndex()).toEqual(7);
    expect(evaluatedStyle2.getZIndex()).toEqual(7);
  });

  it('should only set zIndex if defined', async () => {
    const definedStyle = new Style({ zIndex: 5 });

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(definedStyle, {
      applies: () => true,
      getZIndex: () => undefined,
    });
    // call the created StyleFunction that is defined on createdLocationStyle.style by setting the style property with defaultStyle
    const evaluatedStyle = (createdLocationStyle.style as StyleFunction)(new Feature({ label: 'MyLabel' }), 0);
    expect(evaluatedStyle instanceof Style).toBeTruthy();

    expect((evaluatedStyle as Style).getZIndex()).toEqual(5);
  });

  it('should only set label if defined', async () => {
    const textStyle = new Text({ font: 'normal 15px "Bosch-Sans", "Arial", "sans-serif"', textAlign: 'left' });
    const definedStyle = new Style({ zIndex: 5, text: textStyle });

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(definedStyle, {
      applies: () => true,
      getZIndex: () => 7,
    });

    const evaluatedStyle1 = (createdLocationStyle.style as StyleFunction)(new Feature({}), 0);
    expect((evaluatedStyle1 as Style).getText().getText()).toBeUndefined();

    const evaluatedStyle2 = (createdLocationStyle.style as StyleFunction)(new Feature({ label: 'MyLabel' }), 0);
    expect((evaluatedStyle2 as Style).getText().getText()).toEqual('MyLabel');
  });

  it('should only set label if it should be shown', async () => {
    const textStyle = new Text({ font: 'normal 15px "Bosch-Sans", "Arial", "sans-serif"', textAlign: 'left' });
    const definedStyle = new Style({ zIndex: 5, text: textStyle });

    const createdLocationStyle: LocationStyleCustomized = new LocationStyleCustomized(definedStyle, {
      applies: () => true,
      getZIndex: () => 3,
      showLabel: (feature, resolution) => {
        return resolution < 10;
      },
    });

    const evaluatedStyle1 = (createdLocationStyle.style as StyleFunction)(new Feature({ label: 'MyLabel' }), 15);
    expect((evaluatedStyle1 as Style).getText().getText()).toBeUndefined();

    const evaluatedStyle2 = (createdLocationStyle.style as StyleFunction)(new Feature({ label: 'MyLabel' }), 5);
    expect((evaluatedStyle2 as Style).getText().getText()).toEqual('MyLabel');
  });
});
