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

/*
*  Copyright (c) 2013,
*      Tobias Blaschke <code@tobiasblaschke.de>
*  All rights reserved.

*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions are met:
*
*  1. Redistributions of source code must retain the above copyright notice,
*     this list of conditions and the following disclaimer.
*
*  2. Redistributions in binary form must reproduce the above copyright notice,
*     this list of conditions and the following disclaimer in the documentation
*     and/or other materials provided with the distribution.
*
*  3. The names of the contributors may not be used to endorse or promote
*     products derived from this software without specific prior written
*     permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
*  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
*  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
*  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*/
package com.ibm.wala.core.util.ssa;

import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory;
import com.ibm.wala.classLoader.NewSiteReference;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.shrike.shrikeBT.IConditionalBranchInstruction;
import com.ibm.wala.shrike.shrikeBT.IInvokeInstruction;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.ssa.SSAArrayLoadInstruction;
import com.ibm.wala.ssa.SSAArrayStoreInstruction;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAGotoInstruction;
import com.ibm.wala.ssa.SSANewInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.ssa.SSAReturnInstruction;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeReference;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * Intended for SyntheticMethods, uses JavaInstructionFactory.
 *
 * <p>As the name states this SSAInstructionFactory does type checks. If they pass the actual
 * instructions are generated using the JavaInstructionFactory.
 *
 * <p>Obviously this factory is not complete yet. It's extended on demand.
 *
 * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory
 * @author Tobias Blaschke &lt;code@tobiasblaschke.de&gt;
 * @since 2013-10-20
 */
public class TypeSafeInstructionFactory {

  private static final boolean DEBUG = false;
  protected final JavaInstructionFactory insts;
  protected final IClassHierarchy cha;

  public TypeSafeInstructionFactory(IClassHierarchy cha) {
    this.cha = cha;
    this.insts = new JavaInstructionFactory();
  }

