// 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 mirrors_dart2js;

import 'dart:async';

import '../../compiler.dart' as api;
import '../elements/elements.dart';
import '../apiimpl.dart' as apiimpl;
import '../scanner/scannerlib.dart';
import '../resolution/resolution.dart' show Scope;
import '../dart2jslib.dart';
import '../dart_types.dart';
import '../tree/tree.dart';
import '../util/util.dart' show Spannable, Link;
import '../util/characters.dart' show $CR, $LF;

import 'mirrors.dart';
import 'mirrors_util.dart';
import 'util.dart';

//------------------------------------------------------------------------------
// Utility types and functions for the dart2js mirror system
//------------------------------------------------------------------------------

bool _isPrivate(String name) {
  return name.startsWith('_');
}

List<ParameterMirror> _parametersFromFunctionSignature(
    Dart2JsMirrorSystem system,
    Dart2JsMethodMirror method,
    FunctionSignature signature) {
  var parameters = <ParameterMirror>[];
  Link<Element> link = signature.requiredParameters;
  while (!link.isEmpty) {
    parameters.add(new Dart2JsParameterMirror(
        system, method, link.head, isOptional: false, isNamed: false));
    link = link.tail;
  }
  link = signature.optionalParameters;
  bool isNamed = signature.optionalParametersAreNamed;
  while (!link.isEmpty) {
    parameters.add(new Dart2JsParameterMirror(
        system, method, link.head, isOptional: true, isNamed: isNamed));
    link = link.tail;
  }
  return parameters;
}

Dart2JsTypeMirror _convertTypeToTypeMirror(
    Dart2JsMirrorSystem system,
    DartType type,
    InterfaceType defaultType,
    [FunctionSignature functionSignature]) {
  if (type == null) {
    return new Dart2JsInterfaceTypeMirror(system, defaultType);
  } else if (type is InterfaceType) {
    if (type == system.compiler.types.dynamicType) {
      return new Dart2JsDynamicMirror(system, type);
    } else {
      return new Dart2JsInterfaceTypeMirror(system, type);
    }
  } else if (type is TypeVariableType) {
    return new Dart2JsTypeVariableMirror(system, type);
  } else if (type is FunctionType) {
    return new Dart2JsFunctionTypeMirror(system, type, functionSignature);
  } else if (type is VoidType) {
    return new Dart2JsVoidMirror(system, type);
  } else if (type is TypedefType) {
    return new Dart2JsTypedefMirror(system, type);
  } else if (type is MalformedType) {
    // TODO(johnniwinther): We need a mirror on malformed types.
    return system.dynamicType;
  }
  system.compiler.internalError("Unexpected type $type of kind ${type.kind}");
}

Iterable<Dart2JsMemberMirror> _convertElementMemberToMemberMirrors(
    Dart2JsContainerMirror library, Element element) {
  if (element.isSynthesized) {
    return const <Dart2JsMemberMirror>[];
  } else if (element is VariableElement) {
    return <Dart2JsMemberMirror>[new Dart2JsFieldMirror(library, element)];
  } else if (element is FunctionElement) {
    return <Dart2JsMemberMirror>[new Dart2JsMethodMirror(library, element)];
  } else if (element is AbstractFieldElement) {
    var members = <Dart2JsMemberMirror>[];
    AbstractFieldElement field = element;
    if (field.getter != null) {
      members.add(new Dart2JsMethodMirror(library, field.getter));
    }
    if (field.setter != null) {
      members.add(new Dart2JsMethodMirror(library, field.setter));
    }
    return members;
  }
  library.mirrors.compiler.internalError(
      "Unexpected member type $element ${element.kind}");
}

MethodMirror _convertElementMethodToMethodMirror(Dart2JsContainerMirror library,
                                                 Element element) {
  if (element is FunctionElement) {
    return new Dart2JsMethodMirror(library, element);
  } else {
    return null;
  }
}

InstanceMirror _convertConstantToInstanceMirror(Dart2JsMirrorSystem mirrors,
                                                Constant constant) {
  if (constant is BoolConstant) {
    return new Dart2JsBoolConstantMirror(mirrors, constant);
  } else if (constant is NumConstant) {
    return new Dart2JsNumConstantMirror(mirrors, constant);
  } else if (constant is StringConstant) {
    return new Dart2JsStringConstantMirror(mirrors, constant);
  } else if (constant is ListConstant) {
    return new Dart2JsListConstantMirror(mirrors, constant);
  } else if (constant is MapConstant) {
    return new Dart2JsMapConstantMirror(mirrors, constant);
  } else if (constant is TypeConstant) {
    return new Dart2JsTypeConstantMirror(mirrors, constant);
  } else if (constant is FunctionConstant) {
    return new Dart2JsConstantMirror(mirrors, constant);
  } else if (constant is NullConstant) {
    return new Dart2JsNullConstantMirror(mirrors, constant);
  } else if (constant is ConstructedConstant) {
    return new Dart2JsConstructedConstantMirror(mirrors, constant);
  }
  mirrors.compiler.internalError("Unexpected constant $constant");
}

class Dart2JsMethodKind {
  static const Dart2JsMethodKind REGULAR = const Dart2JsMethodKind("regular");
  static const Dart2JsMethodKind GENERATIVE =
      const Dart2JsMethodKind("generative");
  static const Dart2JsMethodKind REDIRECTING =
      const Dart2JsMethodKind("redirecting");
  static const Dart2JsMethodKind CONST = const Dart2JsMethodKind("const");
  static const Dart2JsMethodKind FACTORY = const Dart2JsMethodKind("factory");
  static const Dart2JsMethodKind GETTER = const Dart2JsMethodKind("getter");
  static const Dart2JsMethodKind SETTER = const Dart2JsMethodKind("setter");
  static const Dart2JsMethodKind OPERATOR = const Dart2JsMethodKind("operator");

  final String text;

  const Dart2JsMethodKind(this.text);

  String toString() => text;
}


String _getOperatorFromOperatorName(String name) {
  Map<String, String> mapping = const {
    'eq': '==',
    'not': '~',
    'index': '[]',
    'indexSet': '[]=',
    'mul': '*',
    'div': '/',
    'mod': '%',
    'tdiv': '~/',
    'add': '+',
    'sub': '-',
    'shl': '<<',
    'shr': '>>',
    'ge': '>=',
    'gt': '>',
    'le': '<=',
    'lt': '<',
    'and': '&',
    'xor': '^',
    'or': '|',
  };
  String newName = mapping[name];
  if (newName == null) {
    throw new Exception('Unhandled operator name: $name');
  }
  return newName;
}

//------------------------------------------------------------------------------
// Analysis entry point.
//------------------------------------------------------------------------------

/**
 * Analyzes set of libraries and provides a mirror system which can be used for
 * static inspection of the source code.
 */
