package tuffy.infer.ds;

import java.sql.Array;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;

import tuffy.util.Config;
import tuffy.util.ExceptionMan;
import tuffy.util.ProbMan;
/**
 * A ground clause.
 *
 */
public class GClause {
	
	/**
	 * ID of this GClause.
	 */
	public int id;
	
	/**
	 * Weight of this GClause.
	 */
	public double weight = 0;
	
	/**
	 * List of literals in this grounded clause. This
	 * is from $lits$ attribute in {@link Config#relClauses}
	 * table.
	 */
	public int[] lits = null;
	
	/**
	 * List of original clauses used to generate this 
	 * grounded clause. This is from $fcid$ attribute
	 * in the clause table when
	 * {@link Config#track_clause_provenance} set to true.
	 */
	public int[] fcid = null; // list of id to fo clauses collectivley generating this gclause
	
	/**
	 * Finer-grained clause origin.
	 * Similar to {@link GClause#fcid}, corresponding to
	 * attribute $ffcid$ in {@link Config#relClauses} table.
	 */
	public String[] ffcid = null;
	
	/**
	 * Number of satisfied literals in this GClause. This
	 * GClause is true iff. nsat > 0.
	 */
	public int nsat = 0;
	
	/**
	 * Whether this clause should be ignored while SampleSAT.
	 * Used by MC-SAT.
	 */
	public boolean dead = false;
	
	/**
	 * Whether this clause should be ignored after unit propagation
	 */
	public boolean ignoreAfterUnitPropagation = false;
	
	
	/**
	 * The largest fcid seen when parsing the database for all GClause.
	 */
	public static int maxFCID = -1;

	/**
	 * Return whether this clause is a positive clause. Here by
	 * positive it means this clause has a positive weight.
	 */
	public boolean isPositiveClause(){
		return weight >= 0;
	}
	
	public boolean isUnitClause(){
		return lits.length == 1;
	}
	
	/**
	 * Return whether this clause is a hard clause. Here by hard
	 * it means this clause has a weight larger than {@link Config#hard_weight},
	 * which means this clause must be satisfied in reasoning result.
	 */
	public boolean isHardClause(){
		return Math.abs(weight) >= Config.hard_weight;
	}

	/**
	 * 
	 * Return true if this clause is not set to ``dead''. This is used
	 * in MCSAT.
	 */
	public boolean selectMCSAT(){
		if(cost() > 0) return false;
		double r = ProbMan.nextDouble();
		return r < (1 - Math.exp(-Math.abs((weight)) * Config.mcsat_sample_para));
	}
	
	/**
	 * Return the cost for violating this GClause. For positive
	 * clause, if it is violated, cost equals to weight.
	 * For negative clause, if it is satisfied, cost equals to
	 * -weight. Otherwise, return 0.
	 */
	public double cost(){
		if(weight > 0 && nsat == 0){
			return weight;
		}
		if(weight < 0 && nsat > 0){
			return -weight;
		}
		return 0;
	}
	
	/**
	 * Returns +/-1 if this GClause contains this atom; 0 if not. 
	 * If -1, then the atom in this clause is with negative sense.
	 * @param atom
	 */
	public int linkType(int atom){
		for(int l : lits){
			if(l==atom) return 1;
			if(l==-atom) return -1;
		}
		return 0;
	}

	/**
	 * Replaces the ID of a particular atom, assuming that
	 * no twins exist.
	 * @param oldID
	 * @param newID
	 * @return 1 if oldID=>newID, -1 if -oldID=>-newID, 0 if no replacement
	 */
	public int replaceAtomID(int oldID, int newID){
		for(int k=0; k<lits.length; k++){
			if(lits[k] == oldID){
				lits[k] = newID;
				return 1;
			}else if(lits[k] == -oldID){
				lits[k] = -newID;
				return -1;
			}
		}
		return 0;
	}

