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

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.ibm.wala.shrikeCT.InvalidClassFileException;
import com.ibm.wala.ssa.SSAArrayLoadInstruction;
import com.ibm.wala.ssa.SSAArrayStoreInstruction;
import com.ibm.wala.ssa.SSACheckCastInstruction;
import com.ibm.wala.ssa.SSAGetCaughtExceptionInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInstruction.Visitor;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSANewInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.HashSetFactory;

/**
 * Simple utilities to scan {@link IMethod}s to gather information without building an IR.
 */
public class CodeScanner {

  /**
   * @throws InvalidClassFileException
   * @throws IllegalArgumentException if m is null
   */
  public static Collection<CallSiteReference> getCallSites(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return getCallSites(sm.getStatements());
    } else {
      return getCallSitesFromShrikeBT((IBytecodeMethod) m);
    }
  }

  /**
   * @throws InvalidClassFileException
   * @throws IllegalArgumentException if m is null
   */
  public static Collection<FieldReference> getFieldsRead(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return getFieldsRead(sm.getStatements());
    } else {
      return getFieldsReadFromShrikeBT((ShrikeCTMethod) m);
    }
  }

  /**
   * @throws InvalidClassFileException
   * @throws IllegalArgumentException if m is null
   */
  public static Collection<FieldReference> getFieldsWritten(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return getFieldsWritten(sm.getStatements());
    } else {
      return getFieldsWrittenFromShrikeBT((ShrikeCTMethod) m);
    }
  }

  /**
   * get the element types of the arrays that m may update
   * 
   * @throws InvalidClassFileException
   */
  public static Collection<TypeReference> getArraysWritten(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return getArraysWritten(sm.getStatements());
    } else {
      return getArraysWrittenFromShrikeBT((ShrikeCTMethod) m);
    }
  }

  /**
   * @throws InvalidClassFileException
   * @throws IllegalArgumentException if m is null
   */
  public static Collection<NewSiteReference> getNewSites(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return getNewSites(sm.getStatements());
    } else {
      return getNewSitesFromShrikeBT((ShrikeCTMethod) m);
    }
  }

  public static boolean hasObjectArrayLoad(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return hasObjectArrayLoad(sm.getStatements());
    } else {
      return hasShrikeBTObjectArrayLoad((ShrikeCTMethod) m);
    }
  }

  public static boolean hasObjectArrayStore(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return hasObjectArrayStore(sm.getStatements());
    } else {
      return hasShrikeBTObjectArrayStore((ShrikeCTMethod) m);
    }
  }

  public static Set getCaughtExceptions(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return getCaughtExceptions(m.getDeclaringClass().getClassLoader().getLanguage(), sm.getStatements());
    } else {
      return getShrikeBTCaughtExceptions((ShrikeCTMethod) m);
    }
  }

  /**
   * Return the types this method may cast to
   * 
   * @return iterator of TypeReference
   * @throws InvalidClassFileException
   * @throws IllegalArgumentException if m is null
   */
  public static Iterator iterateCastTypes(IMethod m) throws InvalidClassFileException {
    if (m == null) {
      throw new IllegalArgumentException("m is null");
    }
    if (m.isSynthetic()) {
      SyntheticMethod sm = (SyntheticMethod) m;
      return iterateCastTypes(sm.getStatements());
    } else {
      return iterateShrikeBTCastTypes((ShrikeCTMethod) m);
    }
  }

  private static Iterator iterateShrikeBTCastTypes(ShrikeCTMethod wrapper) throws InvalidClassFileException {
    return wrapper.getCastTypes();
  }

  private static Set getShrikeBTCaughtExceptions(ShrikeCTMethod method) throws InvalidClassFileException {
    return method.getCaughtExceptionTypes();
  }

  private static boolean hasShrikeBTObjectArrayStore(ShrikeCTMethod M) throws InvalidClassFileException {
    for (Iterator it = M.getArraysWritten(); it.hasNext();) {
      TypeReference t = (TypeReference) it.next();
      if (t.isReferenceType()) {
        return true;
      }
    }
    return false;
  }

  private static Collection<CallSiteReference> getCallSitesFromShrikeBT(IBytecodeMethod M) throws InvalidClassFileException {
    return M.getCallSites();
  }

  /**
   * @param M
   * @return Iterator of TypeReference
   * @throws InvalidClassFileException
   */
  private static Collection<NewSiteReference> getNewSitesFromShrikeBT(ShrikeCTMethod M) throws InvalidClassFileException {
    return M.getNewSites();
  }

  private static List<FieldReference> getFieldsReadFromShrikeBT(ShrikeCTMethod M) throws InvalidClassFileException {
    // TODO move the logic here from ShrikeCTMethodWrapper
    LinkedList<FieldReference> result = new LinkedList<FieldReference>();
    for (Iterator<FieldReference> it = M.getFieldsRead(); it.hasNext();) {
      result.add(it.next());
    }
    return result;
  }

  private static List<FieldReference> getFieldsWrittenFromShrikeBT(ShrikeCTMethod M) throws InvalidClassFileException {
    // TODO move the logic here from ShrikeCTMethodWrapper
    LinkedList<FieldReference> result = new LinkedList<FieldReference>();
    for (Iterator<FieldReference> it = M.getFieldsWritten(); it.hasNext();) {
      result.add(it.next());
    }
    return result;
  }

  private static List<TypeReference> getArraysWrittenFromShrikeBT(ShrikeCTMethod M) throws InvalidClassFileException {
    // TODO move the logic here from ShrikeCTMethodWrapper
    List<TypeReference> result = new LinkedList<TypeReference>();
    for (Iterator<TypeReference> it = M.getArraysWritten(); it.hasNext();) {
      result.add(it.next());
    }
    return result;
  }

  private static boolean hasShrikeBTObjectArrayLoad(ShrikeCTMethod M) throws InvalidClassFileException {
    for (Iterator it = M.getArraysRead(); it.hasNext();) {
      TypeReference t = (TypeReference) it.next();
      if (t.isReferenceType()) {
        return true;
      }
    }
    return false;
  }

  /**
   * @return Set <TypeReference>
   * @throws IllegalArgumentException if statements == null
   */
  public static Set<TypeReference> getCaughtExceptions(final Language l, SSAInstruction[] statements) throws IllegalArgumentException {
    if (statements == null) {
      throw new IllegalArgumentException("statements == null");
    }
    final HashSet<TypeReference> result = HashSetFactory.make(10);
    Visitor v = new Visitor() {
      @Override
      public void visitGetCaughtException(SSAGetCaughtExceptionInstruction instruction) {
        Collection<TypeReference> t = instruction.getExceptionTypes();
        result.addAll(t);
      }
    };
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
    }
    return result;
  }

  /**
   * @throws IllegalArgumentException if statements == null
   */
  public static Iterator<TypeReference> iterateCastTypes(SSAInstruction[] statements) throws IllegalArgumentException {
    if (statements == null) {
      throw new IllegalArgumentException("statements == null");
    }
    final HashSet<TypeReference> result = HashSetFactory.make(10);
    for (int i = 0; i < statements.length; i++) {
      if (statements[i] != null) {
        if (statements[i] instanceof SSACheckCastInstruction) {
          SSACheckCastInstruction c = (SSACheckCastInstruction) statements[i];
          for(TypeReference t : c.getDeclaredResultTypes()) {
            result.add(t);
          }
        }
      }
    }
    return result.iterator();
  }

  /**
   * @param statements list of ssa statements
   * @return List of InvokeInstruction
   */
  private static List<CallSiteReference> getCallSites(SSAInstruction[] statements) {
    final List<CallSiteReference> result = new LinkedList<CallSiteReference>();
    Visitor v = new Visitor() {
      @Override
      public void visitInvoke(SSAInvokeInstruction instruction) {
        result.add(instruction.getCallSite());
      }
    };
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
    }
    return result;
  }

  /**
   * @param statements list of ssa statements
   * @return List of InvokeInstruction
   */
  private static List<NewSiteReference> getNewSites(SSAInstruction[] statements) {
    final List<NewSiteReference> result = new LinkedList<NewSiteReference>();
    Visitor v = new Visitor() {
      @Override
      public void visitNew(SSANewInstruction instruction) {
        result.add(instruction.getNewSite());
      }
    };
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
    }
    return result;
  }

  /**
   * @param statements list of ssa statements
   * @return List of FieldReference
   * @throws IllegalArgumentException if statements == null
   */
  public static List<FieldReference> getFieldsRead(SSAInstruction[] statements) throws IllegalArgumentException {
    if (statements == null) {
      throw new IllegalArgumentException("statements == null");
    }
    final List<FieldReference> result = new LinkedList<FieldReference>();
    Visitor v = new Visitor() {
      @Override
      public void visitGet(SSAGetInstruction instruction) {
        result.add(instruction.getDeclaredField());
      }
    };
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
    }
    return result;
  }

  /**
   * @param statements list of ssa statements
   * @return List of FieldReference
   * @throws IllegalArgumentException if statements == null
   */
  public static List<FieldReference> getFieldsWritten(SSAInstruction[] statements) throws IllegalArgumentException {
    if (statements == null) {
      throw new IllegalArgumentException("statements == null");
    }
    final List<FieldReference> result = new LinkedList<FieldReference>();
    Visitor v = new Visitor() {
      @Override
      public void visitPut(SSAPutInstruction instruction) {
        result.add(instruction.getDeclaredField());
      }
    };
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
    }
    return result;
  }

  /**
   * @param statements list of ssa statements
   * @return List of TypeReference
   * @throws IllegalArgumentException if statements == null
   */
  public static List<TypeReference> getArraysWritten(SSAInstruction[] statements) throws IllegalArgumentException {
    if (statements == null) {
      throw new IllegalArgumentException("statements == null");
    }
    final List<TypeReference> result = new LinkedList<TypeReference>();
    Visitor v = new Visitor() {

      @Override
      public void visitArrayStore(SSAArrayStoreInstruction instruction) {
        result.add(instruction.getElementType());
      }
      
    };
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
    }
    return result;
  }

  public static boolean hasObjectArrayLoad(SSAInstruction[] statements) throws IllegalArgumentException {

    if (statements == null) {
      throw new IllegalArgumentException("statements == null");
    }
    class ScanVisitor extends Visitor {
      boolean foundOne = false;

      @Override
      public void visitArrayLoad(SSAArrayLoadInstruction instruction) {
        if (!instruction.typeIsPrimitive()) {
          foundOne = true;
        }
      }
    }
    ScanVisitor v = new ScanVisitor();
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
      if (v.foundOne) {
        return true;
      }
    }
    return false;
  }

  public static boolean hasObjectArrayStore(SSAInstruction[] statements) throws IllegalArgumentException {

    if (statements == null) {
      throw new IllegalArgumentException("statements == null");
    }
    class ScanVisitor extends Visitor {
      boolean foundOne = false;

      @Override
      public void visitArrayStore(SSAArrayStoreInstruction instruction) {
        if (!instruction.typeIsPrimitive()) {
          foundOne = true;
        }
      }
    }
    ScanVisitor v = new ScanVisitor();
    for (int i = 0; i < statements.length; i++) {
      SSAInstruction s = statements[i];
      if (s != null) {
        s.visit(v);
      }
      if (v.foundOne) {
        return true;
      }
    }
    return false;
  }
}