// TODO(johnniwinther): Move this to [compiler/compiler.dart].
Future<MirrorSystem> analyze(List<Uri> libraries,
                             Uri libraryRoot,
                             Uri packageRoot,
                             api.CompilerInputProvider inputProvider,
                             api.DiagnosticHandler diagnosticHandler,
                             [List<String> options = const <String>[]]) {
  if (!libraryRoot.path.endsWith("/")) {
    throw new ArgumentError("libraryRoot must end with a /");
  }
  if (packageRoot != null && !packageRoot.path.endsWith("/")) {
    throw new ArgumentError("packageRoot must end with a /");
  }
  options = new List<String>.from(options);
  options.add('--analyze-only');
  options.add('--analyze-signatures-only');
  options.add('--analyze-all');
  options.add('--categories=Client,Server');

  bool compilationFailed = false;
  void internalDiagnosticHandler(Uri uri, int begin, int end,
                                 String message, api.Diagnostic kind) {
    if (kind == api.Diagnostic.ERROR ||
        kind == api.Diagnostic.CRASH) {
      compilationFailed = true;
    }
    diagnosticHandler(uri, begin, end, message, kind);
  }

  Compiler compiler = new apiimpl.Compiler(inputProvider,
                                           null,
                                           internalDiagnosticHandler,
                                           libraryRoot, packageRoot,
                                           options,
                                           const {});
  compiler.librariesToAnalyzeWhenRun = libraries;
  return compiler.run(null).then((bool success) {
    if (success && !compilationFailed) {
      return new Dart2JsMirrorSystem(compiler);
    } else {
      throw new StateError('Failed to create mirror system.');
    }
  });
}

//------------------------------------------------------------------------------
// Dart2Js specific extensions of mirror interfaces
//------------------------------------------------------------------------------

abstract class Dart2JsMirror implements Mirror {
  Dart2JsMirrorSystem get mirrors;
}

abstract class Dart2JsDeclarationMirror extends Dart2JsMirror
    implements DeclarationMirror {

  bool get isTopLevel => owner != null && owner is LibraryMirror;

  bool get isPrivate => _isPrivate(simpleName);

  /**
   * Returns the first token for the source of this declaration, not including
   * metadata annotations.
   */
  Token getBeginToken();

  /**
   * Returns the last token for the source of this declaration.
   */
  Token getEndToken();

  /**
   * Returns the script for the source of this declaration.
   */
  Script getScript();
}

abstract class Dart2JsTypeMirror extends Dart2JsDeclarationMirror
    implements TypeMirror {
}

abstract class Dart2JsElementMirror extends Dart2JsDeclarationMirror {
  final Dart2JsMirrorSystem mirrors;
  final Element _element;
  List<InstanceMirror> _metadata;

  Dart2JsElementMirror(this.mirrors, this._element) {
    assert (mirrors != null);
    assert (_element != null);
  }

  /**
   * Returns the element to be used to determine the begin token of this
   * declaration and the metadata associated with this declaration.
   *
   * This indirection is needed to use the [VariableListElement] as the location
   * for type and metadata information on a [VariableElement].
   */
  Element get _beginElement => _element;

  String get simpleName => _element.name;

  bool get isNameSynthetic => false;

  /**
   * Computes the first token for this declaration using the begin token of the
   * element node or element position as indicator.
   */
  Token getBeginToken() {
    // TODO(johnniwinther): Avoid calling [parseNode].
    Node node = _beginElement.parseNode(mirrors.compiler);
    if (node == null) {
      return _beginElement.position();
    }
    return node.getBeginToken();
  }

  /**
   * Computes the last token for this declaration using the end token of the
   * element node or element position as indicator.
   */
  Token getEndToken() {
    // TODO(johnniwinther): Avoid calling [parseNode].
    Node node = _element.parseNode(mirrors.compiler);
    if (node == null) {
      return _element.position();
    }
    return node.getEndToken();
  }

  /**
   * Returns the first token for the source of this declaration, including
   * metadata annotations.
   */
  Token getFirstToken() {
    if (!_beginElement.metadata.isEmpty) {
      for (MetadataAnnotation metadata in _beginElement.metadata) {
        if (metadata.beginToken != null) {
          return metadata.beginToken;
        }
      }
    }
    return getBeginToken();
  }

  Script getScript() => _element.getCompilationUnit().script;

  SourceLocation get location {
    Token beginToken = getFirstToken();
    Script script = getScript();
    SourceSpan span;
    if (beginToken == null) {
      span = new SourceSpan(script.uri, 0, 0);
    } else {
      Token endToken = getEndToken();
      span = mirrors.compiler.spanFromTokens(beginToken, endToken, script.uri);
    }
    return new Dart2JsSourceLocation(script, span);
  }

  String toString() => _element.toString();

  void _appendCommentTokens(Token commentToken) {
    while (commentToken != null && commentToken.kind == COMMENT_TOKEN) {
      _metadata.add(new Dart2JsCommentInstanceMirror(
          mirrors, commentToken.value));
      commentToken = commentToken.next;
    }
  }

  List<InstanceMirror> get metadata {
    if (_metadata == null) {
      _metadata = <InstanceMirror>[];
      for (MetadataAnnotation metadata in _element.metadata) {
        _appendCommentTokens(mirrors.compiler.commentMap[metadata.beginToken]);
        metadata.ensureResolved(mirrors.compiler);
        _metadata.add(
            _convertConstantToInstanceMirror(mirrors, metadata.value));
      }
      _appendCommentTokens(mirrors.compiler.commentMap[getBeginToken()]);
    }
    // TODO(johnniwinther): Return an unmodifiable list instead.
    return new List<InstanceMirror>.from(_metadata);
  }

  DeclarationMirror lookupInScope(String name) {
    // TODO(11653): Support lookup of constructors.
    Scope scope = _element.buildScope();
    Element result;
    int index = name.indexOf('.');
    if (index != -1) {
      // Lookup [: prefix.id :].
      String prefix = name.substring(0, index);
      String id = name.substring(index+1);
      result = scope.lookup(prefix);
      if (result != null && result.isPrefix()) {
        PrefixElement prefix = result;
        result = prefix.lookupLocalMember(id);
      } else {
        result = null;
      }
    } else {
      // Lookup [: id :].
      result = scope.lookup(name);
    }
    if (result == null || result.isPrefix()) return null;
    return _convertElementToDeclarationMirror(mirrors, result);
  }

  bool operator ==(var other) {
    if (identical(this, other)) return true;
    if (other == null) return false;
    if (other is! Dart2JsElementMirror) return false;
    return _element == other._element &&
           owner == other.owner;
  }

  int get hashCode {
    return 13 * _element.hashCode + 17 * owner.hashCode;
  }
}

abstract class Dart2JsMemberMirror extends Dart2JsElementMirror
    implements MemberMirror {

  Dart2JsMemberMirror(Dart2JsMirrorSystem system, Element element)
      : super(system, element);

  bool get isConstructor => false;

  bool get isVariable => false;

  bool get isMethod => false;

  bool get isStatic => false;

  bool get isParameter => false;
}

//------------------------------------------------------------------------------
// Mirror system implementation.
//------------------------------------------------------------------------------

class Dart2JsMirrorSystem extends MirrorSystem {
  final Compiler compiler;
  Map<Uri, Dart2JsLibraryMirror> _libraries;
  Map<LibraryElement, Dart2JsLibraryMirror> _libraryMap;

  Dart2JsMirrorSystem(this.compiler)
    : _libraryMap = new Map<LibraryElement, Dart2JsLibraryMirror>();

