/******************************************************************************
 * Copyright (c) 2009 - 2015 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.memsat.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.ibm.wala.cast.java.ipa.callgraph.AstJavaSSAPropagationCallGraphBuilder.EnclosingObjectReferenceKey;
import com.ibm.wala.cast.loader.AstMethod;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.classLoader.IField;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.ShrikeBTMethod;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.propagation.ArrayContentsKey;
import com.ibm.wala.ipa.callgraph.propagation.InstanceFieldKey;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
import com.ibm.wala.ipa.callgraph.propagation.StaticFieldKey;
import com.ibm.wala.ipa.callgraph.propagation.rta.CallSite;
import com.ibm.wala.ipa.modref.ArrayLengthKey;
import com.ibm.wala.memsat.frontEnd.InlinedInstruction;
import com.ibm.wala.shrikeCT.InvalidClassFileException;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.graph.Graph;
import kodkod.ast.Node;
/**
 * A set of utility functions for string manipulation and pretty printing of Kodkod nodes,
 * IRs, etc.
 * 
 * @author Emina Torlak
 */
public final class Strings {
	/**
	 * Returns a pretty-printed string representation of the 
	 * given nodes, with each line offset with at least the given
	 * number of whitespaces.  The display parameter determines how 
	 * the mapped nodes are displayed; that is, a descendant d of the
	 * given node is displayed as display.get(d.toString()) if 
	 * display.containsKey(d.toString()) is true.
	 * @requires 0 <= offset < line
	 * @return a pretty-printed string representation of the 
	 * given nodes
	 */
	public static String prettyPrint(Collection<Node> nodes, int offset, int line, Map<String,String> display) { 
		return PrettyPrinter.print(nodes, offset, 80, display);
	}
	/**
	 * Returns a pretty-printed string representation of the 
	 * given node, with each line offset with at least the given
	 * number of whitespaces.  The display parameter determines how 
	 * the mapped nodes are displayed; that is, a descendant d of the
	 * given node is displayed as display.get(d.toString()) if 
	 * display.containsKey(d.toString()) is true.
	 * @requires 0 <= offset < line
	 * @return a pretty-printed string representation of the 
	 * given node
	 */
	public static String prettyPrint(Node node, int offset, Map<String, String> display) { 
		return prettyPrint(node, offset, 80, display);
	}
	
	/**
	 * Returns a pretty-printed string representation of the 
	 * given node, with each line offset with at least the given
	 * number of whitespaces.  The line parameter determines the
	 * length of each pretty-printed line, including the offset.
	 * The display parameter determines how 
	 * the mapped nodes are displayed; that is, a descendant d of the
	 * given node is displayed as display.get(d.toString()) if 
	 * display.containsKey(d.toString()) is true.
	 * @requires 0 <= offset < line
	 * @return a pretty-printed string representation of the 
	 * given node
	 */
	public static String prettyPrint(Node node, int offset, int line, final Map<String, String> display) { 
		return prettyPrint(Collections.singleton(node), offset, line, display);
	}
	
	/**
	 * Returns a pretty-printed string representation of the 
	 * given node, with each line offset with at least the given
	 * number of whitespaces.  The line parameter determines the
	 * length of each pretty-printed line, including the offset.
	 * @requires 0 <= offset < line
	 * @return a pretty-printed string representation of the 
	 * given node
	 */
	@SuppressWarnings("unchecked")
	public static String prettyPrint(Node node, int offset, int line) { 
		assert offset >= 0 && offset < line && line > 0;
		return prettyPrint(node, offset, line, Collections.EMPTY_MAP);
	}
	
	/**
	 * Returns a pretty-printed string representation of the 
	 * given node, with each line offset with at least the given
	 * number of whitespaces.  
	 * @requires 0 <= offset < 80
	 * @return a pretty-printed string representation of the 
	 * given node
	 */
	public static String prettyPrint(Node node, int offset) { 
		return prettyPrint(node,offset,80);
	}
	
