// 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.

part of dart_backend;

class LocalPlaceholder {
  final String identifier;
  final Set<Node> nodes;
  LocalPlaceholder(this.identifier) : nodes = new Set<Node>();
  int get hashCode => identifier.hashCode;
  String toString() =>
      'local_placeholder[id($identifier), nodes($nodes)]';
}

class FunctionScope {
  final Set<String> parameterIdentifiers;
  final Set<LocalPlaceholder> localPlaceholders;
  FunctionScope()
      : parameterIdentifiers = new Set<String>(),
      localPlaceholders = new Set<LocalPlaceholder>();
  void registerParameter(Identifier node) {
    parameterIdentifiers.add(node.source);
  }
}

class ConstructorPlaceholder {
  final Node node;
  final DartType type;
  final bool isRedirectingCall;
  ConstructorPlaceholder(this.node, this.type)
      : this.isRedirectingCall = false;
  // Note: factory redirection is not redirecting call!
  ConstructorPlaceholder.redirectingCall(this.node)
      : this.type = null, this.isRedirectingCall = true;
}

class DeclarationTypePlaceholder {
  final TypeAnnotation typeNode;
  final bool requiresVar;
  DeclarationTypePlaceholder(this.typeNode, this.requiresVar);
}

class SendVisitor extends ResolvedVisitor {
  final PlaceholderCollector collector;

  SendVisitor(collector, TreeElements elements)
      : this.collector = collector,
        super(elements, collector.compiler);

  visitOperatorSend(Send node) {
  }

  visitForeignSend(Send node) {}

  visitSuperSend(Send node) {
    Element element = elements[node];
    if (element != null && element.isConstructor()) {
      collector.makeRedirectingConstructorPlaceholder(node.selector, element);
    } else {
      collector.tryMakeMemberPlaceholder(node.selector);
    }
  }

  visitDynamicSend(Send node) {
    final element = elements[node];
    if (element == null || !element.isErroneous()) {
      collector.tryMakeMemberPlaceholder(node.selector);
    }
  }

  visitClosureSend(Send node) {
    final element = elements[node];
    if (element != null) {
      collector.tryMakeLocalPlaceholder(element, node.selector);
    }
  }

  visitGetterSend(Send node) {
    final element = elements[node];
    // element == null means dynamic property access.
    if (element == null) {
      collector.tryMakeMemberPlaceholder(node.selector);
    } else if (element.isErroneous()) {
      return;
    } else if (element.isPrefix()) {
      // Node is prefix part in case of source 'lib.somesetter = 5;'
      collector.makeNullPlaceholder(node);
    } else if (Elements.isStaticOrTopLevel(element)) {
      // Unqualified or prefixed top level or static.
      collector.makeElementPlaceholder(node.selector, element);
    } else if (!element.isTopLevel()) {
      if (element.isInstanceMember()) {
        collector.tryMakeMemberPlaceholder(node.selector);
      } else {
        // May get FunctionExpression here in selector
        // in case of A(int this.f());
        if (node.selector is Identifier) {
          collector.tryMakeLocalPlaceholder(element, node.selector);
        } else {
          assert(node.selector is FunctionExpression);
        }
      }
    }
  }

  visitAssert(node) {
    visitStaticSend(node);
  }

  visitStaticSend(Send node) {
    final element = elements[node];
    collector.backend.registerStaticSend(element, node);

    if (Elements.isUnresolved(element)
        || identical(element, compiler.assertMethod)
        || element.isDeferredLoaderGetter()) {
      return;
    }
    if (element.isConstructor() || element.isFactoryConstructor()) {
      // Rename named constructor in redirection position:
      // class C { C.named(); C.redirecting() : this.named(); }
      if (node.receiver is Identifier
          && node.receiver.asIdentifier().isThis()) {
        assert(node.selector is Identifier);
        collector.makeRedirectingConstructorPlaceholder(node.selector, element);
      }
      return;
    }
    collector.makeElementPlaceholder(node.selector, element);
    // Another ugly case: <lib prefix>.<top level> is represented as
    // receiver: lib prefix, selector: top level.
    if (element.isTopLevel() && node.receiver != null) {
      assert(elements[node.receiver].isPrefix());
      // Hack: putting null into map overrides receiver of original node.
      collector.makeNullPlaceholder(node.receiver);
    }
  }