  void _ensureLibraries() {
    if (_libraries == null) {
      _libraries = new Map<Uri, Dart2JsLibraryMirror>();
      compiler.libraries.forEach((_, LibraryElement v) {
        var mirror = new Dart2JsLibraryMirror(mirrors, v);
        _libraries[mirror.uri] = mirror;
        _libraryMap[v] = mirror;
      });
    }
  }

  Map<Uri, LibraryMirror> get libraries {
    _ensureLibraries();
    return new FilteredImmutableMap<Uri, LibraryMirror>(_libraries,
        (library) => new bool.fromEnvironment("list_all_libraries") ||
                     !library._element.isInternalLibrary);
  }

  Dart2JsLibraryMirror _getLibrary(LibraryElement element) =>
      _libraryMap[element];

  Dart2JsMirrorSystem get mirrors => this;

  TypeMirror get dynamicType =>
      _convertTypeToTypeMirror(this, compiler.types.dynamicType, null);

  TypeMirror get voidType =>
      _convertTypeToTypeMirror(this, compiler.types.voidType, null);
}

abstract class Dart2JsContainerMirror extends Dart2JsElementMirror
    implements ContainerMirror {
  Map<String, MemberMirror> _members;

  Dart2JsContainerMirror(Dart2JsMirrorSystem system, Element element)
      : super(system, element);

  void _ensureMembers();

  Map<String, MemberMirror> get members {
    _ensureMembers();
    return new ImmutableMapWrapper<String, MemberMirror>(_members);
  }

  Map<String, MethodMirror> get functions {
    _ensureMembers();
    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
        _members,
        (MemberMirror member) => member is MethodMirror ? member : null);
  }

  Map<String, MethodMirror> get getters {
    _ensureMembers();
    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
        _members,
        (MemberMirror member) =>
            member is MethodMirror && member.isGetter ? member : null);
  }

  Map<String, MethodMirror> get setters {
    _ensureMembers();
    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
        _members,
        (MemberMirror member) =>
            member is MethodMirror && member.isSetter ? member : null);
  }

  Map<String, VariableMirror> get variables {
    _ensureMembers();
    return new AsFilteredImmutableMap<String, MemberMirror, VariableMirror>(
        _members,
        (MemberMirror member) => member is VariableMirror ? member : null);
  }
}

class Dart2JsLibraryMirror extends Dart2JsContainerMirror
    implements LibraryMirror {
  Map<String, ClassMirror> _classes;
  List<LibraryDependencyMirror> _libraryDependencies;


  Dart2JsLibraryMirror(Dart2JsMirrorSystem system, LibraryElement library)
      : super(system, library);

  LibraryElement get _library => _element;

  Uri get uri => _library.canonicalUri;

  DeclarationMirror get owner => null;

  bool get isPrivate => false;

  LibraryMirror library() => this;

  /**
   * Returns the library name (for libraries with a library tag) or the script
   * file name (for scripts without a library tag). The latter case is used to
   * provide a 'library name' for scripts, to use for instance in dartdoc.
   */
  String get simpleName {
    if (_library.libraryTag != null) {
      return _library.libraryTag.name.toString();
    } else {
      // Use the file name as script name.
      String path = _library.canonicalUri.path;
      return path.substring(path.lastIndexOf('/') + 1);
    }
  }

  String get qualifiedName => simpleName;

  void _ensureClasses() {
    if (_classes == null) {
      _classes = <String, ClassMirror>{};
      _library.forEachLocalMember((Element e) {
        if (e.isClass()) {
          ClassElement classElement = e;
          classElement.ensureResolved(mirrors.compiler);
          var type = new Dart2JsClassMirror.fromLibrary(this, classElement);
          assert(invariant(_library, !_classes.containsKey(type.simpleName),
              message: "Type name '${type.simpleName}' "
                       "is not unique in $_library."));
          _classes[type.simpleName] = type;
        } else if (e.isTypedef()) {
          var type = new Dart2JsTypedefMirror.fromLibrary(this,
              e.computeType(mirrors.compiler));
          assert(invariant(_library, !_classes.containsKey(type.simpleName),
              message: "Type name '${type.simpleName}' "
                       "is not unique in $_library."));
          _classes[type.simpleName] = type;
        }
      });
    }
  }

  void _ensureMembers() {
    if (_members == null) {
      _members = <String, MemberMirror>{};
      _library.forEachLocalMember((Element e) {
        if (!e.isClass() && !e.isTypedef()) {
          // TODO(ahe): I think it is incorrect to filter out classes
          // and typedefs.  See http://dartbug.com/10371.
          for (var member in _convertElementMemberToMemberMirrors(this, e)) {
            assert(!_members.containsKey(member.simpleName));
            _members[member.simpleName] = member;
          }
        }
      });
    }
  }

  Map<String, ClassMirror> get classes {
    _ensureClasses();
    return new ImmutableMapWrapper<String, ClassMirror>(_classes);
  }

  /**
   * Computes the first token of this library using the first library tag as
   * indicator.
   */
  Token getBeginToken() {
    if (_library.libraryTag != null) {
      return _library.libraryTag.getBeginToken();
    } else if (!_library.tags.isEmpty) {
      return _library.tags.reverse().head.getBeginToken();
    }
    return null;
  }

  /**
   * Computes the first token of this library using the last library tag as
   * indicator.
   */
  Token getEndToken() {
    if (!_library.tags.isEmpty) {
      return _library.tags.head.getEndToken();
    }
    return null;
  }

  void _ensureLibraryDependenciesAnalyzed() {
    if (_libraryDependencies == null) {
      _libraryDependencies = <LibraryDependencyMirror>[];
      for (LibraryTag node in _library.tags.reverse()) {
        LibraryDependency libraryDependency = node.asLibraryDependency();
        if (libraryDependency != null) {
          LibraryElement targetLibraryElement =
              _library.getLibraryFromTag(libraryDependency);
          assert(targetLibraryElement != null);
          LibraryMirror targetLibrary =
              mirrors._getLibrary(targetLibraryElement);
          _libraryDependencies.add(new Dart2JsLibraryDependencyMirror(
              libraryDependency, this, targetLibrary));
        }
      }
    }
  }

  List<LibraryDependencyMirror> get libraryDependencies {
    _ensureLibraryDependenciesAnalyzed();
    return _libraryDependencies;
  }
}

class Dart2JsLibraryDependencyMirror implements LibraryDependencyMirror {
  final LibraryDependency _node;
  final Dart2JsLibraryMirror _sourceLibrary;
  final Dart2JsLibraryMirror _targetLibrary;
  List<CombinatorMirror> _combinators;

  Dart2JsLibraryDependencyMirror(this._node,
                                 this._sourceLibrary,
                                 this._targetLibrary);

  SourceLocation get location {
    return new Dart2JsSourceLocation(
      _sourceLibrary._library.entryCompilationUnit.script,
      _sourceLibrary.mirrors.compiler.spanFromNode(_node));
  }

  List<CombinatorMirror> get combinators {
    if (_combinators == null) {
      _combinators = <CombinatorMirror>[];
      if (_node.combinators != null) {
        for (Combinator combinator in _node.combinators.nodes) {
          List<String> identifiers = <String>[];
          for (Identifier identifier in combinator.identifiers.nodes) {
            identifiers.add(identifier.source);
          }
          _combinators.add(new Dart2JsCombinatorMirror(
              identifiers, isShow: combinator.isShow));
        }
      }
    }
    return _combinators;
  }

