package driver;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PTAPattern {
	public enum ContextKind{INSENS,CALLSITE,OBJECT,TYPE,HYBRIDOBJ,HYBRIDTYPE}
	enum Approach{NONE,SUMMARY}
	
	static Map<String, ContextKind> contextKinds = new HashMap<>();
	static Map<String, Approach> approaches = new HashMap<>();
	static Pattern pattern;
	static{
		add(contextKinds, ContextKind.INSENS,"insensitive", "insens", "ci");
		add(contextKinds, ContextKind.CALLSITE,"callsite", "call", "c");
		add(contextKinds, ContextKind.OBJECT,"object", "obj", "o");
		add(contextKinds, ContextKind.TYPE,"type", "t");
		add(contextKinds, ContextKind.HYBRIDOBJ,"hybridobj","hybrid", "so");
		add(contextKinds, ContextKind.HYBRIDTYPE,"hybridtype", "st");
		add(approaches, Approach.SUMMARY,"summary", "sum");
		
		StringBuilder regexPattern=new StringBuilder();
		regexPattern.append("^");
		regexPattern.append("(("+String.join("|", approaches.keySet())+")-)?");
		regexPattern.append("(\\d*)");
		regexPattern.append("("+String.join("|", contextKinds.keySet())+")");
		regexPattern.append("(\\+?(\\d*)h(eap)?)?");
		regexPattern.append("$");
		
		pattern = Pattern.compile(regexPattern.toString());
	}
	
	static <T> void add(Map<String, T> approachesOrPTATypes, T name, String... aliases){
		for(String alias:aliases)
			approachesOrPTATypes.put(alias, name);
	}
	
	private ContextKind kind;
	private Approach approach;
	private int k, hk;
	
	public ContextKind getContextKind() {
		return  kind;
	}
	public boolean isInsensitive(){
		return ContextKind.INSENS==kind;
	}
	public boolean isCallSiteSens(){
		return ContextKind.CALLSITE==kind;
	}
	public boolean isObjSens(){
		return ContextKind.OBJECT==kind;
	}
	public boolean isTypeSens(){
		return ContextKind.TYPE==kind;
	}
	public boolean isHybridOBJ(){
		return ContextKind.HYBRIDOBJ==kind;
	}
	public boolean isHybridTYPE(){
		return ContextKind.HYBRIDTYPE==kind;
	}
	public boolean isSummary() {
		return Approach.SUMMARY==approach;
	}
	
	public int getContextDepth(){
		return k;
	}
	public int getHeapContextDepth(){
		return hk;
	}
	
	public PTAPattern(String ptacmd) {
		Matcher matcher = pattern.matcher(ptacmd);
		if (!matcher.find())
			throw new RuntimeException("Unsupported PTA: "+ptacmd+" !");
		String approachString = matcher.group(2);
		String kString = matcher.group(3);
		String typeString=matcher.group(4);
		String hkString = matcher.group(6);
		approach = approaches.getOrDefault(approachString,Approach.NONE);
		k = kString.equals("")?1:Integer.parseInt(kString);
		kind = typeString==null?ContextKind.INSENS:contextKinds.get(typeString);
		hk = hkString==null?-1:hkString.equals("")?1:Integer.parseInt(hkString);
		
		if(k==0)
			kind=ContextKind.INSENS;
		if(isInsensitive())
			k=0;
		if(isCallSiteSens()){
			if(hk<0)
				hk=k-1;
			else if(hk>k)
				throw new RuntimeException("Heap context depth cannot exceed method context depth!");
		}else if(isObjSens()||isTypeSens()||isHybridOBJ()||isHybridTYPE()){
			if(hk<0)
				hk=k-1;
			else if(hk>k||hk<k-1)
				throw new RuntimeException("Heap context depth can only be k or k-1 for this kind of analysis!");
		}else if(hk>=0)
			throw new RuntimeException("Cannot sepcify heap context depth for this kind of context!");
			
	}
	
	@Override
	public String toString() {
		StringBuilder stringBuilder = new StringBuilder();
		
		if(isSummary())
			stringBuilder.append("summary-");
		stringBuilder.append(k==0?"":k);
		switch (kind) {
		case INSENS:
			stringBuilder.append("insensitive");
			break;
		case CALLSITE:
			stringBuilder.append("callsite");
			break;
		case OBJECT:
			stringBuilder.append("object");
			break;
		case TYPE:
			stringBuilder.append("type");
			break;
		case HYBRIDOBJ:
			stringBuilder.append("hybridobj");
			break;
		case HYBRIDTYPE:
			stringBuilder.append("hybridtype");
			break;
		default:
			break;
		}
		if(hk>=0) {
			stringBuilder.append('+');
			stringBuilder.append(hk);
			stringBuilder.append("heap");
		}
		return stringBuilder.toString();
	}
}
