// Copyright (c) 2011, 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.

/**
 * This class represents a pair of two objects, used by LinkedHashMap
 * to store a {key, value} in a list.
 */
class KeyValuePair<K, V> {
  KeyValuePair(this.key, this.value) {}

  final K key;
  V value;
}

/**
 * A LinkedHashMap is a hash map that preserves the insertion order
 * when iterating over the keys or the values. Updating the value of a
 * key does not change the order.
 */
class LinkedHashMapImplementation<K extends Hashable, V>
    implements LinkedHashMap<K, V> {
  DoubleLinkedQueue<KeyValuePair<K, V>> _list;
  HashMap<K, DoubleLinkedQueueEntry<KeyValuePair<K, V>>> _map;

  LinkedHashMapImplementation() {
    _map = new HashMap<K, DoubleLinkedQueueEntry<KeyValuePair<K, V>>>();
    _list = new DoubleLinkedQueue<KeyValuePair<K, V>>();
  }

  // See bug 5257789.
  factory LinkedHashMapImplementation/* <K, V> */.from(Map/* <K, V> */other) {
    Map/* <K, V> */ result = new LinkedHashMapImplementation/* <K, V> */();
    other.forEach((/* K */ key, /* V */ value) { result[key] = value; });
    return result;
  }

  void operator []=(K key, V value) {
    if (_map.containsKey(key)) {
      _map[key].element.value = value;
    } else {
      _list.addLast(new KeyValuePair<K, V>(key, value));
      _map[key] = _list.lastEntry();
    }
  }

  V operator [](K key) {
    DoubleLinkedQueueEntry<KeyValuePair<K, V>> entry = _map[key];
    if (entry === null) return null;
    return entry.element.value;
  }

  V remove(K key) {
    DoubleLinkedQueueEntry<KeyValuePair<K, V>> entry = _map.remove(key);
    if (entry === null) return null;
    entry.remove();
    return entry.element.value;
  }

  V putIfAbsent(K key, V ifAbsent()) {
    V value = this[key];
    if ((this[key] === null) && !(containsKey(key))) {
      value = ifAbsent();
      this[key] = value;
    }
    return value;
  }

  Collection<K> getKeys() {
    List<K> list = new List<K>(length);
    int index = 0;
    _list.forEach(void _(KeyValuePair<K, V> entry) {
      list[index++] = entry.key;
    });
    assert(index == length);
    return list;
  }


  Collection<V> getValues() {
    List<V> list = new List<V>(length);
    int index = 0;
    _list.forEach(void _(KeyValuePair<K, V> entry) {
      list[index++] = entry.value;
    });
    assert(index == length);
    return list;
  }

  void forEach(void f(K key, V value)) {
    _list.forEach(void _(KeyValuePair<K, V> entry) {
      f(entry.key, entry.value);
    });
  }

  bool containsKey(K key) {
    return _map.containsKey(key);
  }

  bool containsValue(V value) {
    return _list.some(bool _(KeyValuePair<K, V> entry) {
      return (entry.value == value);
    });
  }

  int get length() {
    return _map.length;
  }

  bool isEmpty() {
    return length == 0;
  }

  void clear() {
    _map.clear();
    _list.clear();
  }
}