  LibraryMirror get sourceLibrary => _sourceLibrary;

  LibraryMirror get targetLibrary => _targetLibrary;

  String get prefix {
    Import import = _node.asImport();
    if (import != null && import.prefix != null) {
      return import.prefix.source;
    }
    return null;
  }

  bool get isImport => _node.asImport() != null;

  bool get isExport => _node.asExport() != null;
}

class Dart2JsCombinatorMirror implements CombinatorMirror {
  final List<String> identifiers;
  final bool isShow;

  Dart2JsCombinatorMirror(this.identifiers, {bool isShow: true})
      : this.isShow = isShow;

  bool get isHide => !isShow;
}

class Dart2JsSourceLocation implements SourceLocation {
  final Script _script;
  final SourceSpan _span;
  int _line;
  int _column;

  Dart2JsSourceLocation(this._script, this._span);

  int _computeLine() {
    var sourceFile = _script.file;
    if (sourceFile != null) {
      return sourceFile.getLine(offset) + 1;
    }
    var index = 0;
    var lineNumber = 0;
    while (index <= offset && index < sourceText.length) {
      index = sourceText.indexOf('\n', index) + 1;
      if (index <= 0) break;
      lineNumber++;
    }
    return lineNumber;
  }

  int get line {
    if (_line == null) {
      _line = _computeLine();
    }
    return _line;
  }

  int _computeColumn() {
    if (length == 0) return 0;

    var sourceFile = _script.file;
    if (sourceFile != null) {
      return sourceFile.getColumn(sourceFile.getLine(offset), offset) + 1;
    }
    int index = offset - 1;
    var columnNumber = 0;
    while (0 <= index && index < sourceText.length) {
      columnNumber++;
      var codeUnit = sourceText.codeUnitAt(index);
      if (codeUnit == $CR || codeUnit == $LF) {
        break;
      }
      index--;
    }
    return columnNumber;
  }

  int get column {
    if (_column == null) {
      _column = _computeColumn();
    }
    return _column;
  }

  int get offset => _span.begin;

  int get length => _span.end - _span.begin;

  String get text => _script.text.substring(_span.begin, _span.end);

  Uri get sourceUri => _script.uri;

  String get sourceText => _script.text;
}

class Dart2JsParameterMirror extends Dart2JsMemberMirror
    implements ParameterMirror {
  final MethodMirror _method;
  final bool isOptional;
  final bool isNamed;

  factory Dart2JsParameterMirror(Dart2JsMirrorSystem system,
                                 MethodMirror method,
                                 VariableElement element,
                                 {bool isOptional: false,
                                  bool isNamed: false}) {
    if (element is FieldParameterElement) {
      return new Dart2JsFieldParameterMirror(system,
          method, element, isOptional, isNamed);
    }
    return new Dart2JsParameterMirror._normal(system,
        method, element, isOptional, isNamed);
  }

  Dart2JsParameterMirror._normal(Dart2JsMirrorSystem system,
                         this._method,
                         VariableElement element,
                         this.isOptional,
                         this.isNamed)
    : super(system, element);

  Element get _beginElement => _variableElement.variables;

  DeclarationMirror get owner => _method;

  VariableElement get _variableElement => _element;

  String get qualifiedName => '${_method.qualifiedName}#${simpleName}';

  TypeMirror get type => _convertTypeToTypeMirror(mirrors,
      _variableElement.computeType(mirrors.compiler),
      mirrors.compiler.types.dynamicType,
      _variableElement.variables.functionSignature);


  bool get isFinal => false;

  bool get isConst => false;

  String get defaultValue {
    if (hasDefaultValue) {
      SendSet expression = _variableElement.cachedNode.asSendSet();
      return unparse(expression.arguments.head);
    }
    return null;
  }

  bool get hasDefaultValue {
    return _variableElement.cachedNode != null &&
        _variableElement.cachedNode is SendSet;
  }

  bool get isInitializingFormal => false;

  VariableMirror get initializedField => null;
}

class Dart2JsFieldParameterMirror extends Dart2JsParameterMirror {

  Dart2JsFieldParameterMirror(Dart2JsMirrorSystem system,
                              MethodMirror method,
                              FieldParameterElement element,
                              bool isOptional,
                              bool isNamed)
      : super._normal(system, method, element, isOptional, isNamed);

  FieldParameterElement get _fieldParameterElement => _element;

  TypeMirror get type {
    VariableListElement variables = _fieldParameterElement.variables;
    VariableDefinitions node = variables.parseNode(mirrors.compiler);
    if (node.type != null) {
      return super.type;
    }
    // Use the field type for initializing formals with no type annotation.
    return _convertTypeToTypeMirror(mirrors,
      _fieldParameterElement.fieldElement.computeType(mirrors.compiler),
      mirrors.compiler.types.dynamicType,
      _variableElement.variables.functionSignature);
  }

  bool get isInitializingFormal => true;

  VariableMirror get initializedField => new Dart2JsFieldMirror(
      _method.owner, _fieldParameterElement.fieldElement);
}

//------------------------------------------------------------------------------
// Declarations
//------------------------------------------------------------------------------
class Dart2JsClassMirror extends Dart2JsContainerMirror
    implements Dart2JsTypeMirror, ClassMirror {
  final Dart2JsLibraryMirror library;
  List<TypeVariableMirror> _typeVariables;

  Dart2JsClassMirror(Dart2JsMirrorSystem system, ClassElement _class)
      : this.library = system._getLibrary(_class.getLibrary()),
        super(system, _class);

  ClassElement get _class => _element;

  Dart2JsClassMirror.fromLibrary(Dart2JsLibraryMirror library,
                                 ClassElement _class)
      : this.library = library,
        super(library.mirrors, _class);

  DeclarationMirror get owner => library;

  String get qualifiedName => '${library.qualifiedName}.${simpleName}';

  void _ensureMembers() {
    if (_members == null) {
      _members = <String, Dart2JsMemberMirror>{};
      _class.forEachMember((_, e) {
        for (var member in _convertElementMemberToMemberMirrors(this, e)) {
          assert(!_members.containsKey(member.simpleName));
          _members[member.simpleName] = member;
        }
      });
    }
  }

  Map<String, MethodMirror> get methods => functions;

  Map<String, MethodMirror> get constructors {
    _ensureMembers();
    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
        _members, (m) => m.isConstructor ? m : null);
  }

  bool get isObject => _class == mirrors.compiler.objectClass;

  bool get isDynamic => false;

  bool get isVoid => false;

  bool get isTypeVariable => false;

  bool get isTypedef => false;

  bool get isFunction => false;

  ClassMirror get originalDeclaration => this;

  ClassMirror get superclass {
    if (_class.supertype != null) {
      return new Dart2JsInterfaceTypeMirror(mirrors, _class.supertype);
    }
    return null;
  }

  ClassMirror get mixin {
    if (_class.isMixinApplication) {
      MixinApplicationElement mixinApplication = _class;
      return new Dart2JsInterfaceTypeMirror(mirrors,
                                            mixinApplication.mixinType);
    }
    return this;
  }

  bool get isNameSynthetic {
    if (_class.isMixinApplication) {
      MixinApplicationElement mixinApplication = _class;
      return mixinApplication.isUnnamedMixinApplication;
    }
    return false;
  }

  List<ClassMirror> get superinterfaces {
    var list = <ClassMirror>[];
    Link<DartType> link = _class.interfaces;
    while (!link.isEmpty) {
      var type = _convertTypeToTypeMirror(mirrors, link.head,
                                          mirrors.compiler.types.dynamicType);
      list.add(type);
      link = link.tail;
    }
    return list;
  }

  bool get isClass => true;

  bool get isAbstract => _class.modifiers.isAbstract();

  bool get isOriginalDeclaration => true;

  List<TypeMirror> get typeArguments {
    throw new UnsupportedError(
        'Declarations do not have type arguments');
  }

  List<TypeVariableMirror> get typeVariables {
    if (_typeVariables == null) {
      _typeVariables = <TypeVariableMirror>[];
      _class.ensureResolved(mirrors.compiler);
      for (TypeVariableType typeVariable in _class.typeVariables) {
        _typeVariables.add(
            new Dart2JsTypeVariableMirror(mirrors, typeVariable));
      }
    }
    return _typeVariables;
  }

  bool operator ==(other) {
    if (identical(this, other)) {
      return true;
    }
    if (other is! ClassMirror) {
      return false;
    }
    if (library != other.library) {
      return false;
    }
    if (!identical(isOriginalDeclaration, other.isOriginalDeclaration)) {
      return false;
    }
    return qualifiedName == other.qualifiedName;
  }
}