	public boolean tautology = false; //TODO(ericgribkoff) revisit
	/**
	 * Initialize GClause from results of SQL. This involves set
	 * $cid$ to {@link GClause#id}, $weight$ {@link GClause#weight},
	 * $lits$ to {@link GClause#lits}, $fcid$ to {@link GClause#fcid}.
	 * @param rs the ResultSet for SQL. This sql is a sequential
	 * scan on table {@link Config#relClauses}.
	 * 
	 */
	public void parse(ResultSet rs){
		try {
			id = rs.getInt("cid");
			weight = rs.getDouble("weight");
						
			Array a = rs.getArray("lits");
			int[] atomIdsInClause = new int[Config.maxAtomId];
			Integer[] ilits = (Integer[]) a.getArray();
			lits = new int[ilits.length];
			for(int i=0; i<lits.length; i++){
				if (ilits[i] < Config.maxAtomId) {
					if (atomIdsInClause[Math.abs(ilits[i])] != 0) {
						if (Math.signum(ilits[i]) != atomIdsInClause[Math.abs(ilits[i])]) {
							lits[i] = ilits[i]; // just add to certify the tautology
							tautology = true;
							return;
						}
					} else {
						atomIdsInClause[Math.abs(ilits[i])] = (int) Math.signum(ilits[i]);
					}
				}
				lits[i] = ilits[i];
			}
			if(Config.track_clause_provenance){
				Array fc = rs.getArray("fcid");
				Integer[] ifc = (Integer[]) fc.getArray();
				ArrayList<Integer> lfcid = new ArrayList<Integer>();
				for(int i=0; i< ifc.length; i++){
					// ignoring soft evidence unit clauses
					if(ifc[i] != null && ifc[i] != 0){
						lfcid.add(ifc[i]);
						// ADDED BY CE ON NOV. 29
						if(Math.abs(ifc[i]) > GClause.maxFCID){
							GClause.maxFCID = Math.abs(ifc[i]);
						}
					}
				}
				fcid = new int[lfcid.size()];
				for(int i=0; i<fcid.length; i++){
					fcid[i] = lfcid.get(i);
				}
				
				fc = rs.getArray("ffcid");
				String[] sfc = (String[]) fc.getArray();
				ArrayList<String> lsfc = new ArrayList<String>();
				for(int i=0; i< sfc.length; i++){
					// ignoring soft evidence unit clauses
					if(sfc[i] != null && sfc[i] != "0"){
						lsfc.add(sfc[i]);
					}
				}
				ffcid = new String[lsfc.size()];
				for(int i=0; i<ffcid.length; i++){
					ffcid[i] = lsfc.get(i);
				}
			}
		} catch (Exception e) {
			ExceptionMan.handle(e);
		}
	}
	
	/**
	 * Returns the string form of this GClause, which is,
	 * 
	 * { <lit_1>, <lit_2>, ..., <lit_n> } | weight
	 * 
	 * where lit_i is the literal ID in {@link GClause#lits}.
	 */
	public String toPGString(){
		StringBuilder sb = new StringBuilder();
		sb.append("{");
		for(int i=0; i<lits.length; i++){
			sb.append(lits[i]);
			if(i < lits.length-1) sb.append(",");
		}
		sb.append("} | " + weight);
		return sb.toString();
	}
	
	/**
	 * Returns string form of this GClause. Compared
	 * with {@link GClause#toString()}, this function also only shows
	 * literals violating this clause.
	 * 
	 * @param atoms Map from Literal ID to GAtom object. This is used
	 * for obtaining the truth value of GAtom, which is inevitable
	 * for determining violation.
	 */
	public String toLongString(LinkedHashMap<Integer, GAtom> atoms){
		StringBuilder sb = new StringBuilder();
		sb.append("ViolatedGroundClause" + id + " [weight=" + weight +
				", satisfied=" + (nsat > 0) + "]\n");
		for(int l : lits){
			GAtom a = atoms.get(Math.abs(l));
			boolean vio = false;
			if((weight<0) == ((l > 0) == (a.truth))){
				vio = true;
			}
			if(vio){
				sb.append("\t");
				sb.append(a.truth ? " " : "!");
				sb.append(a.rep + "\n");
			}
		}
		return sb.toString();
	}
	
	/**
	 * Returns its human-friendly representation.
	 */
	public String toString(){
		StringBuilder sb = new StringBuilder();
		sb.append(weight + ": [");
		for(int l : lits){
			sb.append(l + ",");
		}
		sb.append("]");
		
		if(this.ffcid != null){
			sb.append("{");
			for(String s : this.ffcid){
				sb.append(" " + s);
			}
			sb.append("}");
		}
		
		return sb.toString();
	}

}