  internalError(String reason, {Node node}) {
    collector.internalError(reason, node: node);
  }

  visitTypeReferenceSend(Send node) {
    collector.makeElementPlaceholder(node.selector, elements[node]);
  }
}

class PlaceholderCollector extends Visitor {
  final Compiler compiler;
  final Set<String> fixedMemberNames; // member names which cannot be renamed.
  final Map<Element, ElementAst> elementAsts;
  final Set<Node> nullNodes;  // Nodes that should not be in output.
  final Set<Node> unresolvedNodes;
  final Map<Element, Set<Node>> elementNodes;
  final Map<FunctionElement, FunctionScope> functionScopes;
  final Map<LibraryElement, Set<Identifier>> privateNodes;
  final List<DeclarationTypePlaceholder> declarationTypePlaceholders;
  final Map<String, Set<Identifier>> memberPlaceholders;
  final Map<Element, List<ConstructorPlaceholder>> constructorPlaceholders;
  Map<String, LocalPlaceholder> currentLocalPlaceholders;
  Element currentElement;
  FunctionElement topmostEnclosingFunction;
  TreeElements treeElements;

  LibraryElement get coreLibrary => compiler.coreLibrary;
  FunctionElement get entryFunction => compiler.mainFunction;
  DartBackend get backend => compiler.backend;

  get currentFunctionScope => functionScopes.putIfAbsent(
      topmostEnclosingFunction, () => new FunctionScope());

  PlaceholderCollector(this.compiler, this.fixedMemberNames, this.elementAsts) :
      nullNodes = new Set<Node>(),
      unresolvedNodes = new Set<Node>(),
      elementNodes = new Map<Element, Set<Node>>(),
      functionScopes = new Map<FunctionElement, FunctionScope>(),
      privateNodes = new Map<LibraryElement, Set<Identifier>>(),
      declarationTypePlaceholders = new List<DeclarationTypePlaceholder>(),
      memberPlaceholders = new Map<String, Set<Identifier>>(),
      constructorPlaceholders =
          new Map<Element, List<ConstructorPlaceholder>>();

  void collectFunctionDeclarationPlaceholders(
      FunctionElement element, FunctionExpression node) {
    if (element.isGenerativeConstructor() || element.isFactoryConstructor()) {
      DartType type = element.getEnclosingClass().thisType.asRaw();
      makeConstructorPlaceholder(node.name, element, type);
      Return bodyAsReturn = node.body.asReturn();
      if (bodyAsReturn != null && bodyAsReturn.isRedirectingFactoryBody) {
        // Factory redirection.
        FunctionElement redirectTarget = element.defaultImplementation;
        assert(redirectTarget != null && redirectTarget != element);
        type = redirectTarget.getEnclosingClass().thisType.asRaw();
        makeConstructorPlaceholder(
            bodyAsReturn.expression, redirectTarget, type);
      }
    } else if (Elements.isStaticOrTopLevel(element)) {
      // Note: this code should only rename private identifiers for class'
      // fields/getters/setters/methods.  Top-level identifiers are renamed
      // just to escape conflicts and that should be enough as we shouldn't
      // be able to resolve private identifiers for other libraries.
      makeElementPlaceholder(node.name, element);
    } else if (element.isMember()) {
      if (node.name is Identifier) {
        tryMakeMemberPlaceholder(node.name);
      } else {
        assert(node.name.asSend().isOperator);
      }
    }
  }

  void collectFieldDeclarationPlaceholders(Element element, Node node) {
    Identifier name = node is Identifier ? node : node.asSend().selector;
    if (Elements.isStaticOrTopLevel(element)) {
      makeElementPlaceholder(name, element);
    } else if (Elements.isInstanceField(element)) {
      tryMakeMemberPlaceholder(name);
    }
  }

  void collect(Element element) {
    this.currentElement = element;
    this.topmostEnclosingFunction = null;
    final ElementAst elementAst = elementAsts[element];
    this.treeElements = elementAst.treeElements;
    Node elementNode = elementAst.ast;
    if (element is FunctionElement) {
      collectFunctionDeclarationPlaceholders(element, elementNode);
    } else if (element is VariableElement) {
      VariableDefinitions definitions = elementNode;
      Node definition = definitions.definitions.nodes.head;
      final definitionElement = treeElements[elementNode];
      // definitionElement == null if variable is actually unused.
      if (definitionElement != null) {
        collectFieldDeclarationPlaceholders(definitionElement, definition);
      }
      makeVarDeclarationTypePlaceholder(definitions);
    } else {
      assert(element is ClassElement || element is TypedefElement);
    }
    currentLocalPlaceholders = new Map<String, LocalPlaceholder>();
    compiler.withCurrentElement(element, () {
      elementNode.accept(this);
    });
  }