class Dart2JsTypedefMirror extends Dart2JsTypeElementMirror
    implements Dart2JsTypeMirror, TypedefMirror {
  final Dart2JsLibraryMirror _library;
  List<TypeVariableMirror> _typeVariables;
  TypeMirror _definition;

  Dart2JsTypedefMirror(Dart2JsMirrorSystem system, TypedefType _typedef)
      : this._library = system._getLibrary(_typedef.element.getLibrary()),
        super(system, _typedef);

  Dart2JsTypedefMirror.fromLibrary(Dart2JsLibraryMirror library,
                                   TypedefType _typedef)
      : this._library = library,
        super(library.mirrors, _typedef);

  TypedefType get _typedef => _type;

  String get qualifiedName => '${library.qualifiedName}.${simpleName}';

  LibraryMirror get library => _library;

  bool get isTypedef => true;

  List<TypeMirror> get typeArguments {
    throw new UnsupportedError(
        'Declarations do not have type arguments');
  }

  List<TypeVariableMirror> get typeVariables {
    if (_typeVariables == null) {
      _typeVariables = <TypeVariableMirror>[];
      for (TypeVariableType typeVariable in _typedef.typeArguments) {
        _typeVariables.add(
            new Dart2JsTypeVariableMirror(mirrors, typeVariable));
      }
    }
    return _typeVariables;
  }

  TypeMirror get value {
    if (_definition == null) {
      // TODO(johnniwinther): Should be [ensureResolved].
      mirrors.compiler.resolveTypedef(_typedef.element);
      _definition = _convertTypeToTypeMirror(
          mirrors,
          _typedef.element.alias,
          mirrors.compiler.types.dynamicType,
          _typedef.element.functionSignature);
    }
    return _definition;
  }

  ClassMirror get originalDeclaration => this;

  // TODO(johnniwinther): How should a typedef respond to these?
  ClassMirror get superclass => null;

  List<ClassMirror> get superinterfaces => const <ClassMirror>[];

  // TODO(johnniwinther): Refactor [TypedefMirror] to not extend [ClassMirror]
  // and remove this.
  ClassMirror get mixin => this;

  bool get isClass => false;

  bool get isOriginalDeclaration => true;

  bool get isAbstract => false;
}

class Dart2JsTypeVariableMirror extends Dart2JsTypeElementMirror
    implements TypeVariableMirror {
  final TypeVariableType _typeVariableType;
  ClassMirror _declarer;

  Dart2JsTypeVariableMirror(Dart2JsMirrorSystem system,
                            TypeVariableType typeVariableType)
    : this._typeVariableType = typeVariableType,
      super(system, typeVariableType) {
      assert(_typeVariableType != null);
  }


  String get qualifiedName => '${declarer.qualifiedName}.${simpleName}';

  ClassMirror get declarer {
    if (_declarer == null) {
      if (_typeVariableType.element.enclosingElement.isClass()) {
        _declarer = new Dart2JsClassMirror(mirrors,
            _typeVariableType.element.enclosingElement);
      } else if (_typeVariableType.element.enclosingElement.isTypedef()) {
        _declarer = new Dart2JsTypedefMirror(mirrors,
            _typeVariableType.element.enclosingElement.computeType(
                mirrors.compiler));
      }
    }
    return _declarer;
  }

  LibraryMirror get library => declarer.library;

  DeclarationMirror get owner => declarer;

  bool get isTypeVariable => true;

  TypeMirror get upperBound => _convertTypeToTypeMirror(
      mirrors,
      _typeVariableType.element.bound,
      mirrors.compiler.objectClass.computeType(mirrors.compiler));

  bool operator ==(var other) {
    if (identical(this, other)) {
      return true;
    }
    if (other is! TypeVariableMirror) {
      return false;
    }
    if (declarer != other.declarer) {
      return false;
    }
    return qualifiedName == other.qualifiedName;
  }
}


//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------

abstract class Dart2JsTypeElementMirror extends Dart2JsElementMirror
    implements Dart2JsTypeMirror {
  final DartType _type;

  Dart2JsTypeElementMirror(Dart2JsMirrorSystem system, DartType type)
    : super(system, type.element),
      this._type = type;

  String get simpleName => _type.name;

  DeclarationMirror get owner => library;

  LibraryMirror get library {
    return mirrors._getLibrary(_type.element.getLibrary());
  }

  bool get isObject => false;

  bool get isVoid => false;

  bool get isDynamic => false;

  bool get isTypeVariable => false;

  bool get isTypedef => false;

  bool get isFunction => false;

  String toString() => _type.toString();

  Map<String, MemberMirror> get members => const <String, MemberMirror>{};

  Map<String, MethodMirror> get constructors => const <String, MethodMirror>{};

  Map<String, MethodMirror> get methods => const <String, MethodMirror>{};

  Map<String, MethodMirror> get getters => const <String, MethodMirror>{};

  Map<String, MethodMirror> get setters => const <String, MethodMirror>{};

  Map<String, VariableMirror> get variables => const <String, VariableMirror>{};
}