	/**
	 * Returns a string that consists of the given number of repetitions
	 * of the specified string.
	 * @return str^reps
	 */
	public static String repeat(String str, int reps) {
		final StringBuffer result = new StringBuffer();
		for(int i = 0; i < reps; i++) { 
			result.append(str);
		}
		return result.toString();
	}
	/**
	 * Returns the given string, with all new lines replaced with new lines indented 
	 * by the given number of spaces.
	 * @return given string, with all new lines replaced with new lines indented 
	 * by the given number of spaces.
	 */
	public static String indent(String str, int offset) { 
		assert offset >= 0;
		final String indent = repeat(" ", offset);
		return indent + str.replaceAll("\\n", "\n"+indent);
	}
	
	/**
	 * Returns a pretty-print String representation
	 * of the given graph.
	 * @return pretty-print String representation
	 * of the given graph
	 */
	public static String prettyPrint(Graph<?> graph) { 
		return prettyPrint(graph,0);
	}
	
	/**
	 * Returns a pretty-print String representation
	 * of the given graph, with each new line starting
	 * indented at least the given number of spaces.
	 * @return pretty-print String representation
	 * of the given graph
	 */
	public static <T> String prettyPrint(Graph<T> graph, int offset) { 
		assert offset>=0;
		final StringBuffer result = new StringBuffer();
		final String indent = repeat(" ", offset);
		for(T o : graph) {
			result.append("\n");
			result.append(indent);
			result.append(o);
			for(Iterator<?> itr = graph.getSuccNodes(o); itr.hasNext(); ) { 
				result.append("\n" + indent + " --> " + itr.next());
			}
			result.append(",\n");
		}
		result.delete(result.length()-2, result.length());
		return result.toString();
	}
	/**
	 * Returns a pretty-print String representation
	 * of the given IR, with each new line starting
	 * indented at least the given number of spaces.
	 * @return pretty-print String representation
	 * of the given IR
	 */
	public static String prettyPrint(IR ir, int offset) { 
		return indent(ir.toString(), offset);
		/*
	    final StringBuffer result = new StringBuffer();
	    result.append("\n"+indent+"CFG:\n");
	    final SSACFG cfg = ir.getControlFlowGraph();
	    for (int i = 0; i <= cfg.getNumber(cfg.exit()); i++) {
	        BasicBlock bb = cfg.getNode(i);
	        result.append(indent+"BB").append(i).append("[").append(bb.getFirstInstructionIndex()).append("..").append(bb.getLastInstructionIndex())
	            .append("]\n");
	        Iterator<ISSABasicBlock> succNodes = cfg.getSuccNodes(bb);
	        while (succNodes.hasNext()) {
	          result.append(indent+"    -> BB").append(((BasicBlock) succNodes.next()).getNumber()).append("\n");
	        }
	      }
	    result.append(indent+"Instructions:\n");
	    for (int i = 0; i <= cfg.getMaxNumber(); i++) {
	      BasicBlock bb = cfg.getNode(i);
	      int start = bb.getFirstInstructionIndex();
	      int end = bb.getLastInstructionIndex();
	      result.append(indent+"BB").append(bb.getNumber());
	      if (bb instanceof ExceptionHandlerBasicBlock) {
	        result.append(indent+"<Handler>");
	      }
	      result.append("\n");
	      final SymbolTable symbolTable = ir.getSymbolTable();
	      for (Iterator<SSAPhiInstruction> it = bb.iteratePhis(); it.hasNext();) {
	        SSAPhiInstruction phi = it.next();
	        if (phi != null) {
	          result.append(indent+"           " + phi.toString(symbolTable)).append("\n");
	        }
	      }
	      if (bb instanceof ExceptionHandlerBasicBlock) {
	        ExceptionHandlerBasicBlock ebb = (ExceptionHandlerBasicBlock) bb;
	        SSAGetCaughtExceptionInstruction s = ebb.getCatchInstruction();
	        if (s != null) {
	          result.append(indent+"           " + s.toString(symbolTable)).append("\n");
	        } else {
	          result.append(indent+"           " + " No catch instruction. Unreachable?\n");
	        }
	      }
	      final SSAInstruction[] instructions = ir.getInstructions();
	      for (int j = start; j <= end; j++) {
	        if (instructions[j] != null) {
	          StringBuffer x = new StringBuffer(indent+j + "   " + instructions[j].toString(symbolTable));
	          StringStuff.padWithSpaces(x, 45);
	          result.append(indent+x);
	          result.append("\n");
	        }
	      }
	      for (Iterator<SSAPiInstruction> it = bb.iteratePis(); it.hasNext();) {
	        SSAPiInstruction pi = it.next();
	        if (pi != null) {
	          result.append(indent+"           " + pi.toString(symbolTable)).append("\n");
	        }
	      }
	    }
	    return result.toString();
	    */
	}
	
