// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export const inverse = function<K, V>(map: Map<K, V>): Multimap<V, K> {
  const result = new Multimap<V, K>();
  for (const [key, value] of map.entries()) {
    result.set(value, key);
  }
  return result;
};

export class Multimap<K, V> {
  private map = new Map<K, Set<V>>();

  set(key: K, value: V): void {
    let set = this.map.get(key);
    if (!set) {
      set = new Set();
      this.map.set(key, set);
    }
    set.add(value);
  }

  get(key: K): Set<V> {
    return this.map.get(key) || new Set();
  }

  has(key: K): boolean {
    return this.map.has(key);
  }

  hasValue(key: K, value: V): boolean {
    const set = this.map.get(key);
    if (!set) {
      return false;
    }
    return set.has(value);
  }

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

  delete(key: K, value: V): boolean {
    const values = this.get(key);
    if (!values) {
      return false;
    }
    const result = values.delete(value);
    if (!values.size) {
      this.map.delete(key);
    }
    return result;
  }

  deleteAll(key: K): void {
    this.map.delete(key);
  }

  keysArray(): K[] {
    return [...this.map.keys()];
  }

  keys(): IterableIterator<K> {
    return this.map.keys();
  }

  valuesArray(): V[] {
    const result = [];
    for (const set of this.map.values()) {
      result.push(...set.values());
    }
    return result;
  }

  clear(): void {
    this.map.clear();
  }
}

/**
 * Gets value for key, assigning a default if value is falsy.
 */
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export function getWithDefault<K extends {}, V>(
    map: WeakMap<K, V>|Map<K, V>, key: K, defaultValueFactory: (key?: K) => V): V {
  let value = map.get(key);
  if (value === undefined || value === null) {
    value = defaultValueFactory(key);
    map.set(key, value);
  }

  return value;
}