  // TODO(karlklose): should we create placeholders for these?
  bool isTypedefParameter(Element element) {
    return element != null &&
        element.enclosingElement != null &&
        element.enclosingElement.isTypedef();
  }

  void tryMakeLocalPlaceholder(Element element, Identifier node) {
    bool isNamedOptionalParameter() {
      FunctionElement function = element.enclosingElement;
      FunctionSignature signature = function.functionSignature;
      if (!signature.optionalParametersAreNamed) return false;
      for (Element parameter in signature.optionalParameters) {
        if (identical(parameter, element)) return true;
      }
      return false;
    }

    // TODO(smok): Maybe we should rename privates as well, their privacy
    // should not matter if they are local vars.
    if (isPrivateName(node.source)) return;
    if (element.isParameter() && !isTypedefParameter(element) &&
        isNamedOptionalParameter()) {
      currentFunctionScope.registerParameter(node);
    } else if (Elements.isLocal(element) && !isTypedefParameter(element)) {
      makeLocalPlaceholder(node);
    }
  }

  void tryMakeMemberPlaceholder(Identifier node) {
    assert(node != null);
    if (isPrivateName(node.source)) return;
    if (node is Operator) return;
    final identifier = node.source;
    if (fixedMemberNames.contains(identifier)) return;
    memberPlaceholders.putIfAbsent(
        identifier, () => new Set<Identifier>()).add(node);
  }

  void makeTypePlaceholder(Node node, DartType type) {
    Send send = node.asSend();
    if (send != null) {
      // Prefix.
      assert(send.receiver is Identifier);
      assert(send.selector is Identifier);
      makeNullPlaceholder(send.receiver);
      node = send.selector;
    }
    makeElementPlaceholder(node, type.element);
  }

  void makeOmitDeclarationTypePlaceholder(TypeAnnotation type) {
    if (type == null) return;
    declarationTypePlaceholders.add(
        new DeclarationTypePlaceholder(type, false));
  }

  void makeVarDeclarationTypePlaceholder(VariableDefinitions node) {
    // TODO(smok): Maybe instead of calling this method and
    // makeDeclaratioTypePlaceholder have type declaration placeholder
    // collector logic in visitVariableDefinitions when resolver becomes better
    // and/or catch syntax changes.
    if (node.type == null) return;
    Element definitionElement = treeElements[node.definitions.nodes.head];
    bool requiresVar = !node.modifiers.isFinalOrConst();
    declarationTypePlaceholders.add(
        new DeclarationTypePlaceholder(node.type, requiresVar));
  }

  void makeNullPlaceholder(Node node) {
    assert(node is Identifier || node is Send);
    nullNodes.add(node);
  }

  void makeElementPlaceholder(Node node, Element element) {
    assert(element != null);
    if (identical(element, entryFunction)) return;
    if (identical(element.getLibrary(), coreLibrary)) return;
    if (element.getLibrary().isPlatformLibrary && !element.isTopLevel()) {
      return;
    }
    if (element == compiler.dynamicClass) {
      return;
    }
    elementNodes.putIfAbsent(element, () => new Set<Node>()).add(node);
  }

  void makePrivateIdentifier(Identifier node) {
    assert(node != null);
    privateNodes.putIfAbsent(
        currentElement.getLibrary(), () => new Set<Identifier>()).add(node);
  }

  void makeUnresolvedPlaceholder(Node node) {
    unresolvedNodes.add(node);
  }

  void makeLocalPlaceholder(Identifier identifier) {
    LocalPlaceholder getLocalPlaceholder() {
      String name = identifier.source;
      return currentLocalPlaceholders.putIfAbsent(name, () {
        LocalPlaceholder localPlaceholder = new LocalPlaceholder(name);
        currentFunctionScope.localPlaceholders.add(localPlaceholder);
        return localPlaceholder;
      });
    }

    getLocalPlaceholder().nodes.add(identifier);
  }

