package edu.uky.ai.csp.kr;

import java.util.HashMap;

/**
 * <p>A solution to a constraint satisfaction problem is an assignment of
 * values to the problem's variables.</p>
 * 
 * <p>A solution is complete when every variable has exactly 1 value in
 * its domain.</p>
 * 
 * <p>A solution can be a partial solution, meaning that we have not yet
 * narrowed down the domains of all variables to a single value.</p>
 * 
 * <p>A solution is impossible if any of its assigned variables (those with
 * 1 value in the domain) violate a constraint in the problem or if any
 * variable has a domain with no values in it (i.e. no legal value).</p>
 * 
 * @author Stephen G. Ware
 */
public class Solution implements Cloneable {

	/** The previous solution, of which this is a refinement */
	private final Solution parent;
	
	/** The problem this solution is potentially a solution for */
	public final Problem problem;
	
	/** A mapping of variables to their domains */
	private final HashMap<Variable, Domain> domains = new HashMap<>();
	
	/** The number of descendant solutions generated from this solution */
	private int children = 0;
	
	/**
	 * Creates a new solution object whose state is identical to a given
	 * solution. The given solution will be recorded as the parent of the new
	 * solution object.
	 * 
	 * @param toClone the parent solution
	 */
	private Solution(Solution toClone) {
		this.parent = toClone;
		this.problem = toClone.problem;
		for(Variable variable : problem.variables)
			domains.put(variable, toClone.getDomain(variable).clone());
		Solution ancestor = parent;
		while(ancestor != null) {
			ancestor.children++;
			ancestor = ancestor.parent;
		}
	}
	
	/**
	 * Constructs a new solution object for a given problem.  The domains of
	 * all variables will be set to their domains in the initial state of the
	 * problem.
	 * 
	 * @param problem the problem that this object will potentially be a solution for
	 */
	public Solution(Problem problem) {
		this.parent = null;
		this.problem = problem;
		for(Variable variable : problem.variables)
			domains.put(variable, problem.getDomain(variable).clone());
	}
	
	@Override
	public String toString() {
		return problem.toString(this);
	}
	
	@Override
	public Solution clone() {
		return new Solution(this);
	}
	
	/**
	 * Returns the number of descendant solutions that were generated from this
	 * object.
	 * 
	 * @return the number of descendants
	 */
	public int countDescendants() {
		return children;
	}
	
	/**
	 * A solution is complete if every variable has a domain of size exactly 1
	 * and none of the problem's constraints are violated.
	 * 
	 * @return true if this solution is complete, false otherwise
	 */
	public boolean isComplete() {
		for(Domain domain : domains.values())
			if(domain.size() != 1)
				return false;
		return !isImpossible();
	}
	
	/**
	 * A solution is impossible if any variable has a domain of size 0 (i.e. no
	 * legal value) or if any of the problem's constraints have been violated.
	 * 
	 * @return true if this solution can never be complete
	 */
	public boolean isImpossible() {
		for(Constraint constraint : problem.constraints) {
			Domain left = getDomain(constraint.left);
			Domain right = getDomain(constraint.right);
			if(left.size() == 0 || right.size() == 0)
				return true;
			if(left.size() == 1 && right.size() == 1 && !test(constraint, left.getValue(), right.getValue()))
				return true;
		}
		return false;
	}
	
	/**
	 * The size of a solution is the sum of the sizes of all domains which have
	 * more than 1 value. This implies that size is 0 for a complete solution.
	 * The size of a solution is a simple representation of how much work
	 * remains to be done before this solution is complete.
	 * 
	 * @return the size of the solution
	 */
	public int size() {
		int size = 0;
		for(Domain domain : domains.values())
			if(domain.size() > 1)
				size += domain.size();
		return size;
	}
	
	/**
	 * Returns the domain (i.e. the set of possible values) for a given
	 * variable in this solution.
	 * 
	 * @param variable the variable
	 * @return the variable's domain
	 */
	public Domain getDomain(Variable variable) {
		Domain domain = domains.get(variable);
		if(domain == null)
			throw new IllegalArgumentException(variable + " not defined for this problem");
		return domain;
	}
	
	/**
	 * Given some constraint and two candidate values for the constraint's
	 * variables, this method checks if the constraint would be violated by
	 * those values.
	 * 
	 * @param constraint the constraint to check
	 * @param left the value for the constraint's left variable
	 * @param right the value for the constraint's right variable
	 * @return true if the values do not violate the constraint, false otherwise
	 */
	public boolean test(Constraint constraint, Object left, Object right) {
		return constraint.test(this, left, right);
	}
}
