/*
 * Copyright (c) 2013 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;

import com.ibm.wala.cast.js.loader.JavaScriptLoader;
import com.ibm.wala.cast.js.types.JavaScriptTypes;
import com.ibm.wala.cast.types.AstMethodReference;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.MethodTargetSelector;
import com.ibm.wala.ipa.callgraph.propagation.ConstantKey;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.callgraph.propagation.LocalPointerKey;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.intset.OrdinalSet;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

public class LoadFileTargetSelector implements MethodTargetSelector {

  private final MethodTargetSelector base;

  private final JSSSAPropagationCallGraphBuilder builder;

  private final TypeReference loadFileRef =
      TypeReference.findOrCreate(
          JavaScriptTypes.jsLoader, TypeName.string2TypeName("Lprologue.js/loadFile"));

  private final MethodReference loadFileFunRef = AstMethodReference.fnReference(loadFileRef);

  private final HashSet<String> loadedFiles = HashSetFactory.make();

  @Override
  public IMethod getCalleeTarget(CGNode caller, CallSiteReference site, IClass receiver) {
    IMethod target = base.getCalleeTarget(caller, site, receiver);
    if (target != null && target.getReference().equals(loadFileFunRef)) {

      Set<String> names = new HashSet<>();
      SSAInstruction call =
          caller.getIR()
              .getInstructions()[
              caller.getIR().getCallInstructionIndices(site).intIterator().next()];
      if (call.getNumberOfUses() > 1) {
        LocalPointerKey fileNameV = new LocalPointerKey(caller, call.getUse(1));
        OrdinalSet<InstanceKey> ptrs = builder.getPointerAnalysis().getPointsToSet(fileNameV);
        for (InstanceKey k : ptrs) {
          if (k instanceof ConstantKey) {
            Object v = ((ConstantKey<?>) k).getValue();
            if (v instanceof String) {
              names.add((String) v);
            }
          }
        }

        if (names.size() == 1) {
          String str = names.iterator().next();
          try {
            JavaScriptLoader cl =
                (JavaScriptLoader) builder.getClassHierarchy().getLoader(JavaScriptTypes.jsLoader);
            URL url = new URL(builder.getBaseURL(), str);
            if (!loadedFiles.contains(url.toString())) {
              // try to open the input stream for the URL.  if it fails, we'll get an IOException
              // and fall through to default case
              try (InputStream inputStream = url.openConnection().getInputStream()) {}
              JSCallGraphUtil.loadAdditionalFile(builder.getClassHierarchy(), cl, url);
              loadedFiles.add(url.toString());
              IClass script =
                  builder
                      .getClassHierarchy()
                      .lookupClass(
                          TypeReference.findOrCreate(cl.getReference(), 'L' + url.getFile()));
              return script.getMethod(AstMethodReference.fnSelector);
            }
          } catch (RuntimeException | IOException e1) {
            // do nothing, fall through and return 'target'
          }
        }
      }
    }

    return target;
  }

  public LoadFileTargetSelector(
      MethodTargetSelector base, JSSSAPropagationCallGraphBuilder builder) {
    super();
    this.base = base;
    this.builder = builder;
  }
}