  void makeConstructorPlaceholder(Node node, Element element, DartType type) {
    assert(type != null);
    constructorPlaceholders
        .putIfAbsent(element, () => <ConstructorPlaceholder>[])
            .add(new ConstructorPlaceholder(node, type));
  }
  void makeRedirectingConstructorPlaceholder(Node node, Element element) {
    constructorPlaceholders
        .putIfAbsent(element, () => <ConstructorPlaceholder>[])
            .add(new ConstructorPlaceholder.redirectingCall(node));
  }

  void internalError(String reason, {Node node}) {
    compiler.internalError(node, reason);
  }

  visit(Node node) => (node == null) ? null : node.accept(this);

  visitNode(Node node) { node.visitChildren(this); }  // We must go deeper.

  visitNewExpression(NewExpression node) {
    Send send = node.send;
    DartType type = treeElements.getType(node);
    assert(type != null);
    Element constructor = treeElements[send];
    assert(constructor != null);
    assert(send.receiver == null);
    if (!Elements.isErroneousElement(constructor)) {
      makeConstructorPlaceholder(node.send.selector, constructor, type);
      // TODO(smok): Should this be in visitNamedArgument?
      // Field names can be exposed as names of optional arguments, e.g.
      // class C {
      //   final field;
      //   C([this.field]);
      // }
      // Do not forget to rename them as well.
      FunctionElement constructorFunction = constructor;
      Link<Element> optionalParameters =
          constructorFunction.functionSignature.optionalParameters;
      for (final argument in send.argumentsNode) {
        NamedArgument named = argument.asNamedArgument();
        if (named == null) continue;
        Identifier name = named.name;
        String nameAsString = name.source;
        for (final parameter in optionalParameters) {
          if (identical(parameter.kind, ElementKind.FIELD_PARAMETER)) {
            if (parameter.name == nameAsString) {
              tryMakeMemberPlaceholder(name);
              break;
            }
          }
        }
      }
    } else {
      makeUnresolvedPlaceholder(node.send.selector);
    }
    visit(node.send.argumentsNode);
  }

  visitSend(Send send) {
    new SendVisitor(this, treeElements).visitSend(send);
    send.visitChildren(this);
  }

  visitSendSet(SendSet send) {
    Element element = treeElements[send];
    if (Elements.isErroneousElement(element)) {
      // Complicated case: constructs like receiver.selector++ can resolve
      // to ErroneousElement.  Fortunately, receiver.selector still
      // can be resoved via treeElements[send.selector], that's all
      // that is needed to rename the construct properly.
      element = treeElements[send.selector];
    }
    if (element == null) {
      if (send.receiver != null) tryMakeMemberPlaceholder(send.selector);
    } else if (!element.isErroneous()) {
      if (Elements.isStaticOrTopLevel(element)) {
        // TODO(smok): Worth investigating why sometimes we get getter/setter
        // here and sometimes abstract field.
        assert(element.isClass() || element is VariableElement ||
               element.isAccessor() || element.isAbstractField() ||
               element.isFunction() || element.isTypedef() ||
               element is TypeVariableElement);
        makeElementPlaceholder(send.selector, element);
      } else {
        Identifier identifier = send.selector.asIdentifier();
        if (identifier == null) {
          // Handle optional function expression parameters with default values.
          identifier = send.selector.asFunctionExpression().name;
        }
        if (Elements.isInstanceField(element)) {
          tryMakeMemberPlaceholder(identifier);
        } else {
          tryMakeLocalPlaceholder(element, identifier);
        }
      }
    }
    send.visitChildren(this);
  }

  visitIdentifier(Identifier identifier) {
    if (isPrivateName(identifier.source)) makePrivateIdentifier(identifier);
  }

  visitTypeAnnotation(TypeAnnotation node) {
    final type = treeElements.getType(node);
    assert(invariant(node, type != null,
        message: "Missing type for type annotation: $treeElements"));
    if (!type.isVoid) {
      if (!type.treatAsDynamic) {
        makeTypePlaceholder(node.typeName, type);
      } else if (!type.isDynamic) {
        makeUnresolvedPlaceholder(node.typeName);
      }
    }
    // Visit only type arguments, otherwise in case of lib.Class type
    // annotation typeName is Send and we go to visitGetterSend, as a result
    // "Class" is added to member placeholders.
    visit(node.typeArguments);
  }