  /**
   * result = site(params).
   *
   * <p>Instruction that calls a method which has a return-value.
   *
   * <p>All parameters (but exception) are typechecked first. If the check passes they get unpacked
   * and handed over to the corresponding JavaInstructionFactory.InvokeInstruction.
   *
   * <p>Calls result.setAssigned()
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#InvokeInstruction(int, int,
   *     int[], int, CallSiteReference,
   *     com.ibm.wala.shrike.shrikeCT.BootstrapMethodsReader.BootstrapMethod)
   * @param iindex Zero or a positive number unique to any instruction of the same method
   * @param result Where to place the return-value of the called method. Is SSAValue.setAssigned()
   *     automatically.
   * @param params Parameters to the call starting with the implicit this-pointer if necessary
   * @param exception An SSAValue receiving the exception-object when something in the method throws
   *     unhandled
   * @param site The CallSiteReference to this call.
   */
  public SSAAbstractInvokeInstruction InvokeInstruction(
      final int iindex,
      final SSAValue result,
      List<? extends SSAValue> params,
      final SSAValue exception,
      final CallSiteReference site) {
    info("Now: InvokeInstruction to {} using {}", site, params);
    if (iindex < 0) {
      throw new IllegalArgumentException("The iIndex may not be negative");
    }
    if (result == null) {
      throw new IllegalArgumentException("The result may not be null");
    }
    if (exception == null) {
      throw new IllegalArgumentException("The parameter exception may not be null");
    }
    if (params == null) {
      params = Collections.emptyList();
    }
    if (site == null) {
      throw new IllegalArgumentException("The CallSite may not be null");
    }
    final ParameterAccessor acc = new ParameterAccessor(site.getDeclaredTarget(), this.cha);
    if (acc.hasImplicitThis()) {
      if (params.size() != (acc.getNumberOfParameters() + 1)) {
        throw new IllegalArgumentException(
            "The callee takes "
                + acc.getNumberOfParameters()
                + " + 1 (implicit this) "
                + "parameters. The given parameter-list has length "
                + params.size()
                + ". They are: "
                + params);
      }

      if (site.getInvocationCode()
          == IInvokeInstruction.Dispatch.STATIC) { // TODO use Dispatch.hasImplicitThis
        throw new IllegalArgumentException(
            "A function expecting an implicit this can not be invoked static.");
      }
    } else {
      if (params.size() != acc.getNumberOfParameters()) {
        throw new IllegalArgumentException(
            "The callee takes "
                + acc.getNumberOfParameters()
                + " parameters (no implicit this)."
                + "The given parameter-list has length "
                + params.size()
                + ". They are: "
                + params);
      }

      if (site.getInvocationCode() != IInvokeInstruction.Dispatch.STATIC) {
        throw new IllegalArgumentException(
            "A function without implicit this can only be invoked static.");
      }
    }

    // final MethodReference mRef = site.getDeclaredTarget();

    // ***********
    // Params cosher, start typechecks

    { // Return-Value
      final TypeReference retType = acc.getReturnType();
      if (!isAssignableFrom(retType, result.getType())) {
        throw new IllegalArgumentException(
            "The return-value does not stand the TypeCheck! "
                + retType
                + " is not assignable to "
                + result);
      }
    }

    final int[] aParams = new int[params.size()];
    if (acc.hasImplicitThis()) { // Implicit This
      final SSAValue targetThis = acc.getThis();
      final SSAValue givenThis = params.get(0);

      aParams[0] = givenThis.getNumber();

      if (!isAssignableFrom(givenThis.getType(), targetThis.getType())) {
        throw new IllegalArgumentException(
            "Parameter 'this' is not assignable from\n\t"
                + givenThis
                + " to\n\t"
                + targetThis
                + "\n----------");
      }

      for (int i = 1; i < params.size(); ++i) { // "regular" parameters
        final SSAValue givenParam = params.get(i);
        final SSAValue targetParam = acc.getParameter(i);

        aParams[i] = givenParam.getNumber();
        if (!isAssignableFrom(givenParam.getType(), targetParam.getType())) {
          throw new IllegalArgumentException(
              "Parameter " + i + " is not assignable from " + givenParam + " to " + targetParam);
        }
        if (result.equals(givenParam)) {
          throw new IllegalArgumentException(
              result + " can't be the result and parameter " + i + " at the same time in " + site);
        }
      }
    } else {
      for (int i = 0; i < params.size(); ++i) { // "regular" parameters
        final SSAValue givenParam = params.get(i);
        final SSAValue targetParam = acc.getParameter(i + 1);

        aParams[i] = givenParam.getNumber();
        if (!isAssignableFrom(givenParam.getType(), targetParam.getType())) {
          throw new IllegalArgumentException(
              "Parameter "
                  + (i + 1)
                  + " is not assignable from "
                  + givenParam
                  + " to "
                  + targetParam
                  + " in call "
                  + site);
        }
        if (result.equals(givenParam)) {
          throw new IllegalArgumentException(
              result + " can't be the result and parameter " + i + " at the same time in " + site);
        }
      }
    }

    // TODO somehow check exception?

    result.setAssigned();
    return insts.InvokeInstruction(
        iindex, result.getNumber(), aParams, exception.getNumber(), site, null);
  }

