/*
 * Copyright (c) 2002 - 2014 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.summaries;

import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil;
import com.ibm.wala.cast.js.loader.JavaScriptLoader;
import com.ibm.wala.cast.js.ssa.JSInstructionFactory;
import com.ibm.wala.cast.js.types.JavaScriptMethods;
import com.ibm.wala.cast.js.types.JavaScriptTypes;
import com.ibm.wala.cast.loader.DynamicCallSiteReference;
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.classLoader.Module;
import com.ibm.wala.classLoader.ModuleEntry;
import com.ibm.wala.classLoader.NewSiteReference;
import com.ibm.wala.classLoader.SourceModule;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.ipa.summaries.MethodSummary;
import com.ibm.wala.shrike.shrikeBT.IConditionalBranchInstruction.Operator;
import com.ibm.wala.ssa.ConstantValue;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.NonNullSingletonIterator;
import com.ibm.wala.util.collections.Pair;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class JavaScriptConstructorFunctions {

  private final Map<Object, IMethod> constructors = HashMapFactory.make();

  private final IClassHierarchy cha;

  public JavaScriptConstructorFunctions(IClassHierarchy cha) {
    this.cha = cha;
  }

  public static class JavaScriptConstructor extends JavaScriptSummarizedFunction {
    private final String toStringExtra;
    private final IClass constructorForType;

    private JavaScriptConstructor(
        MethodReference ref,
        MethodSummary summary,
        IClass declaringClass,
        IClass constructorForType,
        String toStringExtra) {
      super(ref, summary, declaringClass);
      this.toStringExtra = toStringExtra;
      this.constructorForType = constructorForType;
    }

    private JavaScriptConstructor(
        MethodReference ref,
        MethodSummary summary,
        IClass declaringClass,
        IClass constructorForType) {
      this(ref, summary, declaringClass, constructorForType, "");
    }

    @Override
    public String toString() {
      return "<ctor for " + getReference().getDeclaringClass() + toStringExtra + '>';
    }

    public IClass constructedType() {
      return constructorForType;
    }
  }

  private IMethod record(Object key, IMethod m) {
    constructors.put(key, m);
    return m;
  }

  private static IMethod makeNullaryValueConstructor(IClass cls, Object value) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = JavaScriptMethods.makeCtorReference(cls.getReference());
    JavaScriptSummary S = new JavaScriptSummary(ref, 1);

    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), 4, 1, "prototype"));
    S.getNumberOfStatements();

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            5,
            NewSiteReference.make(S.getNumberOfStatements(), cls.getReference())));

    S.addStatement(insts.SetPrototype(S.getNumberOfStatements(), 5, 4));
    // S.addStatement(insts.PutInstruction(5, 4, "__proto__"));
    S.getNumberOfStatements();

    S.addConstant(8, new ConstantValue(value));
    S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), 5, 8, "$value"));
    if (value instanceof String) {
      S.addConstant(9, new ConstantValue(0));
      S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), 5, 9, "length"));
    }
    S.getNumberOfStatements();

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 5, false));
    S.getNumberOfStatements();

    // S.addConstant(9, new ConstantValue("__proto__"));

    return new JavaScriptConstructor(ref, S, cls, cls);
  }

  private static IMethod makeUnaryValueConstructor(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = JavaScriptMethods.makeCtorReference(cls.getReference());
    JavaScriptSummary S = new JavaScriptSummary(ref, 2);

    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), 5, 1, "prototype"));
    S.getNumberOfStatements();

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            6,
            NewSiteReference.make(S.getNumberOfStatements(), cls.getReference())));

    S.addStatement(insts.SetPrototype(S.getNumberOfStatements(), 6, 5));
    // S.addStatement(insts.PutInstruction(6, 5, "__proto__"));
    S.getNumberOfStatements();

    S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), 6, 2, "$value"));
    S.getNumberOfStatements();

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 6, false));
    S.getNumberOfStatements();

    // S.addConstant(7, new ConstantValue("__proto__"));

    return new JavaScriptConstructor(ref, S, cls, cls);
  }

  private IMethod makeValueConstructor(IClass cls, int nargs, Object value) {
    if (nargs == 0 || nargs == 1) {

      Object key = Pair.make(cls, nargs);
      if (constructors.containsKey(key)) return constructors.get(key);
      else
        return record(
            key,
            (nargs == 0)
                ? makeNullaryValueConstructor(cls, value)
                : makeUnaryValueConstructor(cls));
    } else {
      // not a legal call, likely due to dataflow imprecision
      return null;
    }
  }

  /** create a method for constructing an Object with no arguments passed */
  private IMethod makeNullaryObjectConstructor(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = JavaScriptMethods.makeCtorReference(JavaScriptTypes.Object);
    JavaScriptSummary S = new JavaScriptSummary(ref, 1);

    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), 4, 1, "prototype"));
    S.getNumberOfStatements();

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            5,
            NewSiteReference.make(S.getNumberOfStatements(), JavaScriptTypes.Object)));

    S.addStatement(insts.SetPrototype(S.getNumberOfStatements(), 5, 4));
    // S.addStatement(insts.PutInstruction(5, 4, "__proto__"));
    S.getNumberOfStatements();

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 5, false));
    S.getNumberOfStatements();

    // S.addConstant(6, new ConstantValue("__proto__"));

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.Object));
  }

  private IMethod makeUnaryObjectConstructor(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = JavaScriptMethods.makeCtorReference(JavaScriptTypes.Object);
    JavaScriptSummary S = new JavaScriptSummary(ref, 2);

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 2, false));
    S.getNumberOfStatements();

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.Object));
  }

  private IMethod makeObjectConstructor(IClass cls, int nargs) {
    if (nargs == 0 || nargs == 1) {

      Object key = Pair.make(cls, nargs);
      if (constructors.containsKey(key)) return constructors.get(key);
      else
        return record(
            key,
            (nargs == 0) ? makeNullaryObjectConstructor(cls) : makeUnaryObjectConstructor(cls));

    } else {
      // not a legal call, likely the result of analysis imprecision
      return null;
    }
  }

  private IMethod makeObjectCall(IClass cls, int nargs) {
    assert nargs == 0;

    Object key = Pair.make(cls, nargs);
    if (constructors.containsKey(key)) return constructors.get(key);
    else return record(key, makeNullaryObjectConstructor(cls));
  }

  private IMethod makeArrayLengthConstructor(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = JavaScriptMethods.makeCtorReference(JavaScriptTypes.Array);
    JavaScriptSummary S = new JavaScriptSummary(ref, 2);

    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), 5, 1, "prototype"));
    S.getNumberOfStatements();

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            6,
            NewSiteReference.make(S.getNumberOfStatements(), JavaScriptTypes.Array)));

    S.addStatement(insts.SetPrototype(S.getNumberOfStatements(), 6, 5));
    // S.addStatement(insts.PutInstruction(6, 5, "__proto__"));
    S.getNumberOfStatements();

    S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), 6, 2, "length"));
    S.getNumberOfStatements();

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 6, false));
    S.getNumberOfStatements();

    // S.addConstant(7, new ConstantValue("__proto__"));

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.Array));
  }

  private IMethod makeArrayContentsConstructor(IClass cls, int nargs) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = JavaScriptMethods.makeCtorReference(JavaScriptTypes.Array);
    JavaScriptSummary S = new JavaScriptSummary(ref, nargs + 1);

    S.addConstant(nargs + 3, new ConstantValue("prototype"));
    S.addStatement(insts.PropertyRead(S.getNumberOfStatements(), nargs + 4, 1, nargs + 3));
    S.getNumberOfStatements();

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            nargs + 5,
            NewSiteReference.make(S.getNumberOfStatements(), JavaScriptTypes.Array)));

    S.addStatement(insts.SetPrototype(S.getNumberOfStatements(), nargs + 5, nargs + 4));
    // S.addStatement(insts.PutInstruction(nargs + 5, nargs + 4, "__proto__"));
    S.getNumberOfStatements();

    S.addConstant(nargs + 7, new ConstantValue(nargs));
    S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), nargs + 5, nargs + 7, "length"));
    S.getNumberOfStatements();

    int vn = nargs + 9;
    for (int i = 0; i < nargs; i++, vn += 2) {
      S.addConstant(vn, new ConstantValue(i));
      S.addStatement(insts.PropertyWrite(S.getNumberOfStatements(), nargs + 5, vn, i + 1));
      S.getNumberOfStatements();
    }

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 5, false));
    S.getNumberOfStatements();

    // S.addConstant(vn, new ConstantValue("__proto__"));

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.Array));
  }

  private IMethod makeArrayConstructor(IClass cls, int nargs) {
    Object key = Pair.make(cls, nargs);
    if (constructors.containsKey(key)) return constructors.get(key);
    else
      return record(
          key,
          (nargs == 1)
              ? makeArrayLengthConstructor(cls)
              : makeArrayContentsConstructor(cls, nargs));
  }

  private IMethod makeNullaryStringCall(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = AstMethodReference.fnReference(JavaScriptTypes.String);
    JavaScriptSummary S = new JavaScriptSummary(ref, 1);

    S.addConstant(2, new ConstantValue(""));

    S.addConstant(3, new ConstantValue(0));
    S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), 2, 3, "length"));
    S.getNumberOfStatements();

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 2, false));
    S.getNumberOfStatements();

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.String));
  }

  private IMethod makeUnaryStringCall(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = AstMethodReference.fnReference(JavaScriptTypes.String);
    JavaScriptSummary S = new JavaScriptSummary(ref, 2);

    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), 4, 2, "toString"));
    S.getNumberOfStatements();

    CallSiteReference cs =
        new DynamicCallSiteReference(JavaScriptTypes.CodeBody, S.getNumberOfStatements());
    S.addStatement(insts.Invoke(S.getNumberOfStatements(), 4, 5, new int[] {2}, 6, cs));

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 5, false));
    S.getNumberOfStatements();

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.String));
  }

  private IMethod makeStringCall(IClass cls, int nargs) {
    assert nargs == 0 || nargs == 1;

    Object key = Pair.make(cls, nargs);
    if (constructors.containsKey(key)) return constructors.get(key);
    else return record(key, (nargs == 0) ? makeNullaryStringCall(cls) : makeUnaryStringCall(cls));
  }

  private IMethod makeNullaryNumberCall(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = AstMethodReference.fnReference(JavaScriptTypes.Number);
    JavaScriptSummary S = new JavaScriptSummary(ref, 1);

    S.addConstant(2, new ConstantValue(0.0));
    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 2, false));
    S.getNumberOfStatements();

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.Number));
  }

  private IMethod makeUnaryNumberCall(IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    MethodReference ref = AstMethodReference.fnReference(JavaScriptTypes.Number);
    JavaScriptSummary S = new JavaScriptSummary(ref, 2);

    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), 4, 2, "toNumber"));
    S.getNumberOfStatements();

    CallSiteReference cs =
        new DynamicCallSiteReference(JavaScriptTypes.CodeBody, S.getNumberOfStatements());
    S.addStatement(insts.Invoke(S.getNumberOfStatements(), 4, 5, new int[] {2}, 6, cs));

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 5, false));
    S.getNumberOfStatements();

    return new JavaScriptConstructor(ref, S, cls, cha.lookupClass(JavaScriptTypes.Number));
  }

  private IMethod makeNumberCall(IClass cls, int nargs) {
    assert nargs == 0 || nargs == 1;

    Object key = Pair.make(cls, nargs);
    if (constructors.containsKey(key)) return constructors.get(key);
    else return record(key, (nargs == 0) ? makeNullaryNumberCall(cls) : makeUnaryNumberCall(cls));
  }

  private IMethod makeFunctionConstructor(IClass receiver, IClass cls) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    Pair<IClass, IClass> tableKey = Pair.make(receiver, cls);
    if (constructors.containsKey(tableKey)) return constructors.get(tableKey);

    MethodReference ref = JavaScriptMethods.makeCtorReference(receiver.getReference());
    JavaScriptSummary S = new JavaScriptSummary(ref, 1);

    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), 4, 1, "prototype"));
    S.getNumberOfStatements();

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            5,
            NewSiteReference.make(S.getNumberOfStatements(), cls.getReference())));

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            7,
            NewSiteReference.make(S.getNumberOfStatements(), JavaScriptTypes.Object)));

    S.addStatement(insts.SetPrototype(S.getNumberOfStatements(), 5, 4));
    // S.addStatement(insts.PutInstruction(5, 4, "__proto__"));
    S.getNumberOfStatements();

    S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), 5, 7, "prototype"));
    S.getNumberOfStatements();

    S.addStatement(insts.PutInstruction(S.getNumberOfStatements(), 7, 5, "constructor"));
    S.getNumberOfStatements();

    // TODO we need to set v7.__proto__ to Object.prototype
    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), 5, false));
    S.getNumberOfStatements();

    // S.addConstant(8, new ConstantValue("__proto__"));

    if (receiver != cls)
      return record(
          tableKey,
          new JavaScriptConstructor(
              ref, S, receiver, cls, "(" + cls.getReference().getName() + ')'));
    else return record(tableKey, new JavaScriptConstructor(ref, S, receiver, cls));
  }

  private int ctorCount = 0;

  private IMethod makeFunctionConstructor(
      IR callerIR, SSAAbstractInvokeInstruction callStmt, IClass cls, int nargs) {
    SymbolTable ST = callerIR.getSymbolTable();

    switch (nargs) {
      case 0:
        return makeFunctionConstructor(cls, cls);
      case 1:
        if (ST.isStringConstant(callStmt.getUse(1))
            && !ST.getStringValue(callStmt.getUse(1)).isEmpty()) {
          TypeReference ref =
              TypeReference.findOrCreate(
                  JavaScriptTypes.jsLoader,
                  TypeName.string2TypeName(ST.getStringValue(callStmt.getUse(1))));

          IClass cls2 = cha.lookupClass(ref);
          if (cls2 != null) {
            return makeFunctionConstructor(cls, cls2);
          }
        }

        return makeFunctionConstructor(cls, cls);
      default:
        assert nargs > 1;
        JavaScriptLoader cl = (JavaScriptLoader) cha.getLoader(JavaScriptTypes.jsLoader);

        for (int i = 1; i < callStmt.getNumberOfUses(); i++)
          if (!ST.isStringConstant(callStmt.getUse(i))) return makeFunctionConstructor(cls, cls);

        final StringBuilder fun = new StringBuilder("function _fromctor (");
        for (int j = 1; j < callStmt.getNumberOfUses() - 1; j++) {
          if (j != 1) fun.append(',');
          fun.append(ST.getStringValue(callStmt.getUse(j)));
        }

        fun.append(") {");
        fun.append(ST.getStringValue(callStmt.getUse(callStmt.getNumberOfUses() - 1)));
        fun.append('}');

        try {
          final String fileName = "ctor$" + ++ctorCount;
          ModuleEntry ME =
              new SourceModule() {

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

                @Override
                public boolean isClassFile() {
                  return false;
                }

                @Override
                public boolean isSourceFile() {
                  return true;
                }

                @Override
                public InputStream getInputStream() {
                  return new InputStream() {
                    private int i = 0;

                    @Override
                    public int read() throws IOException {
                      if (i >= fun.length()) {
                        return -1;
                      } else {
                        return fun.codePointAt(i++);
                      }
                    }
                  };
                }

                @Override
                public boolean isModuleFile() {
                  return false;
                }

                @Override
                public Module asModule() {
                  return null;
                }

                @Override
                public String getClassName() {
                  return fileName;
                }

                @Override
                public Module getContainer() {
                  return null;
                }

                @Override
                public Iterator<? extends ModuleEntry> getEntries() {
                  return new NonNullSingletonIterator<ModuleEntry>(this);
                }

                @Override
                public Reader getInputReader() {
                  return new StringReader(fun.toString());
                }

                @Override
                public URL getURL() {
                  try {
                    return new URL("file://" + fileName);
                  } catch (MalformedURLException e) {
                    assert false;
                    return null;
                  }
                }
              };

          Set<String> fnNames = JSCallGraphUtil.loadAdditionalFile(cha, cl, ME);
          IClass fcls = null;
          for (String nm : fnNames) {
            if (nm.endsWith("_fromctor")) {
              fcls = cl.lookupClass(nm, cha);
            }
          }

          if (fcls == null) {
            // This can happen, e.g., if the arguments to the Function constructor call are
            // malformed.
            // In this case, return a synthetic constructor similar to the 0-argument case
            return makeFunctionConstructor(cls, cls);
          } else {
            return makeFunctionConstructor(cls, fcls);
          }

        } catch (IOException e) {

        }

        return makeFunctionConstructor(cls, cls);
    }
  }

  private IMethod makeFunctionObjectConstructor(IClass cls, int nargs) {
    JSInstructionFactory insts =
        (JSInstructionFactory) cls.getClassLoader().getInstructionFactory();
    Object key = Pair.make(cls, nargs);
    if (constructors.containsKey(key)) return constructors.get(key);

    MethodReference ref = JavaScriptMethods.makeCtorReference(cls.getReference());
    JavaScriptSummary S = new JavaScriptSummary(ref, nargs + 1);
    S.addStatement(insts.GetInstruction(S.getNumberOfStatements(), nargs + 4, 1, "prototype"));
    S.getNumberOfStatements();

    S.addStatement(
        insts.NewInstruction(
            S.getNumberOfStatements(),
            nargs + 5,
            NewSiteReference.make(S.getNumberOfStatements(), JavaScriptTypes.Object)));

    S.addStatement(insts.SetPrototype(S.getNumberOfStatements(), nargs + 5, nargs + 4));
    S.getNumberOfStatements();

    CallSiteReference cs =
        new DynamicCallSiteReference(JavaScriptTypes.CodeBody, S.getNumberOfStatements());
    int[] args = new int[nargs + 1];
    args[0] = nargs + 5;
    for (int i = 0; i < nargs; i++) args[i + 1] = i + 2;
    S.addStatement(insts.Invoke(S.getNumberOfStatements(), 1, nargs + 7, args, nargs + 8, cs));
    int pc = S.getNumberOfStatements();

    S.addConstant(nargs + 9, null);
    S.addStatement(
        insts.ConditionalBranchInstruction(
            S.getNumberOfStatements(),
            Operator.EQ,
            JavaScriptTypes.Root,
            nargs + 7,
            nargs + 9,
            pc + 2));
    S.getNumberOfStatements();

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), nargs + 7, false));
    S.getNumberOfStatements();

    S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), nargs + 5, false));
    S.getNumberOfStatements();

    return record(key, new JavaScriptConstructor(ref, S, cls, cls));
  }

  public IMethod findOrCreateConstructorMethod(
      IR callerIR, SSAAbstractInvokeInstruction callStmt, IClass receiver, int nargs) {
    if (receiver.getReference().equals(JavaScriptTypes.Object))
      return makeObjectConstructor(receiver, nargs);
    else if (receiver.getReference().equals(JavaScriptTypes.Array))
      return makeArrayConstructor(receiver, nargs);
    else if (receiver.getReference().equals(JavaScriptTypes.StringObject))
      return makeValueConstructor(receiver, nargs, "");
    else if (receiver.getReference().equals(JavaScriptTypes.BooleanObject)) {
      assert nargs == 1;
      return makeValueConstructor(receiver, nargs, null);
    } else if (receiver.getReference().equals(JavaScriptTypes.NumberObject))
      return makeValueConstructor(receiver, nargs, 0);
    else if (receiver.getReference().equals(JavaScriptTypes.Function))
      return makeFunctionConstructor(callerIR, callStmt, receiver, nargs);
    else if (cha.isSubclassOf(receiver, cha.lookupClass(JavaScriptTypes.CodeBody)))
      return makeFunctionObjectConstructor(receiver, nargs);
    else {
      return null;
    }
  }

  public IMethod findOrCreateCallMethod(
      IR callerIR, SSAAbstractInvokeInstruction callStmt, IClass receiver, int nargs) {
    if (receiver.getReference().equals(JavaScriptTypes.Object))
      return makeObjectCall(receiver, nargs);
    else if (receiver.getReference().equals(JavaScriptTypes.Array))
      return makeArrayConstructor(receiver, nargs);
    else if (receiver.getReference().equals(JavaScriptTypes.StringObject))
      return makeStringCall(receiver, nargs);
    else if (receiver.getReference().equals(JavaScriptTypes.NumberObject))
      return makeNumberCall(receiver, nargs);
    else if (receiver.getReference().equals(JavaScriptTypes.Function))
      return makeFunctionConstructor(callerIR, callStmt, receiver, nargs);
    else {
      return null;
    }
  }
}