class Dart2JsInterfaceTypeMirror extends Dart2JsTypeElementMirror
    implements ClassMirror {
  List<TypeMirror> _typeArguments;

  Dart2JsInterfaceTypeMirror(Dart2JsMirrorSystem system,
                             InterfaceType interfaceType)
      : super(system, interfaceType);

  InterfaceType get _interfaceType => _type;

  bool get isNameSynthetic => originalDeclaration.isNameSynthetic;

  String get qualifiedName => originalDeclaration.qualifiedName;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  Map<String, MemberMirror> get members => originalDeclaration.members;

  bool get isObject => mirrors.compiler.objectClass == _type.element;

  // TODO(johnniwinther): How to show malformed types?
  bool get isDynamic => _type.isDynamic;

  ClassMirror get originalDeclaration
      => new Dart2JsClassMirror(mirrors, _type.element);

  // TODO(johnniwinther): Substitute type arguments for type variables.
  ClassMirror get superclass => originalDeclaration.superclass;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  ClassMirror get mixin {
    if (originalDeclaration.mixin == originalDeclaration) {
      return this;
    }
    return originalDeclaration.mixin;
  }

  bool get isClass => originalDeclaration.isClass;

  bool get isAbstract => originalDeclaration.isAbstract;

  bool get isPrivate => originalDeclaration.isPrivate;

  bool get isOriginalDeclaration => false;

  List<TypeMirror> get typeArguments {
    if (_typeArguments == null) {
      _typeArguments = <TypeMirror>[];
      if (!_interfaceType.isRaw) {
        Link<DartType> type = _interfaceType.typeArguments;
        while (type != null && type.head != null) {
          _typeArguments.add(_convertTypeToTypeMirror(mirrors, type.head,
              mirrors.compiler.types.dynamicType));
          type = type.tail;
        }
      }
    }
    return _typeArguments;
  }

  List<TypeVariableMirror> get typeVariables =>
      originalDeclaration.typeVariables;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  Map<String, MethodMirror> get constructors =>
      originalDeclaration.constructors;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  Map<String, MethodMirror> get methods => originalDeclaration.methods;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  Map<String, MethodMirror> get setters => originalDeclaration.setters;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  Map<String, MethodMirror> get getters => originalDeclaration.getters;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  Map<String, VariableMirror> get variables => originalDeclaration.variables;

  bool operator ==(other) {
    if (identical(this, other)) {
      return true;
    }
    if (other is! ClassMirror) {
      return false;
    }
    if (other.isOriginalDeclaration) {
      return false;
    }
    if (originalDeclaration != other.originalDeclaration) {
      return false;
    }
    var thisTypeArguments = typeArguments.iterator;
    var otherTypeArguments = other.typeArguments.iterator;
    while (thisTypeArguments.moveNext()) {
      if (!otherTypeArguments.moveNext()) return false;
      if (thisTypeArguments.current != otherTypeArguments.current) {
        return false;
      }
    }
    return !otherTypeArguments.moveNext();
  }
}


class Dart2JsFunctionTypeMirror extends Dart2JsTypeElementMirror
    implements FunctionTypeMirror {
  final FunctionSignature _functionSignature;
  List<ParameterMirror> _parameters;

  Dart2JsFunctionTypeMirror(Dart2JsMirrorSystem system,
                            FunctionType functionType, this._functionSignature)
      : super(system, functionType) {
    assert (_functionSignature != null);
  }

  FunctionType get _functionType => _type;

  // TODO(johnniwinther): Is this the qualified name of a function type?
  String get qualifiedName => originalDeclaration.qualifiedName;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  Map<String, MemberMirror> get members {
    var method = callMethod;
    if (method != null) {
      var map = new Map<String, MemberMirror>.from(
          originalDeclaration.members);
      var name = method.qualifiedName;
      assert(!map.containsKey(name));
      map[name] = method;
      return new ImmutableMapWrapper<String, MemberMirror>(map);
    }
    return originalDeclaration.members;
  }

  bool get isFunction => true;

  MethodMirror get callMethod => _convertElementMethodToMethodMirror(
      mirrors._getLibrary(_functionType.element.getLibrary()),
      _functionType.element);

  ClassMirror get originalDeclaration
      => new Dart2JsClassMirror(mirrors, mirrors.compiler.functionClass);

  // TODO(johnniwinther): Substitute type arguments for type variables.
  ClassMirror get superclass => originalDeclaration.superclass;

  // TODO(johnniwinther): Substitute type arguments for type variables.
  List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces;

  ClassMirror get mixin => this;

  bool get isClass => originalDeclaration.isClass;

  bool get isPrivate => originalDeclaration.isPrivate;

  bool get isOriginalDeclaration => false;

  bool get isAbstract => false;

  List<TypeMirror> get typeArguments => const <TypeMirror>[];

  List<TypeVariableMirror> get typeVariables =>
      originalDeclaration.typeVariables;

  TypeMirror get returnType {
    return _convertTypeToTypeMirror(mirrors, _functionType.returnType,
                                    mirrors.compiler.types.dynamicType);
  }

  List<ParameterMirror> get parameters {
    if (_parameters == null) {
      _parameters = _parametersFromFunctionSignature(mirrors, callMethod,
                                                     _functionSignature);
    }
    return _parameters;
  }
}

class Dart2JsVoidMirror extends Dart2JsTypeElementMirror {

  Dart2JsVoidMirror(Dart2JsMirrorSystem system, VoidType voidType)
      : super(system, voidType);

  VoidType get _voidType => _type;

  String get qualifiedName => simpleName;

  /**
   * The void type has no location.
   */
  SourceLocation get location => null;

  /**
   * The void type has no library.
   */
  LibraryMirror get library => null;

  bool get isVoid => true;

  bool operator ==(other) {
    if (identical(this, other)) {
      return true;
    }
    if (other is! TypeMirror) {
      return false;
    }
    return other.isVoid;
  }
}


class Dart2JsDynamicMirror extends Dart2JsTypeElementMirror {
  Dart2JsDynamicMirror(Dart2JsMirrorSystem system, InterfaceType voidType)
      : super(system, voidType);

  InterfaceType get _dynamicType => _type;

  String get qualifiedName => simpleName;

  /**
   * The dynamic type has no location.
   */
  SourceLocation get location => null;

  /**
   * The dynamic type has no library.
   */
  LibraryMirror get library => null;

  bool get isDynamic => true;

  bool operator ==(other) {
    if (identical(this, other)) {
      return true;
    }
    if (other is! TypeMirror) {
      return false;
    }
    return other.isDynamic;
  }
}

//------------------------------------------------------------------------------
// Member mirrors implementation.
//------------------------------------------------------------------------------