	/**
	 * Returns a pretty-print String representation
	 * of the given IR.
	 * @return pretty-print String representation
	 * of the given IR
	 */
	public static String prettyPrint(IR ir) { return prettyPrint(ir,0); }
	
	/**
	 * Returns a pretty-print String representation
	 * of the given collection.
	 * @return pretty-print String representation
	 * of the given collection
	 */
	public static String prettyPrint(Collection<?> c) { 
		return prettyPrint(c,0);
	}
	
	/**
	 * Returns a pretty-print String representation
	 * of the given collection, with each new line starting
	 * indented at least the given number of spaces.
	 * @return pretty-print String representation
	 * of the given collection
	 */
	public static String prettyPrint(Collection<?> c, int offset) { 
		assert offset>=0;
		final StringBuffer result = new StringBuffer();
		final String indent = repeat(" ", offset);
		for(Object o : c) { 
			result.append(indent);
			result.append(o);
			result.append("\n");
		}
		return result.toString();
	}
	/**
	 * Returns a String representation of the position in the source of the given method corresponding
	 * to the instruction at the specified index, or the empty string if the line is unknown.
	 * @return a String representation of the position in the source of the given method corresponding
	 * to the instruction at the specified index, or the empty string if the line is unknown.
	 */
	public static final String line(IMethod method, int instructionIndex) { 
		if (instructionIndex>=0) {
			if (method instanceof ShrikeBTMethod) { 
				try {
					return String.valueOf(method.getLineNumber(((ShrikeBTMethod)method).getBytecodeIndex(instructionIndex)));
				} catch (InvalidClassFileException e) {  } // ignore
			} else if (method instanceof AstMethod) { 
				final Position pos = ((AstMethod)method).getSourcePosition(instructionIndex);
				if (pos!=null)
					return String.valueOf(pos.getFirstLine());
			}
		}
		return "";
	}
	
	/**
	 * Returns a map from each InlinedInstruction in the given set to a unique name.
	 * The names are constructed from the names of the concrete instructions wrapped
	 * in each inlined instruction. Short type names are used whenever possible.
	 * @return a map from each InlinedInstruction in the given set to a unique name.
	 */
	public static Map<InlinedInstruction, String> instructionNames(Set<InlinedInstruction> insts) { 
		final Map<CGNode,String> methodNames = nodeNames(Programs.relevantMethods(insts));
		final Map<String, List<InlinedInstruction>> name2Inst = new LinkedHashMap<String, List<InlinedInstruction>>();
		final Map<InlinedInstruction, String> inst2Name = new LinkedHashMap<InlinedInstruction, String>();
		
		for(InlinedInstruction inst : insts) { 
			final String m = methodNames.get(inst.cgNode());
			final String infix;
			final int idx = inst.instructionIndex();
			if (idx==Integer.MIN_VALUE) { 
				infix = "start";
			} else if (idx==Integer.MAX_VALUE) { 
				infix = "end";
			} else { 
				final String cname = "";//inst.instruction().getClass().getSimpleName().replaceAll("SSA", "").replaceAll("Instruction", "");
				infix = cname+idx;
			} 
			final String name = m + "[" + infix + "]"; // m+"_"+infix;
			List<InlinedInstruction> named = name2Inst.get(name);
			if (named==null) { 
				named = new ArrayList<InlinedInstruction>(3);
				name2Inst.put(name, named);
			}
			named.add(inst);
		}
		
		for(Map.Entry<String, List<InlinedInstruction>> entry : name2Inst.entrySet()) { 
			final List<InlinedInstruction> named = entry.getValue();
			if (named.size()==1) { 
				inst2Name.put(named.get(0), entry.getKey());
			} else {
				for(InlinedInstruction inst : named) { 
					final StringBuilder b = new StringBuilder();
					assert !inst.callStack().empty();
					final Iterator<CallSite> itr = inst.callStack().iterator();
					b.append(methodNames.get(itr.next().getNode()));
					while(itr.hasNext()) { 
						b.append("_" + methodNames.get(itr.next().getNode()));
					}
					b.append("_" + entry.getKey());
					inst2Name.put(inst, b.toString());
				}
			}
		}
		
		return inst2Name;
	}
	
