package jayhorn.hornify;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Verify;

import jayhorn.solver.Prover;
import jayhorn.solver.ProverExpr;
import jayhorn.solver.ProverFun;
import jayhorn.solver.ProverTupleExpr;
import jayhorn.solver.ProverTupleType;
import jayhorn.solver.ProverType;
import soottocfg.cfg.type.BoolType;
import soottocfg.cfg.type.IntType;
import soottocfg.cfg.type.ReferenceType;
import soottocfg.cfg.type.Type;
import soottocfg.cfg.type.TypeType;
import soottocfg.cfg.variable.Variable;

public class HornHelper {

	public static final int NullValue = 0;
	
	private static HornHelper hh;
	
	public static void resetInstance() {
		hh = null;
	}

	public static HornHelper hh() {
		if (null == hh) {
			hh = new HornHelper();
		}
		return hh;
	}

	private HornHelper() {
	}

	/**
	 * Creates a ProverType from a Type.
	 * TODO: not fully implemented.
	 * 
	 * @param p
	 * @param t
	 * @return
	 */
	public ProverType getProverType(Prover p, Type t) {
		if (t == IntType.instance()) {
			return p.getIntType();
		}
		if (t == BoolType.instance()) {
			return p.getBooleanType();
		}
		if (t instanceof ReferenceType) {
			ReferenceType rt = (ReferenceType) t;

			final ProverType[] subTypes = new ProverType[rt.getElementTypeList().size()];
			for (int i = 0; i < rt.getElementTypeList().size(); i++) {
				subTypes[i] = getProverType(p, rt.getElementTypeList().get(i));
			}
			return p.getTupleType(subTypes);
		}
		if (t instanceof TypeType) {
			return p.getIntType();
		}

		throw new IllegalArgumentException("don't know what to do with " + t);
	}
	
	public ProverTupleExpr mkNullExpression(Prover p, ProverType[] types) {
		ProverExpr[] subExprs = new ProverExpr[types.length];
		for (int i = 0; i < types.length; i++) {
			if (types[i] instanceof jayhorn.solver.BoolType) {
				subExprs[i] = p.mkLiteral(false);
			} else if (types[i] instanceof jayhorn.solver.IntType) {
				subExprs[i] = p.mkLiteral(NullValue);
			} else if (types[i] instanceof ProverTupleType) {				
				subExprs[i] = mkNullExpression(p, ((ProverTupleType)types[i]).getSubTypes());
			} else {
				throw new RuntimeException("Not implemented " + types[i].getClass());
			}
		}
		return (ProverTupleExpr) p.mkTuple(subExprs);
	}

	public ProverFun genHornPredicate(Prover p, String name, List<Variable> sortedVars) {
		final List<ProverType> types = new LinkedList<ProverType>();
		for (Variable v : sortedVars) {
			types.add(getProverType(p, v.getType()));
		}
		return p.mkHornPredicate(name, types.toArray(new ProverType[types.size()]));
	}

	private int varNum = 0;

	public int newVarNum() {
		return varNum++;
	}

	public List<ProverExpr> findOrCreateProverVar(Prover p, List<Variable> cfgVars, Map<Variable, ProverExpr> varMap) {
		List<ProverExpr> res = new LinkedList<ProverExpr>();
		for (Variable v : cfgVars) {
			res.add(findOrCreateProverVar(p, v, varMap));
		}
		return res;
	}

	public ProverExpr findOrCreateProverVar(Prover p, Variable v, Map<Variable, ProverExpr> varMap) {
		if (!varMap.containsKey(v)) {
			varMap.put(v, createVariable(p, v));
		}
		return varMap.get(v);
	}

	public ProverExpr createVariable(Prover p, Variable v) {
		ProverType pt = getProverType(p, v.getType());
		if (pt instanceof ProverTupleType) {
			ProverTupleType ptt = (ProverTupleType) pt;
			ProverExpr[] subExprs = new ProverExpr[ptt.getArity()];
			Verify.verify(v.getType() instanceof ReferenceType);
			for (int i = 0; i < ptt.getArity(); i++) {
				subExprs[i] = createVariable(p,
						new Variable(v.getName() + "#" + i, ((ReferenceType) v.getType()).getElementTypeList().get(i)));
			}
			return p.mkTuple(subExprs);
		} else {
			return p.mkHornVariable(v.getName() + "_" + newVarNum(), pt);
		}
	}

	public ProverExpr createVariable(Prover p, String prefix, Type tp) {
		return p.mkHornVariable(prefix + newVarNum(), getProverType(p, tp));
	}

	public List<Variable> setToSortedList(Set<Variable> set) {
		List<Variable> res = new LinkedList<Variable>(set);
		if (!res.isEmpty()) {
			Collections.sort(res, new Comparator<Variable>() {
				@Override
				public int compare(final Variable object1, final Variable object2) {
					return object1.getName().compareTo(object2.getName());
				}
			});
		}
		return res;
	}
	
	
	public ProverExpr findOrCreateUniqueVariable(Prover p, Variable v, Map<Variable, ProverExpr> varMap, int uniqueNumber) {
		if (!varMap.containsKey(v)) {
			varMap.put(v, createVariable(p, v));
		}
		return varMap.get(v);
	}	
	public ProverExpr createUniqueVariable(Prover p, Variable v, int number) {
		ProverType pt = getProverType(p, v.getType());
		if (pt instanceof ProverTupleType) {
			ProverTupleType ptt = (ProverTupleType) pt;
			ProverExpr[] subExprs = new ProverExpr[ptt.getArity()];
			Verify.verify(v.getType() instanceof ReferenceType);
			subExprs[0] = p.mkLiteral(number);
			for (int i = 1; i < ptt.getArity(); i++) {
				subExprs[i] = createVariable(p,
						new Variable(v.getName() + "#" + i, ((ReferenceType) v.getType()).getElementTypeList().get(i)));
			}
			return p.mkTuple(subExprs);
		} else if (pt instanceof jayhorn.solver.IntType) {
			return p.mkLiteral(number);
		} else {
			return p.mkHornVariable(v.getName() + "_" + newVarNum(), pt);
		}
	}

}