  /**
   * Instruction that calls a void-method.
   *
   * <p>All parameters (but exception) are typechecked first. If the check passes they get unpacked
   * and handed over to the corresponding JavaInstructionFactory.InvokeInstruction.
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#InvokeInstruction(int, int[],
   *     int, CallSiteReference,
   *     com.ibm.wala.shrike.shrikeCT.BootstrapMethodsReader.BootstrapMethod)
   * @param iindex Zero or a positive number unique to any instruction of the same method
   * @param params Parameters to the call starting with the implicit this-pointer if necessary
   * @param exception An SSAValue receiving the exception-object when something in the method throws
   *     unhandled
   * @param site The CallSiteReference to this call.
   * @return the invocation instruction
   */
  public SSAAbstractInvokeInstruction InvokeInstruction(
      final int iindex,
      List<? extends SSAValue> params,
      final SSAValue exception,
      final CallSiteReference site) {
    info("Now: InvokeInstruction to {} using {}", site, params);
    if (iindex < 0) {
      throw new IllegalArgumentException("The iIndex may not be negative");
    }
    if (exception == null) {
      throw new IllegalArgumentException("The parameter exception may not be null");
    }
    if (params == null) {
      params = Collections.emptyList();
    }
    if (site == null) {
      throw new IllegalArgumentException("The CallSite may not be null");
    }
    final ParameterAccessor acc = new ParameterAccessor(site.getDeclaredTarget(), this.cha);
    if (acc.hasImplicitThis()) {
      if (params.size() != (acc.getNumberOfParameters() + 1)) {
        throw new IllegalArgumentException(
            "The callee takes "
                + acc.getNumberOfParameters()
                + " + 1 (implicit this) "
                + "parameters. The given parameter-list has length "
                + params.size()
                + ". They are: "
                + params);
      }

      if (site.getInvocationCode()
          == IInvokeInstruction.Dispatch.STATIC) { // TODO use Dispatch.hasImplicitThis
        throw new IllegalArgumentException(
            "A function expecting an implicit this can not be invoked static.");
      }
    } else {
      if (params.size() != acc.getNumberOfParameters()) {
        throw new IllegalArgumentException(
            "The callee takes "
                + acc.getNumberOfParameters()
                + " parameters (no implicit this)."
                + "The given parameter-list has length "
                + params.size()
                + ". They are: "
                + params);
      }

      if (site.getInvocationCode() != IInvokeInstruction.Dispatch.STATIC) {
        throw new IllegalArgumentException(
            "A function without implicit this can only be invoked static.");
      }
    }

    final MethodReference mRef = site.getDeclaredTarget();

    // ***********
    // Params cosher, start typechecks

    { // Return-Value
      if (acc.hasReturn()) {
        throw new IllegalArgumentException(
            "This InvokeInstruction only works on void-functions but "
                + mRef
                + " returns a value.");
      }
    }

    final int[] aParams = new int[params.size()];
    if (acc.hasImplicitThis()) { // Implicit This
      // final SSAValue targetThis = acc.getThis();
      final SSAValue givenThis = params.get(0);

      aParams[0] = givenThis.getNumber();

      /*if (! isAssignableFrom(givenThis.getType(), targetThis.getType())) {
          throw new IllegalArgumentException("Parameter 'this' is not assignable from " + givenThis + " to " + targetThis);
      }*/

      for (int i = 1; i < params.size(); ++i) { // "regular" parameters
        final SSAValue givenParam = params.get(i);
        final SSAValue targetParam = acc.getParameter(i);

        aParams[i] = givenParam.getNumber();
        if (!isAssignableFrom(givenParam.getType(), targetParam.getType())) {
          throw new IllegalArgumentException(
              "Parameter "
                  + i
                  + " is not assignable from\n\t"
                  + givenParam
                  + " to\n\t"
                  + targetParam
                  + "\nin call "
                  + site
                  + "\n---------");
        }
      }
    } else {
      for (int i = 0; i < params.size(); ++i) { // "regular" parameters
        final SSAValue givenParam = params.get(i);
        final SSAValue targetParam = acc.getParameter(i + 1);

        aParams[i] = givenParam.getNumber();
        if (!isAssignableFrom(givenParam.getType(), targetParam.getType())) {
          throw new IllegalArgumentException(
              "Parameter "
                  + (i + 1)
                  + " is not assignable from "
                  + givenParam
                  + " to "
                  + targetParam);
        }
      }
    }

    // TODO somehow check exception?

    return insts.InvokeInstruction(iindex, aParams, exception.getNumber(), site, null);
  }

  /**
   * Check if the type of the SSAValue is assignable to the return-value of the method it's valid
   * in.
   *
   * <p>If type check passes the corresponding ReturnInstruction of the JavaInstructionFactory is
   * called.
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#ReturnInstruction(int, int,
   *     boolean)
   * @param iindex Zero or a positive number unique to any instruction of the same method
   * @param result SSAValue to return _with_ validIn _set_!
   * @throws IllegalArgumentException if result has no validIn set
   */
  public SSAReturnInstruction ReturnInstruction(final int iindex, final SSAValue result) {
    info("Now: ReturnInstruction using {}", result);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (!isAssignableFrom(result.getType(), result.getValidIn().getReturnType())) {
      throw new IllegalArgumentException(
          "Return type not assignable from "
              + result.getType()
              + " to "
              + result.getValidIn().getReturnType());
    }

    return insts.ReturnInstruction(iindex, result.getNumber(), result.getType().isPrimitiveType());
  }

