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

import Feature from 'ol/Feature';

export interface Entity {
  id: string;
}

export class FeatureEntityMap<E extends Entity> {
  private featureIdToEntity = new Map<string | number, E>();
  private entityIdToFeatures = new Map<string, Feature[]>();

  /**
   * Set mapping of features and an entity.
   * @param features Feature to map to entity.
   * @param entity Entity to map feature to.
   */
  set(features: Feature[], entity: E) {
    if (this.entityIdToFeatures.has(entity.id)) {
      this.clearFeatures(this.getFeaturesOfEntity(entity));
    }
    this.entityIdToFeatures.set(entity.id, features);
    features.forEach((feature) => this.featureIdToEntity.set(feature.getId(), entity));
  }

  /**
   * Remove features from mapping.
   * @param features The features to remove.
   */
  clearFeatures(features: Feature[]) {
    features.forEach((feature) => {
      const entity = this.featureIdToEntity.get(feature.getId());
      if (!!entity && this.entityIdToFeatures.has(entity.id)) {
        const entityFeatureSet = this.entityIdToFeatures.get(entity.id);
        this.entityIdToFeatures.set(
          entity.id,
          entityFeatureSet.filter((entityFeature) => entityFeature !== feature)
        );
      }
      this.featureIdToEntity.delete(feature.getId());
    });
  }

  /**
   * Iterate over feature sets and their linked entity id.
   * @param callbackfn Function to execute for each element.
   * @param thisArg Value to use as this when executing callback.
   */
  forEachFeature(
    callbackfn: (value: Feature[], key: string, map: Map<string, Feature[]>) => void,
    thisArg?: any
  ): void {
    return this.entityIdToFeatures.forEach(callbackfn, thisArg);
  }

  /**
   * Get entity of feature.
   * @param feature Feature to look up entities for.
   * @returns Matching entity instance.
   */
  getEntityOfFeature(feature: Feature): E {
    return this.featureIdToEntity.get(feature.getId());
  }

  /**
   * Get distinct entitys from features.
   * @param features Features The features to resolve.
   * @returns Resolved distinct entities.
   */
  getEntitiesOfFeatures(features: Feature[]): E[] {
    return features
      .map((fs) => this.getEntityOfFeature(fs))
      .filter((entity) => !!entity)
      .filter(this.distinctEntityFilter()); // only distinct entitys
  }

  /**
   * Get features related to entity.
   * @param entity Entity to resolve.
   * @returns Resolved features or empty array, if none are known.
   */
  getFeaturesOfEntity(entity: E): Feature[] {
    return this.entityIdToFeatures.get(entity.id) || [];
  }

  private distinctEntityFilter(): (item: E) => boolean {
    const set = new Set<E>();
    return (item) => {
      const result = set.has(item);
      set.add(item);
      return !result;
    };
  }
}