class Dart2JsMethodMirror extends Dart2JsMemberMirror
    implements MethodMirror {
  final Dart2JsContainerMirror _objectMirror;
  final String simpleName;
  final Dart2JsMethodKind _kind;

  Dart2JsMethodMirror._internal(Dart2JsContainerMirror objectMirror,
      FunctionElement function,
      String this.simpleName,
      Dart2JsMethodKind this._kind)
      : this._objectMirror = objectMirror,
        super(objectMirror.mirrors, function);

  factory Dart2JsMethodMirror(Dart2JsContainerMirror objectMirror,
                              FunctionElement function) {
    String realName = function.name;
    // TODO(ahe): This method should not be calling
    // Elements.operatorNameToIdentifier.
    String simpleName =
        Elements.operatorNameToIdentifier(function.name);
    Dart2JsMethodKind kind;
    if (function.kind == ElementKind.GETTER) {
      kind = Dart2JsMethodKind.GETTER;
    } else if (function.kind == ElementKind.SETTER) {
      kind = Dart2JsMethodKind.SETTER;
      simpleName = '$simpleName=';
    } else if (function.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
      // TODO(johnniwinther): Support detection of redirecting constructors.
      if (function.modifiers.isConst()) {
        kind = Dart2JsMethodKind.CONST;
      } else {
        kind = Dart2JsMethodKind.GENERATIVE;
      }
    } else if (function.modifiers.isFactory()) {
      kind = Dart2JsMethodKind.FACTORY;
    } else if (realName == 'unary-') {
      kind = Dart2JsMethodKind.OPERATOR;
      // Simple name is 'unary-'.
      simpleName = Mirror.UNARY_MINUS;
    } else if (simpleName.startsWith('operator\$')) {
      String str = simpleName.substring(9);
      simpleName = 'operator';
      kind = Dart2JsMethodKind.OPERATOR;
      simpleName = _getOperatorFromOperatorName(str);
    } else {
      kind = Dart2JsMethodKind.REGULAR;
    }
    return new Dart2JsMethodMirror._internal(objectMirror, function,
        simpleName, kind);
  }

  FunctionElement get _function => _element;

  String get qualifiedName
      => '${owner.qualifiedName}.$simpleName';

  DeclarationMirror get owner => _objectMirror;

  bool get isTopLevel => _objectMirror is LibraryMirror;

  bool get isConstructor
      => isGenerativeConstructor || isConstConstructor ||
         isFactoryConstructor || isRedirectingConstructor;

  bool get isMethod => !isConstructor;

  bool get isStatic => _function.modifiers.isStatic();

  List<ParameterMirror> get parameters {
    return _parametersFromFunctionSignature(mirrors, this,
        _function.computeSignature(mirrors.compiler));
  }

  TypeMirror get returnType => _convertTypeToTypeMirror(
      mirrors, _function.computeSignature(mirrors.compiler).returnType,
      mirrors.compiler.types.dynamicType);

  bool get isAbstract => _function.isAbstract;

  bool get isRegularMethod => !(isGetter || isSetter || isConstructor);

  bool get isConstConstructor => _kind == Dart2JsMethodKind.CONST;

  bool get isGenerativeConstructor => _kind == Dart2JsMethodKind.GENERATIVE;

  bool get isRedirectingConstructor => _kind == Dart2JsMethodKind.REDIRECTING;

  bool get isFactoryConstructor => _kind == Dart2JsMethodKind.FACTORY;

  bool get isGetter => _kind == Dart2JsMethodKind.GETTER;

  bool get isSetter => _kind == Dart2JsMethodKind.SETTER;

  bool get isOperator => _kind == Dart2JsMethodKind.OPERATOR;

  DeclarationMirror lookupInScope(String name) {
    for (ParameterMirror parameter in parameters) {
      if (parameter.simpleName == name) {
        return parameter;
      }
    }
    return super.lookupInScope(name);
  }
}

class Dart2JsFieldMirror extends Dart2JsMemberMirror implements VariableMirror {
  Dart2JsContainerMirror _objectMirror;
  VariableElement _variable;

  Dart2JsFieldMirror(Dart2JsContainerMirror objectMirror,
                     VariableElement variable)
      : this._objectMirror = objectMirror,
        this._variable = variable,
        super(objectMirror.mirrors, variable);

  Element get _beginElement => _variable.variables;

  String get qualifiedName
      => '${owner.qualifiedName}.$simpleName';

  DeclarationMirror get owner => _objectMirror;

  bool get isTopLevel => _objectMirror is LibraryMirror;

  bool get isVariable => true;

  bool get isStatic => _variable.modifiers.isStatic();

  bool get isFinal => _variable.modifiers.isFinal();

  bool get isConst => _variable.modifiers.isConst();

  TypeMirror get type => _convertTypeToTypeMirror(mirrors,
      _variable.computeType(mirrors.compiler),
      mirrors.compiler.types.dynamicType);
}

////////////////////////////////////////////////////////////////////////////////
// Mirrors on constant values used for metadata.
////////////////////////////////////////////////////////////////////////////////

class Dart2JsConstantMirror extends InstanceMirror {
  final Dart2JsMirrorSystem mirrors;
  final Constant _constant;

  Dart2JsConstantMirror(this.mirrors, this._constant);

  ClassMirror get type {
    return new Dart2JsClassMirror(mirrors,
        _constant.computeType(mirrors.compiler).element);
  }

  bool get hasReflectee => false;

  get reflectee {
    // TODO(johnniwinther): Which exception/error should be thrown here?
    throw new UnsupportedError('InstanceMirror does not have a reflectee');
  }

  InstanceMirror getField(String fieldName) {
    // TODO(johnniwinther): Which exception/error should be thrown here?
    throw new UnsupportedError('InstanceMirror does not have a reflectee');
  }
}

class Dart2JsNullConstantMirror extends Dart2JsConstantMirror {
  Dart2JsNullConstantMirror(Dart2JsMirrorSystem mirrors, NullConstant constant)
      : super(mirrors, constant);

  NullConstant get _constant => super._constant;

  bool get hasReflectee => true;

  get reflectee => null;
}

class Dart2JsBoolConstantMirror extends Dart2JsConstantMirror {
  Dart2JsBoolConstantMirror(Dart2JsMirrorSystem mirrors, BoolConstant constant)
      : super(mirrors, constant);

  Dart2JsBoolConstantMirror.fromBool(Dart2JsMirrorSystem mirrors, bool value)
      : super(mirrors, value ? new TrueConstant() : new FalseConstant());

  BoolConstant get _constant => super._constant;

  bool get hasReflectee => true;

  get reflectee => _constant is TrueConstant;
}

class Dart2JsStringConstantMirror extends Dart2JsConstantMirror {
  Dart2JsStringConstantMirror(Dart2JsMirrorSystem mirrors,
                              StringConstant constant)
      : super(mirrors, constant);

  Dart2JsStringConstantMirror.fromString(Dart2JsMirrorSystem mirrors,
                                         String text)
      : super(mirrors, new StringConstant(new DartString.literal(text)));

  StringConstant get _constant => super._constant;

  bool get hasReflectee => true;

  get reflectee => _constant.value.slowToString();
}

class Dart2JsNumConstantMirror extends Dart2JsConstantMirror {
  Dart2JsNumConstantMirror(Dart2JsMirrorSystem mirrors,
                           NumConstant constant)
      : super(mirrors, constant);

  NumConstant get _constant => super._constant;

  bool get hasReflectee => true;

  get reflectee => _constant.value;
}

class Dart2JsListConstantMirror extends Dart2JsConstantMirror
    implements ListInstanceMirror {
  Dart2JsListConstantMirror(Dart2JsMirrorSystem mirrors,
                            ListConstant constant)
      : super(mirrors, constant);

  ListConstant get _constant => super._constant;

  int get length => _constant.length;

  InstanceMirror operator[](int index) {
    if (index < 0) throw new RangeError('Negative index');
    if (index >= _constant.length) throw new RangeError('Index out of bounds');
    return _convertConstantToInstanceMirror(mirrors, _constant.entries[index]);
  }
}