	/**
	 * Returns a map from each CGNode in the given set to a unique name derived
	 * from the signature of that node. Short type names are used whenever possible.
	 * @return a map from each CGNode in the given set to a unique name derived
	 * from the signature of that node. 
	 */
	public static Map<CGNode, String> nodeNames(Set<CGNode> nodes) { 
		final Map<String, List<CGNode>> name2Node = new LinkedHashMap<String, List<CGNode>>();
		final Map<CGNode,String> node2Name = new LinkedHashMap<CGNode, String>();
		
		for(CGNode ref : nodes) { 
			final String name = ref.getMethod().getName().toString();
			List<CGNode> named = name2Node.get(name);
			if (named==null) { 
				named = new ArrayList<CGNode>(3);
				name2Node.put(name, named);
			}
			named.add(ref);
		}
		for(Map.Entry<String,List<CGNode>> entry: name2Node.entrySet()) { 
			final List<CGNode> named = entry.getValue();
			if (named.size()==1) { 
				node2Name.put(named.get(0), entry.getKey());
			} else {
				for(CGNode ref : named) { 
					node2Name.put(ref, ref.getMethod().getSignature());
				}
			}
		}
		return node2Name;
	}
	
	/**
	 * Returns a map from each InstanceKey in the given set to a unique name.
	 * The names are constructed from the names of the concrete types represented
	 * by each instance key.  If there is more than one instance key for the same 
	 * type, unique suffixes are appended to make the names unique.  Short type names
	 * are used whenever possible.
	 * @return a map from each InstanceKey in the given set to a unique name.
	 */
	public static Map<InstanceKey, String> instanceNames(Set<InstanceKey> instances) { 
		final Map<TypeName, List<InstanceKey>> nameToKey = new LinkedHashMap<TypeName,List<InstanceKey>>();
		final Map<String, Boolean> uniqueShort = new HashMap<String,Boolean>();
		final Map<InstanceKey, String> keyToName = new LinkedHashMap<InstanceKey,String>();
		
		for(InstanceKey key : instances) {
			final TypeName fullName = key.getConcreteType().getName();
			List<InstanceKey> named = nameToKey.get(fullName);
			if (named==null) {
				named = new ArrayList<InstanceKey>(3);
				nameToKey.put(fullName, named);
			}
			named.add(key);
		}
		
		for(TypeName fullName : nameToKey.keySet()) { 
			final String shortName = fullName.getClassName().toString();
			final Boolean unique = uniqueShort.get(shortName);
			if (unique==null)	{ uniqueShort.put(shortName, Boolean.TRUE); }
			else 				{ uniqueShort.put(shortName, Boolean.FALSE); }
		}
		
		for(Map.Entry<TypeName, List<InstanceKey>> entry : nameToKey.entrySet()) {
			final TypeName fullName = entry.getKey();
			final List<InstanceKey> named = entry.getValue();
			final String shortName = fullName.getClassName().toString();
			final String name = uniqueShort.get(shortName) ? shortName : fullName.toString();
			final int size = named.size();
			if (size==1) { 
				keyToName.put(named.get(0), name);
			} else {
				for(int i = 0; i < size; i++) {
					keyToName.put(named.get(i), name + i);
				}
			}
		}
		assert keyToName.size() == instances.size();
		return keyToName;
	}
	
	/**
	 * Returns a map from each IField in the given set to a unique name.
	 * The names are constructed from the names of the  fields represented
	 * by IField.  Short field names are used whenever possible.
	 * @return a map from each IField in the given set to a unique name.
	 */
	public static Map<IField, String> fieldNames(Set<IField> fields) { 
		final Map<String, List<IField>> name2Field = new LinkedHashMap<String, List<IField>>();
		final Map<IField,String> field2Name = new LinkedHashMap<IField, String>();
		


Answer: 		for(IField field : fields) {