/*
 * Copyright (c) 2011 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */

package com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction;

import com.ibm.wala.cast.tree.CAstAnnotation;
import com.ibm.wala.cast.tree.CAstControlFlowMap;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.cast.tree.CAstNodeTypeMap;
import com.ibm.wala.cast.tree.CAstQualifier;
import com.ibm.wala.cast.tree.CAstSourcePositionMap;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.cast.tree.CAstType;
import com.ibm.wala.cast.tree.rewrite.CAstRewriter.Rewrite;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.collections.Iterator2Iterable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * A simple implementation of {@link CAstEntity} used by the {@link ClosureExtractor}.
 *
 * @author mschaefer
 */
class ExtractedFunction implements CAstEntity {
  private final String name;
  private String[] parms;
  private final ExtractionPos pos;
  private Rewrite r;
  private CAstNode root;
  private CAstControlFlowMap cfg;
  private CAstSourcePositionMap posmap;
  private CAstNodeTypeMap types;
  private Map<CAstNode, Collection<CAstEntity>> scopedEntities;

  public ExtractedFunction(String name, ExtractionPos pos) {
    this.name = name;
    this.pos = pos;
  }

  public void setRewrite(Rewrite r) {
    assert this.r == null : "Rewrite shouldn't be set more than once.";
    this.r = r;
    this.root = r.newRoot();
    this.cfg = r.newCfg();
    this.posmap = r.newPos();
    this.types = r.newTypes();
  }

  @Override
  public int getKind() {
    return CAstEntity.FUNCTION_ENTITY;
  }

  @Override
  public String getName() {
    return name;
  }

  @Override
  public String getSignature() {
    return null;
  }

  @Override
  public String[] getArgumentNames() {
    computeParms();
    return parms;
  }

  @Override
  public CAstNode[] getArgumentDefaults() {
    return new CAstNode[0];
  }

  @Override
  public int getArgumentCount() {
    computeParms();
    return parms.length;
  }

  private void computeParms() {
    if (this.parms == null) {
      ArrayList<String> parms = new ArrayList<>();
      parms.add(name);
      parms.add("this");
      parms.addAll(pos.getParameters());
      if (pos.containsThis()) parms.add(pos.getThisParmName());
      this.parms = parms.toArray(new String[0]);
    }
  }

  @Override
  public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
    if (scopedEntities == null) {
      scopedEntities = HashMapFactory.make();
      // first, add all existing entities inside the body of this for loop
      for (Entry<CAstNode, Collection<CAstEntity>> e : r.newChildren().entrySet()) {
        if (NodePos.inSubtree(e.getKey(), root)) {
          Collection<CAstEntity> c = scopedEntities.get(e.getKey());
          if (c == null) scopedEntities.put(e.getKey(), c = HashSetFactory.make());
          c.addAll(e.getValue());
        }
      }
      // now, add all new entities which arise from extracted nested for-in loops
      for (ExtractionPos nested_loop : Iterator2Iterable.make(pos.getNestedLoops())) {
        CAstNode callsite = nested_loop.getCallSite();
        CAstEntity scoped_entity = nested_loop.getExtractedEntity();
        Collection<CAstEntity> c = scopedEntities.get(callsite);
        if (c == null) scopedEntities.put(callsite, c = HashSetFactory.make());
        c.add(scoped_entity);
      }
    }
    return scopedEntities;
  }

  @Override
  public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
    if (getAllScopedEntities().containsKey(construct)) {
      return getAllScopedEntities().get(construct).iterator();
    } else {
      return EmptyIterator.instance();
    }
  }

  @Override
  public CAstNode getAST() {
    return root;
  }

  @Override
  public CAstControlFlowMap getControlFlow() {
    return cfg;
  }

  @Override
  public CAstSourcePositionMap getSourceMap() {
    return posmap;
  }

  @Override
  public Position getPosition() {
    return getSourceMap().getPosition(root);
  }

  @Override
  public CAstNodeTypeMap getNodeTypeMap() {
    return types;
  }

  @Override
  public Collection<CAstQualifier> getQualifiers() {
    return null;
  }

  @Override
  public CAstType getType() {
    return null;
  }

  @Override
  public Collection<CAstAnnotation> getAnnotations() {
    return null;
  }

  @Override
  public String toString() {
    return "<JS function " + name + '>';
  }

  @Override
  public Position getPosition(int arg) {
    return null;
  }

  @Override
  public Position getNamePosition() {
    return null;
  }
}
