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

part of types;

/**
 * A [DictionaryTypeMask] is a [TypeMask] for a specific allocation
 * site of a map (currently only internal Map class) that is used as
 * a dictionary, i.e. a mapping from a set of statically known strings
 * to values. These typemasks only come into existence after the
 * [TypeGraphInferrer] has successfully identified such a usage. Otherwise,
 * the more general [MapTypeMask] is used.
 */
class DictionaryTypeMask extends MapTypeMask {
  // The underlying key/value map of this dictionary.
  final Map<String, TypeMask> typeMap;

  DictionaryTypeMask(forwardTo,
                     allocationNode,
                     allocationElement,
                     keyType, valueType,
                     this.typeMap) :
      super(forwardTo, allocationNode, allocationElement, keyType, valueType);

  TypeMask nullable() {
    return isNullable
        ? this
        : new DictionaryTypeMask(forwardTo.nullable(),
                                 allocationNode,
                                 allocationElement,
                                 keyType, valueType,
                                 typeMap);
  }

  TypeMask nonNullable() {
    return isNullable
        ? new DictionaryTypeMask(forwardTo.nonNullable(),
                                 allocationNode,
                                 allocationElement,
                                 keyType, valueType,
                                 typeMap)
        : this;
  }

  bool get isDictionary => true;
  bool get isExact => true;

  bool equalsDisregardNull(other) {
    if (other is! DictionaryTypeMask) return false;
    return allocationNode == other.allocationNode &&
           keyType == other.keyType &&
           valueType == other.valueType &&
           typeMap.keys.every((k) => other.typeMap.containsKey(k)) &&
           other.typeMap.keys.every((k) => typeMap.containsKey(k) &&
                                           typeMap[k] == other.typeMap[k]);

  }

  TypeMask intersection(TypeMask other, Compiler compiler) {
    TypeMask forwardIntersection = forwardTo.intersection(other, compiler);
    if (forwardIntersection.isEmpty) return forwardIntersection;
    return forwardIntersection.isNullable
        ? nullable()
        : nonNullable();
  }

  TypeMask union(other, Compiler compiler) {
    if (this == other) {
      return this;
    } else if (equalsDisregardNull(other)) {
      return other.isNullable ? other : this;
    } else if (other.isEmpty) {
      return other.isNullable ? this.nullable() : this;
    } else if (other.isDictionary) {
      TypeMask newForwardTo = forwardTo.union(other.forwardTo, compiler);
      TypeMask newKeyType = keyType.union(other.keyType, compiler);
      TypeMask newValueType = valueType.union(other.valueType, compiler);
      Map<String, TypeMask> mappings = <String, TypeMask>{};
      typeMap.forEach((k,v) {
              if (!other.typeMap.containsKey(k)) {
                mappings[k] = v.nullable();
              }
            });
      other.typeMap.forEach((k,v) {
        if (typeMap.containsKey(k)) {
          mappings[k] = v.union(typeMap[k], compiler);
        } else {
          mappings[k] = v.nullable();
        }
      });
      return new DictionaryTypeMask(newForwardTo, null, null,
                                    newKeyType, newValueType, mappings);
    } else if (other.isMap &&
               (other.keyType != null) &&
               (other.valueType != null)) {
      TypeMask newForwardTo = forwardTo.union(other.forwardTo, compiler);
      TypeMask newKeyType = keyType.union(other.keyType, compiler);
      TypeMask newValueType = valueType.union(other.valueType, compiler);
      return new MapTypeMask(newForwardTo, null, null,
                             newKeyType, newValueType);
    } else {
      return forwardTo.union(other, compiler);
    }
  }

  bool operator==(other) => super == other;

  int get hashCode {
    return computeHashCode(
        allocationNode, isNullable, typeMap, forwardTo);
  }

  String toString() {
    return
        'Dictionary mask: [$keyType/$valueType with $typeMap] type: $forwardTo';
  }
}