// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library dart2js.mirrors.util;

import 'dart:collection' show Maps;

/**
 * An abstract map implementation. This class can be used as a superclass for
 * implementing maps, requiring only the further implementation of the
 * [:operator []:], [:forEach:] and [:length:] methods to provide a fully
 * implemented immutable map.
 */
abstract class AbstractMap<K, V> implements Map<K, V> {
  AbstractMap();

  AbstractMap.from(Map<K, V> other) {
    other.forEach((k,v) => this[k] = v);
  }

  void operator []=(K key, value) {
    throw new UnsupportedError('[]= is not supported');
  }

  void clear() {
    throw new UnsupportedError('clear() is not supported');
  }

  void addAll(Map<K, V> other) {
    throw new UnsupportedError('addAll() is not supported');
  }

  bool containsKey(K key) {
    var found = false;
    forEach((k,_) {
      if (k == key) {
        found = true;
      }
    });
    return found;
  }

  bool containsValue(V value) {
    var found = false;
    forEach((_,v) {
      if (v == value) {
        found = true;
      }
    });
    return found;
  }

  Iterable<K> get keys {
    var keys = <K>[];
    forEach((k,_) => keys.add(k));
    return keys;
  }

  Iterable<V> get values {
    var values = <V>[];
    forEach((_,v) => values.add(v));
    return values;
  }

  bool get isEmpty => length == 0;
  bool get isNotEmpty => !isEmpty;
  V putIfAbsent(K key, V ifAbsent()) {
    if (!containsKey(key)) {
      V value = this[key];
      this[key] = ifAbsent();
      return value;
    }
    return null;
  }

  V remove(K key) {
    throw new UnsupportedError('V remove(K key) is not supported');
  }

  String toString() => Maps.mapToString(this);
}

/**
 * [ImmutableMapWrapper] wraps a (mutable) map as an immutable map where all
 * mutating operations throw [UnsupportedError] upon invocation.
 */
class ImmutableMapWrapper<K, V> extends AbstractMap<K, V> {
  final Map<K, V> _map;

  ImmutableMapWrapper(this._map);

  int get length => _map.length;

  V operator [](K key) {
    if (key is K) {
      return _map[key];
    }
    return null;
  }

  void forEach(void f(K key, V value)) {
    _map.forEach(f);
  }
}

/**
 * A [Filter] function returns [:true:] iff [value] should be included.
 */
typedef bool Filter<V>(V value);

/**
 * An immutable map wrapper capable of filtering the input map.
 */
class FilteredImmutableMap<K, V> extends ImmutableMapWrapper<K, V> {
  final Filter<V> _filter;

  FilteredImmutableMap(Map<K, V> map, this._filter) : super(map);

  int get length {
    var count = 0;
    forEach((k,v) {
      count++;
    });
    return count;
  }

  void forEach(void f(K key, V value)) {
    _map.forEach((K k, V v) {
      if (_filter(v)) {
        f(k, v);
      }
    });
  }
}

/**
 * An [AsFilter] takes a [value] of type [V1] and returns [value] iff it is of
 * type [V2] or [:null:] otherwise. An [AsFilter] therefore behaves like the
 * [:as:] expression.
 */
typedef V2 AsFilter<V1, V2>(V1 value);

/**
 * An immutable map wrapper capable of filtering the input map based on types.
 * It takes an [AsFilter] function which converts the original values of type
 * [Vin] into values of type [Vout], or returns [:null:] if the value should
 * not be included in the filtered map.
 */
class AsFilteredImmutableMap<K, Vin, Vout> extends AbstractMap<K, Vout> {
  final Map<K, Vin> _map;
  final AsFilter<Vin, Vout> _filter;

  AsFilteredImmutableMap(this._map, this._filter);

  int get length {
    var count = 0;
    forEach((k,v) {
      count++;
    });
    return count;
  }

  Vout operator [](K key) {
    if (key is K) {
      Vin value = _map[key];
      if (value != null) {
        return _filter(value);
      }
    }
    return null;
  }

  void forEach(void f(K key, Vout value)) {
    _map.forEach((K k, Vin v) {
      var value = _filter(v);
      if (value != null) {
        f(k, value);
      }
    });
  }
}
