/* ============
The FastMap has the goal of creating the fastes to use map possible in JS

============ */

import * as plugins from './classes.plugins.js';

/**
 * fast map allows for very quick lookups of objects with a unique key
 */
export class FastMap<T> {
  private mapObject = new Map<string, T>();

  public isUniqueKey(keyArg: string): boolean {
    return !this.mapObject.has(keyArg);
  }

  public has(keyArg: string): boolean {
    return this.mapObject.has(keyArg);
  }

  public get size(): number {
    return this.mapObject.size;
  }

  public addToMap(
    keyArg: string,
    objectArg: T,
    optionsArg?: {
      force: boolean;
    }
  ): boolean {
    if (this.isUniqueKey(keyArg) || (optionsArg && optionsArg.force)) {
      this.mapObject.set(keyArg, objectArg);
      return true;
    } else {
      return false;
    }
  }

  public getByKey(keyArg: string): T | undefined {
    return this.mapObject.get(keyArg);
  }

  public removeFromMap(keyArg: string): T {
    const removedItem = this.mapObject.get(keyArg);
    this.mapObject.delete(keyArg);
    return removedItem;
  }

  public getKeys(): string[] {
    return Array.from(this.mapObject.keys());
  }

  public values(): T[] {
    return Array.from(this.mapObject.values());
  }

  public entries(): [string, T][] {
    return Array.from(this.mapObject.entries());
  }

  public clean() {
    this.mapObject.clear();
  }

  /**
   * returns a new Fastmap that includes all values from this and all from the fastmap in the argument
   */
  public concat(fastMapArg: FastMap<T>) {
    const concatedFastmap = new FastMap<T>();
    for (const key of this.getKeys()) {
      concatedFastmap.addToMap(key, this.getByKey(key));
    }

    for (const key of fastMapArg.getKeys()) {
      concatedFastmap.addToMap(key, fastMapArg.getByKey(key), {
        force: true,
      });
    }

    return concatedFastmap;
  }

  /**
   * tries to merge another Fastmap
   * Note: uniqueKeyCollisions will cause overwrite
   * @param fastMapArg
   */
  public addAllFromOther(fastMapArg: FastMap<T>) {
    for (const key of fastMapArg.getKeys()) {
      this.addToMap(key, fastMapArg.getByKey(key), {
        force: true,
      });
    }
  }

  public async find(findFunctionArg: (mapItemArg: T) => Promise<boolean>) {
    for (const key of this.getKeys()) {
      const item = this.getByKey(key);
      const findFunctionResult = await findFunctionArg(item);
      if (findFunctionResult) {
        return item;
      }
    }
  }

  public [Symbol.iterator](): Iterator<[string, T]> {
    return this.mapObject.entries();
  }
}