  visitVariableDefinitions(VariableDefinitions node) {
    Element definitionElement = treeElements[node];
    if (definitionElement == backend.mirrorHelperSymbolsMap) {
      backend.registerMirrorHelperElement(definitionElement, node);
    }
    // Collect only local placeholders.
    for (Node definition in node.definitions.nodes) {
      Element definitionElement = treeElements[definition];
      // definitionElement may be null if we're inside variable definitions
      // of a function that is a parameter of another function.
      // TODO(smok): Fix this when resolver correctly deals with
      // such cases.
      if (definitionElement == null) continue;
      Send send = definition.asSend();
      if (send != null) {
        // May get FunctionExpression here in definition.selector
        // in case of A(int this.f());
        if (send.selector is Identifier) {
          if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) {
            tryMakeMemberPlaceholder(send.selector);
          } else {
            tryMakeLocalPlaceholder(definitionElement, send.selector);
          }
        } else {
          assert(send.selector is FunctionExpression);
          if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) {
            tryMakeMemberPlaceholder(
                send.selector.asFunctionExpression().name);
          }
        }
      } else if (definition is Identifier) {
        tryMakeLocalPlaceholder(definitionElement, definition);
      } else if (definition is FunctionExpression) {
        // Skip, it will be processed in visitFunctionExpression.
      } else {
        internalError('Unexpected definition structure $definition');
      }
    }
    node.visitChildren(this);
  }

  visitFunctionExpression(FunctionExpression node) {
    bool isKeyword(Identifier id) =>
        id != null && Keyword.keywords[id.source] != null;

    Element element = treeElements[node];
    // May get null here in case of A(int this.f());
    if (element != null) {
      if (element == backend.mirrorHelperGetNameFunction) {
        backend.registerMirrorHelperElement(element, node);
      }
      // Rename only local functions.
      if (topmostEnclosingFunction == null) {
        topmostEnclosingFunction = element;
      }
      if (!identical(element, currentElement)) {
        if (node.name != null) {
          assert(node.name is Identifier);
          tryMakeLocalPlaceholder(element, node.name);
        }
      }
    }
    node.visitChildren(this);
    // Make sure we don't omit return type of methods which names are
    // identifiers, because the following works fine:
    // int interface() => 1;
    // But omitting 'int' makes VM unhappy.
    // TODO(smok): Remove it when http://dartbug.com/5278 is fixed.
    if (node.name == null || !isKeyword(node.name.asIdentifier())) {
      makeOmitDeclarationTypePlaceholder(node.returnType);
    }
    collectFunctionParameters(node.parameters);
  }

  void collectFunctionParameters(NodeList parameters) {
    if (parameters == null) return;
    for (Node parameter in parameters.nodes) {
      if (parameter is NodeList) {
        // Optional parameter list.
        collectFunctionParameters(parameter);
      } else {
        assert(parameter is VariableDefinitions);
        makeOmitDeclarationTypePlaceholder(
            parameter.asVariableDefinitions().type);
      }
    }
  }

  visitClassNode(ClassNode node) {
    ClassElement classElement = currentElement;
    makeElementPlaceholder(node.name, classElement);
    node.visitChildren(this);
  }

  visitNamedMixinApplication(NamedMixinApplication node) {
    ClassElement classElement = currentElement;
    makeElementPlaceholder(node.name, classElement);
    node.visitChildren(this);
  }

  visitTypeVariable(TypeVariable node) {
    DartType type = treeElements.getType(node);
    assert(invariant(node, type != null,
        message: "Missing type for type variable: $treeElements"));
    makeTypePlaceholder(node.name, type);
    node.visitChildren(this);
  }

  visitTypedef(Typedef node) {
    assert(currentElement is TypedefElement);
    makeElementPlaceholder(node.name, currentElement);
    node.visitChildren(this);
    makeOmitDeclarationTypePlaceholder(node.returnType);
    collectFunctionParameters(node.formals);
  }

  visitBlock(Block node) {
    for (Node statement in node.statements.nodes) {
      if (statement is VariableDefinitions) {
        makeVarDeclarationTypePlaceholder(statement);
      }
    }
    node.visitChildren(this);
  }
}
