/*******************************************************************************
 * 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.ipa.callgraph;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.ShrikeCTMethod;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.graph.traverse.DFS;

/**
 * Collect basic call graph statistics
 */
public class CallGraphStats {

  public static class CGStats {

    private final int nNodes;

    private final int nEdges;

    private final int nMethods;

    private final int bytecodeBytes;

    private CGStats(int nodes, int edges, int methods, int bytecodeBytes) {
      super();
      nNodes = nodes;
      nEdges = edges;
      nMethods = methods;
      this.bytecodeBytes = bytecodeBytes;
    }

    public int getNNodes() {
      return nNodes;
    }

    public int getNEdges() {
      return nEdges;
    }

    public int getNMethods() {
      return nMethods;
    }

    public int getBytecodeBytes() {
      return bytecodeBytes;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + bytecodeBytes;
      result = prime * result + nEdges;
      result = prime * result + nMethods;
      result = prime * result + nNodes;
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      CGStats other = (CGStats) obj;
      if (bytecodeBytes != other.bytecodeBytes)
        return false;
      if (nEdges != other.nEdges)
        return false;
      if (nMethods != other.nMethods)
        return false;
      if (nNodes != other.nNodes)
        return false;
      return true;
    }

    @Override
    public String toString() {
      StringBuffer result = new StringBuffer();
      result.append("Call graph stats:");
      result.append("\n");
      result.append("  Nodes: " + nNodes);
      result.append("\n");
      result.append("  Edges: " + nEdges);
      result.append("\n");
      result.append("  Methods: " + nMethods);
      result.append("\n");
      result.append("  Bytecode Bytes: " + bytecodeBytes);
      result.append("\n");
      return result.toString();

    }
  }

  public static CGStats getCGStats(CallGraph cg) {
    if (cg == null) {
      throw new IllegalArgumentException("cg is null");
    }
    Set<CGNode> reachableNodes = DFS.getReachableNodes(cg, Collections.singleton(cg.getFakeRootNode()));
    int nNodes = 0;
    int nEdges = 0;
    for (Iterator<CGNode> it = reachableNodes.iterator(); it.hasNext();) {
      CGNode n = it.next();
      nNodes++;
      nEdges += cg.getSuccNodeCount(n);
    }
    return new CGStats(nNodes, nEdges, collectMethods(cg).size(), countBytecodeBytes(cg));
  }

  /**
   * @throws IllegalArgumentException if cg is null
   */
  public static String getStats(CallGraph cg) {
    return getCGStats(cg).toString();
  }

  /**
   * @param cg
   * @return the number of bytecode bytes
   * @throws IllegalArgumentException if cg is null
   */
  public static int countBytecodeBytes(CallGraph cg) {
    if (cg == null) {
      throw new IllegalArgumentException("cg is null");
    }
    int ret = 0;
    HashSet<IMethod> counted = HashSetFactory.make();
    for (Iterator<? extends CGNode> iter = cg.iterator(); iter.hasNext();) {
      CGNode node = iter.next();
      IMethod method = node.getMethod();
      if (counted.add(method)) {
        if (method instanceof ShrikeCTMethod) {
          byte[] bytecodes = ((ShrikeCTMethod) method).getBytecodes();
          if (bytecodes != null) {
            ret += bytecodes.length;
          }
        }
      }
    }
    return ret;
  }

  /**
   * Walk the call graph and return the set of MethodReferences that appear in the graph.
   * 
   * @param cg
   * @return a set of MethodReferences
   * @throws IllegalArgumentException if cg is null
   */
  public static Set<MethodReference> collectMethods(CallGraph cg) {
    if (cg == null) {
      throw new IllegalArgumentException("cg is null");
    }
    HashSet<MethodReference> result = HashSetFactory.make();
    for (Iterator it = cg.iterator(); it.hasNext();) {
      CGNode N = (CGNode) it.next();
      result.add(N.getMethod().getReference());
    }
    return result;
  }
}