  /**
   * targetValue = containingInstance.field.
   *
   * <p>Reads field from containingInstance into targetValue. If type check passes the corresponding
   * GetInstruction of the JavaInstructionFactory is called. Calls targetValue.setAssigned()
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#GetInstruction(int, int, int,
   *     FieldReference)
   * @param iindex Zero or a positive number unique to any instruction of the same method
   * @param targetValue the result of the GetInstruction is placed there
   * @param containingInstance The Object instance to read the field from
   * @param field The description of the field
   */
  public SSAGetInstruction GetInstruction(
      final int iindex,
      final SSAValue targetValue,
      final SSAValue containingInstance,
      FieldReference field) {
    info("Now: Get {} from {} into {}", field, containingInstance, targetValue);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (targetValue == null) {
      throw new IllegalArgumentException("targetValue may not be null");
    }
    if (containingInstance == null) {
      throw new IllegalArgumentException("containingInstance may not be null");
    }
    if (field == null) {
      throw new IllegalArgumentException("field may not be null");
    }
    if (!isSuperclassOf(field.getDeclaringClass(), containingInstance.getType())) {
      throw new IllegalArgumentException(
          "The targetInstance "
              + containingInstance
              + " is not equal or a "
              + " super-class of "
              + field.getDeclaringClass());
    }
    if (!isAssignableFrom(field.getFieldType(), targetValue.getType())) {
      throw new IllegalArgumentException(
          "The field " + targetValue + " is not assignable from " + field);
    }

    final MethodReference targetValueValidIn = targetValue.getValidIn();
    final MethodReference instValidIn = containingInstance.getValidIn();

    if ((targetValueValidIn != null)
        && (instValidIn != null)
        && !targetValueValidIn.equals(instValidIn)) {
      throw new IllegalArgumentException(
          "containingInstance "
              + containingInstance
              + "and targetValue "
              + targetValue
              + " are valid in different scopes");
    }

    targetValue.setAssigned();
    return insts.GetInstruction(
        iindex, targetValue.getNumber(), containingInstance.getNumber(), field);
  }

  /**
   * Reads static field into targetValue.
   *
   * <p>If type check passes the corresponding GetInstruction of the JavaInstructionFactory is
   * called. Calls targetValue.setAssigned()
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#GetInstruction(int, int, int,
   *     FieldReference)
   * @param iindex Zero or a positive number unique to any instruction of the same method
   * @param targetValue the result of the GetInstruction is placed there
   * @param field The description of the field
   */
  public SSAGetInstruction GetInstruction(
      final int iindex, final SSAValue targetValue, FieldReference field) {
    info("Now: Get {} into {}", field, targetValue);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (targetValue == null) {
      throw new IllegalArgumentException("targetValue may not be null");
    }
    if (field == null) {
      throw new IllegalArgumentException("field may not be null");
    }
    if (!isAssignableFrom(field.getFieldType(), targetValue.getType())) {
      throw new IllegalArgumentException(
          "The field " + targetValue + " is not assignable from " + field);
    }

    targetValue.setAssigned();
    return insts.GetInstruction(iindex, targetValue.getNumber(), field);
  }

  /**
   * Writes newValue to field of targetInstance.
   *
   * <p>If type check passes the corresponding PutInstruction of the JavaInstructionFactory is
   * called.
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#PutInstruction(int, int, int,
   *     FieldReference)
   * @param iindex Zero or a psitive number unique to any instruction of the same method
   * @param targetInstance the instance of the object to write a field of
   * @param newValue The value to write to the field
   * @param field The description of the target
   */
  public SSAPutInstruction PutInstruction(
      final int iindex,
      final SSAValue targetInstance,
      final SSAValue newValue,
      FieldReference field) {
    info("Now: Put {} to {}", newValue, field);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (targetInstance == null) {
      throw new IllegalArgumentException("targetInstance may not be null");
    }
    if (newValue == null) {
      throw new IllegalArgumentException("newValue may not be null");
    }
    if (field == null) {
      throw new IllegalArgumentException("field may not be null");
    }
    if (!isSuperclassOf(field.getDeclaringClass(), targetInstance.getType())) {
      throw new IllegalArgumentException(
          "The targetInstance "
              + targetInstance
              + " is not equal or a "
              + " super-class of "
              + field.getDeclaringClass());
    }
    if (!isAssignableFrom(newValue.getType(), field.getFieldType())) {
      throw new IllegalArgumentException(
          "The field " + field + " is not assignable from " + newValue);
    }

    final MethodReference newValueValidIn = newValue.getValidIn();
    final MethodReference instValidIn = targetInstance.getValidIn();

    if ((newValueValidIn != null)
        && (instValidIn != null)
        && !newValueValidIn.equals(instValidIn)) {
      throw new IllegalArgumentException(
          "targetInstance "
              + targetInstance
              + "and newValue "
              + newValue
              + " are valid in different scopes");
    }

    return insts.PutInstruction(iindex, targetInstance.getNumber(), newValue.getNumber(), field);
  }