class Dart2JsMapConstantMirror extends Dart2JsConstantMirror
    implements MapInstanceMirror {
  List<String> _listCache;

  Dart2JsMapConstantMirror(Dart2JsMirrorSystem mirrors,
                           MapConstant constant)
      : super(mirrors, constant);

  MapConstant get _constant => super._constant;

  List<String> get _list {
    if (_listCache == null) {
      _listCache = new List<String>(_constant.keys.entries.length);
      int index = 0;
      for (StringConstant keyConstant in _constant.keys.entries) {
        _listCache[index] = keyConstant.value.slowToString();
        index++;
      }
    }
    return _listCache;
  }

  int get length => _constant.length;

  Iterable<String> get keys {
    // TODO(johnniwinther): Return an unmodifiable list instead.
    return new List<String>.from(_list);
  }

  InstanceMirror operator[](String key) {
    int index = _list.indexOf(key);
    if (index == -1) return null;
    return _convertConstantToInstanceMirror(mirrors, _constant.values[index]);
  }
}

class Dart2JsTypeConstantMirror extends Dart2JsConstantMirror
    implements TypeInstanceMirror {

  Dart2JsTypeConstantMirror(Dart2JsMirrorSystem mirrors,
                            TypeConstant constant)
      : super(mirrors, constant);

  TypeConstant get _constant => super._constant;

  TypeMirror get representedType => _convertTypeToTypeMirror(
      mirrors, _constant.representedType, mirrors.compiler.types.dynamicType);
}

class Dart2JsConstructedConstantMirror extends Dart2JsConstantMirror {
  Map<String,Constant> _fieldMapCache;

  Dart2JsConstructedConstantMirror(Dart2JsMirrorSystem mirrors,
                                   ConstructedConstant constant)
      : super(mirrors, constant);

  ConstructedConstant get _constant => super._constant;

  Map<String,Constant> get _fieldMap {
    if (_fieldMapCache == null) {
      _fieldMapCache = new Map<String,Constant>();
      if (identical(_constant.type.element.kind, ElementKind.CLASS)) {
        var index = 0;
        ClassElement element = _constant.type.element;
        element.forEachInstanceField((_, Element field) {
          String fieldName = field.name;
          _fieldMapCache.putIfAbsent(fieldName, () => _constant.fields[index]);
          index++;
        }, includeSuperAndInjectedMembers: true);
      }
    }
    return _fieldMapCache;
  }

  InstanceMirror getField(String fieldName) {
    Constant fieldConstant = _fieldMap[fieldName];
    if (fieldConstant != null) {
      return _convertConstantToInstanceMirror(mirrors, fieldConstant);
    }
    return super.getField(fieldName);
  }
}

class Dart2JsCommentInstanceMirror implements CommentInstanceMirror {
  final Dart2JsMirrorSystem mirrors;
  final String text;
  String _trimmedText;

  Dart2JsCommentInstanceMirror(this.mirrors, this.text);

  ClassMirror get type {
    return new Dart2JsClassMirror(mirrors, mirrors.compiler.documentClass);
  }

  bool get isDocComment => text.startsWith('/**') || text.startsWith('///');

  String get trimmedText {
    if (_trimmedText == null) {
      _trimmedText = stripComment(text);
    }
    return _trimmedText;
  }

  bool get hasReflectee => false;

  get reflectee {
    // TODO(johnniwinther): Which exception/error should be thrown here?
    throw new UnsupportedError('InstanceMirror does not have a reflectee');
  }

  InstanceMirror getField(String fieldName) {
    if (fieldName == 'isDocComment') {
      return new Dart2JsBoolConstantMirror.fromBool(mirrors, isDocComment);
    } else if (fieldName == 'text') {
      return new Dart2JsStringConstantMirror.fromString(mirrors, text);
    } else if (fieldName == 'trimmedText') {
      return new Dart2JsStringConstantMirror.fromString(mirrors, trimmedText);
    }
    // TODO(johnniwinther): Which exception/error should be thrown here?
    throw new UnsupportedError('InstanceMirror does not have a reflectee');
  }
}

_convertElementToMembers(Dart2JsLibraryMirror library, Element e) {
  // TODO(ahe): This method creates new mirror objects which is not correct.
  if (e.isClass()) {
    ClassElement classElement = e;
    classElement.ensureResolved(library.mirrors.compiler);
    return [new Dart2JsClassMirror.fromLibrary(library, classElement)];
  } else if (e.isTypedef()) {
    return [new Dart2JsTypedefMirror.fromLibrary(
          library, e.computeType(library.mirrors.compiler))];
  } else {
    return _convertElementMemberToMemberMirrors(library, e);
  }
}

/**
 * Converts [element] into its corresponding [DeclarationMirror], if any.
 *
 * If [element] is an [AbstractFieldElement] the mirror for its getter is
 * returned or, if not present, the mirror for its setter.
 */
DeclarationMirror _convertElementToDeclarationMirror(Dart2JsMirrorSystem system,
                                                     Element element) {
  if (element.isTypeVariable()) {
    return new Dart2JsTypeVariableMirror(
        system, element.computeType(system.compiler));
  }

  Dart2JsLibraryMirror library = system._libraryMap[element.getLibrary()];
  if (element.isLibrary()) return library;
  if (element.isTypedef()) {
    return new Dart2JsTypedefMirror.fromLibrary(
        library, element.computeType(system.compiler));
  }

  Dart2JsContainerMirror container = library;
  if (element.getEnclosingClass() != null) {
    container = new Dart2JsClassMirror.fromLibrary(
        library, element.getEnclosingClass());
  }
  if (element.isClass()) return container;
  if (element.isParameter()) {
    MethodMirror method = _convertElementMethodToMethodMirror(
        container, element.getOutermostEnclosingMemberOrTopLevel());
    // TODO(johnniwinther): Find the right info for [isOptional] and [isNamed].
    return new Dart2JsParameterMirror(
        system, method, element, isOptional: false, isNamed: false);
  }
  Iterable<MemberMirror> members =
      _convertElementMemberToMemberMirrors(container, element);
  if (members.isEmpty) return null;
  return members.first;
}

/**
 * Experimental API for accessing compilation units defined in a
 * library.
 */
// TODO(ahe): Superclasses? Is this really a mirror?
class Dart2JsCompilationUnitMirror extends Dart2JsMirror {
  final Dart2JsLibraryMirror _library;
  final CompilationUnitElement _element;

  Dart2JsCompilationUnitMirror(this._element, this._library);

  Dart2JsMirrorSystem get mirrors => _library.mirrors;

  List<DeclarationMirror> get members {
    // TODO(ahe): Should return an immutable List.
    // TODO(johnniwinther): make sure that these are returned in declaration
    // order.
    List<DeclarationMirror> members= <DeclarationMirror>[];
    _element.forEachLocalMember((m) {
      members.addAll(_convertElementToMembers(_library, m));
    });
    return members;
  }

  Uri get uri => _element.script.uri;
}

/**
 * Transitional class that allows access to features that have not yet
 * made it to the mirror API.
 *
 * All API in this class is experimental.
 */
class BackDoor {
  /// Return the compilation units comprising [library].
  static List<Mirror> compilationUnitsOf(Dart2JsLibraryMirror library) {
    return library._library.compilationUnits.toList().map(
        (cu) => new Dart2JsCompilationUnitMirror(cu, library)).toList();
  }
}