  /**
   * Writes newValue to static field.
   *
   * <p>If type check passes the corresponding PutInstruction of the JavaInstructionFactory is
   * called.
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#PutInstruction(int, int, int,
   *     FieldReference)
   * @param iindex Zero or a psitive number unique to any instruction of the same method
   * @param newValue The value to write to the field
   * @param field The description of the target
   */
  public SSAPutInstruction PutInstruction(
      final int iindex, final SSAValue newValue, FieldReference field) {
    info("Now: Put {} to {}", newValue, field);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (newValue == null) {
      throw new IllegalArgumentException("newValue may not be null");
    }
    if (field == null) {
      throw new IllegalArgumentException("field may not be null");
    }
    if (!isAssignableFrom(newValue.getType(), field.getFieldType())) {
      throw new IllegalArgumentException(
          "The field " + field + " is not assignable from " + newValue);
    }

    // final MethodReference newValueValidIn = newValue.getValidIn();

    return insts.PutInstruction(iindex, newValue.getNumber(), field);
  }

  public SSANewInstruction NewInstruction(int iindex, SSAValue result, NewSiteReference site) {
    info("Now: New {}", result);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (result == null) {
      throw new IllegalArgumentException("result may not be null");
    }
    if (site == null) {
      throw new IllegalArgumentException("site may not be null");
    }
    if (!isAssignableFrom(site.getDeclaredType(), result.getType())) {
      throw new IllegalArgumentException("type mismatch");
    }
    result.setAssigned();
    return insts.NewInstruction(iindex, result.getNumber(), site);
  }

  public SSANewInstruction NewInstruction(
      int iindex, SSAValue result, NewSiteReference site, Collection<? extends SSAValue> params) {
    info("Now: New {}", result);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (result == null) {
      throw new IllegalArgumentException("result may not be null");
    }
    if (site == null) {
      throw new IllegalArgumentException("site may not be null");
    }
    if (!isAssignableFrom(site.getDeclaredType(), result.getType())) {
      throw new IllegalArgumentException("type mismatch");
    }

    final MethodReference resultValidIn = result.getValidIn();
    final int[] aParams = new int[params.size()];
    int i = 0;
    for (final SSAValue param : params) {
      final MethodReference paramValidIn = param.getValidIn();

      if ((resultValidIn != null)
          && (paramValidIn != null)
          && !paramValidIn.equals(resultValidIn)) {
        throw new IllegalArgumentException(
            "The parameter " + param + " is valid in another scope than" + result);
      }

      aParams[i] = param.getNumber();
      i++;
    }

    result.setAssigned();
    return insts.NewInstruction(iindex, result.getNumber(), site, aParams);
  }

  /**
   * Combine SSA-Values into a newone.
   *
   * <p>If type check passes the corresponding PhiInstruction of the JavaInstructionFactory is
   * called. Calls result.setAssigned().
   *
   * @see com.ibm.wala.classLoader.JavaLanguage.JavaInstructionFactory#PhiInstruction(int, int,
   *     int[])
   * @param iindex Zero or a positive number unique to any instruction of the same method
   * @param result Where to write result to
   * @param params at least one SSAValue to read from
   */
  public SSAPhiInstruction PhiInstruction(
      int iindex, SSAValue result, Collection<? extends SSAValue> params) {
    info("Now: Phi into {} from {}", result, params);

    if (iindex < 0) {
      throw new IllegalArgumentException("iIndex may not be negative");
    }
    if (result == null) {
      throw new IllegalArgumentException("result may not be null");
    }
    if (params == null) {
      throw new IllegalArgumentException("params may not be null");
    }
    if (params.size() < 1) {
      throw new IllegalArgumentException(
          "Phi needs at least one source value. Type is " + result.getType());
    }
    final MethodReference resultValidIn = result.getValidIn();
    final TypeReference resultType = result.getType();

    final int[] aParams = new int[params.size()];
    int i = 0;
    for (final SSAValue param : params) {
      final MethodReference paramValidIn = param.getValidIn();

      if ((resultValidIn != null)
          && (paramValidIn != null)
          && !paramValidIn.equals(resultValidIn)) {
        throw new IllegalArgumentException(
            "The parameter " + param + " is valid in another scope than" + result);
      }

      if (!isAssignableFrom(param.getType(), resultType)) {
        throw new IllegalArgumentException("Param " + param + " is not assignable to " + result);
      }

      if (result.equals(param)) {
        throw new IllegalArgumentException("Cannot phi to myself: " + result);
      }

      aParams[i] = param.getNumber();
      i++;
      // TODO: Assert no parameter appears twice
    }

    result.setAssigned();
    return insts.PhiInstruction(iindex, result.getNumber(), aParams);
  }

  @SuppressWarnings("unused")
  private static boolean isSuperclassOf(
      final TypeReference superClass, final TypeReference subClass) {
    return true; // TODO
  }

  public boolean isAssignableFrom(final TypeReference from, final TypeReference to) {
    try {
      return ParameterAccessor.isAssignable(from, to, this.cha);
    } catch (ClassLookupException e) {
      return true;
    }
  }

  // ************
  //  Instructions simply handed through follow

  /**
   * Return from a void-function.
   *
   * <p>Handed through to JavaInstructionFactory directly (unless the iindex is negative).
   *
   * @param iindex Zero or a positive number unique to any instruction of the same method
   */
  public SSAReturnInstruction ReturnInstruction(final int iindex) {
    if (iindex < 0) {
      throw new IllegalArgumentException("The iindex may not be negative");
    }
    return insts.ReturnInstruction(iindex);
  }

  /**
   * Unconditionally jump to a (non-Phi) Instruction.
   *
   * @param target the iindex of the instruction to jump to
   */
  public SSAGotoInstruction GotoInstruction(final int iindex, final int target) {
    if (iindex < 0) {
      throw new IllegalArgumentException("The iindex may not be negative");
    }
    if (target < 0) {
      throw new IllegalArgumentException("The target-iindex may not be negative");
    }

    return insts.GotoInstruction(iindex, target);
  }

  public SSAConditionalBranchInstruction ConditionalBranchInstruction(
      final int iindex,
      final IConditionalBranchInstruction.IOperator operator,
      final TypeReference type,
      final int val1,
      final int val2,
      final int target) {
    return insts.ConditionalBranchInstruction(iindex, operator, type, val1, val2, target);
  }
  /**
   * result = array[index].
   *
   * <p>Load a a reference from an array.
   *
   * @param result The SSAValue to store the loaded stuff in
   * @param array The array to load from
   * @param index Te position in array to load from
   */
  public SSAArrayLoadInstruction ArrayLoadInstruction(
      final int iindex, final SSAValue result, final SSAValue array, final int index) {
    if (iindex < 0) {
      throw new IllegalArgumentException("The iindex may not be negative. It's " + iindex);
    }
    if (result == null) {
      throw new IllegalArgumentException("Can't use null for the result");
    }
    if (array == null) {
      throw new IllegalArgumentException("Can't load from array null");
    }
    if (index < 0) {
      throw new IllegalArgumentException(
          "The index in the array may not be negative. It's " + index);
    }
    if (!array.getType().isArrayType()) {
      throw new IllegalArgumentException(
          "The array to read from is expected to be ... well ... an array. The given value was "
              + array);
    }
    final TypeReference innerType = array.getType().getArrayElementType();
    if (!isAssignableFrom(innerType, result.getType())) {
      throw new IllegalArgumentException(
          "Can't assign from an array of "
              + innerType.getName()
              + " to "
              + result.getType().getName());
    }

    result.setAssigned();
    return insts.ArrayLoadInstruction(
        iindex, result.getNumber(), array.getNumber(), index, innerType);
  }

  /**
   * array[index] = value.
   *
   * <p>Save a value to a specific position in an Array.
   *
   * @param array the array to store to
   * @param index the position in the array to place value at
   * @param value The SSAValue to store in the array
   */
  public SSAArrayStoreInstruction ArrayStoreInstruction(
      final int iindex, final SSAValue array, final int index, final SSAValue value) {
    if (iindex < 0) {
      throw new IllegalArgumentException("The iindex may not be negative. It's " + iindex);
    }
    if (value == null) {
      throw new IllegalArgumentException("Can't use null for the value to put");
    }
    if (array == null) {
      throw new IllegalArgumentException("Can't write to array null");
    }
    if (index < 0) {
      throw new IllegalArgumentException(
          "The index in the array may not be negative. It's " + index);
    }
    if (!array.getType().isArrayType()) {
      throw new IllegalArgumentException(
          "The array to write to is expected to be ... well ... an array. The given value was "
              + array);
    }

    final TypeReference innerType = array.getType().getArrayElementType();
    if (!isAssignableFrom(value.getType(), innerType)) {
      throw new IllegalArgumentException(
          "Can't assign to an array of "
              + innerType.getName()
              + " from "
              + value.getType().getName());
    }

    return insts.ArrayStoreInstruction(
        iindex, array.getNumber(), index, value.getNumber(), innerType);
  }

  private static void info(String s, Object... args) {
    if (DEBUG) {
      System.err.printf(s, args);
    }
  }
}
