package edu.uky.ai.csp;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import edu.uky.ai.csp.kr.*;

/**
 * Provides the constraint propagation and search algorithms used to solve
 * constraint satisfaction problems.
 * 
 * @author Your Name
 */
public class Solver {

	/**
	 * <p>Given a partial solution, this method propagates the constraints of a
	 * problem to achieve 2-consistency.</p>
	 * 
	 * <p>This method should call {@link #propagate(Solution, Queue)} to do the
	 * actual work.  This method simply sets up the initial queue of
	 * constraints (which is all of the problem's binary constraints).<p>
	 * 
	 * @param solution a partial solution whose variable domains should be modified
	 * @return false if the problem is found to be unsolvable, true otherwise
	 */
	public static boolean propagate(Solution solution) {
		// When you are ready to work on this method, remove the line below.
		return true;
		// Start by initializing an empty queue. (Uncomment the line below.)
		// Queue<Constraint> queue = new LinkedList<Constraint>();
		// Add all of the problem's constraints to the queue.  You can find the
		// constraints in Solution#problem#constraints.  We only need to add
		// binary constraints, but all of the example problems in this exercise
		// use only binary constraints.
		
		// Now call the workhorse method, #propagate(Solution, Queue) and
		// return whatever value it returns.
		
	}
	
	/**
	 * This helper method is the workhorse for constraint propagation.  It
	 * reasons about the constraints in the queue to make the solution
	 * 2-consistent and (hopefully) narrow down the domains of some of the
	 * variables.  In some cases, this method may actually solve the problem or
	 * discover that it cannot be solved.
	 * 
	 * @param solution a partial solution which will (hopefully) be improved
	 * @param queue the initial queue of constraints to consider
	 * @return false if the problem is found to be unsolvable, true otherwise
	 */
	private static boolean propagate(Solution solution, Queue<Constraint> queue) {
		// Set up a basic loop which will run until the queue no longer has any
		// constraints left in it.
		
		// Remove a constraint from the queue using Queue#poll().
		Constraint constraint;
		// Call #revise(Solution, Constraint) using the constraint we are currently
		// considering.
		
		// If revise returned true, it means that the domain of the
		// constraint's left variable (Constraint#left) was narrowed down.
		// Check that variable's domain to see if it has been reduced to 0.
		// If so, return false to indicate that the problem can't be solved.
		
		// If revise returned true and the left variable's domain was not
		// reduced all the way to 0, we might be able to learn even more
		// information about other variables. Add all the constraints from the
		// problem that have the current constraint's left variable on the
		// right (Constraint#right) using Queue#offer(Constraint).
		
		// If the loop finished and the queue is empty, we are done.  Simply
		// return true.
		return true;
	}
	
	/**
	 * Given one specific constraint, this method will try to narrow down the
	 * domain of {@link edu.uky.ai.csp.kr.Constraint#left}.
	 * 
	 * @param solution the solution to be refined
	 * @param constraint the constraint whose left variable will (hopefully) be reduced
	 * @return true if the domain was reduced, false if it was unchanged
	 */
	private static boolean revise(Solution solution, Constraint constraint) {
		// This is the value we will return at the end of the function.  We
		// start by assuming the domain has not changed, and will set this
		// value to true if the domain does change.
		boolean revised = false;
		// Loop through all the possible values of the constraint's left
		// variable.  You can use Solution#getDomain(Variable) to get all the
		// possible values for a variable.
		
		// Given some value for the left variable, call
		// #canSatisfy(Solution, Constraint, Object) and pass to it that value.
		
		// canSatisfy tells us whether or not there exists a value for the
		// right variable which satisfies the constraint.  If no such value
		// exists, remove the current left value from the left variable's
		// domain using Domain#remove(Object).
		
		// If the variable's domain has changed, remember to set 'revised' to
		// true.
		
		// Return the variable that indicates whether or not the left
		// variable's domain was reduced.
		return revised;
	}
	
	/**
	 * Given some constraint and a value for its left variable, this method
	 * considers all the values for its right variable to determine if any
	 * value exists which satisfies the constraint.
	 * 
	 * @param solution the solution to be refined
	 * @param constraint the constraint whose left and right variables are being considered
	 * @param leftValue the value for the left variable
	 * @return true if a value for the right variable satisfies the constraint, false otherwise
	 */
	private static boolean canSatisfy(Solution solution, Constraint constraint, Object leftValue) {
		// Loop through all the values in the domain of Constraint#right.  You
		// can access these using Solution#getDomain(Variable).
		
		// Given values for the left and right variables, check if the
		// constraint is satisfied by those values using
		// Solution#test(Constraint, Object, Object).  If it is, return true.
		
		// If no value could be found that satisfied the constraint, return
		// false.
		return false;
	}
	
	/**
	 * Solves a constraint satisfaction problem via backtracking search.
	 * 
	 * @param solution a partial solution to refine
	 * @return a complete solution, or null if the problem cannot be solved
	 */
	public static Solution solve(Solution solution) {
		// If the solution is complete, return it.
		if(solution.isComplete())
			return solution;
		// Choose a variable to which a value will be assigned.
		Variable variable = chooseVariable(solution);
		// This list will hold the current solution's children states.
		List<Solution> children = new ArrayList<>();
		// Generate all children states by assigning each of the variable's
		// potential values.
		for(Object value : solution.getDomain(variable)) {
			Solution child = solution.clone();
			child.getDomain(variable).set(value);
			// Infer any new information that is available now that the
			// variable has been assigned. Unless the child solution is
			// unsolvable, add it to the list of children.
			if(infer(child, variable))
				children.add(child);
		}
		// Sort the children in the order they should be searched.
		orderChildren(children);
		// Recursively (i.e. depth first) search each child.  If one leads to
		// a solution, return it.
		for(Solution child : children) {
			Solution result = solve(child);
			if(result != null)
				return result;
		}
		// If no solution is found, return null.
		return null;
	}
	
	/**
	 * This method propagates new information through a solution after a
	 * variable has been assigned a value during the search for a solution.
	 * It calls {@link #propagate(Solution, Queue)} to do the actual work,
	 * but must first initialize the queue of constraints.
	 * 
	 * @param solution the solution to refine
	 * @param variable the variable that was just assigned a value
	 * @return false if the problem is found to be unsolvable, true otherwise
	 */
	private static boolean infer(Solution solution, Variable variable) {
		// When you are ready to work on this method, remove the line below.
		return !solution.isImpossible();
		// First, initialize an empty queue of constraints. (Uncomment the line
		// below.)
		// Queue<Constraint> queue = new LinkedList<>();
		// We don't need to consider all the constraints in the problem, only
		// those involving the newly set variable.  Loop through all the
		// problem's constraints (Solution#problem#constraints) and add only
		// those whose left or right variable is the variable in question to
		// the queue.
		
		// Now call the #propagate(Solution, Queue) method developed earlier.
		
	}
	
	/**
	 * Chooses the next variable to set during the solution search process.
	 * 
	 * @param solution the solution to be refined
	 * @return the variable that should be set next
	 */
	private static Variable chooseVariable(Solution solution) {
		// This variable will be the one we return at the end of the method.
		Variable best = null;
		// Consider all the variable in the problem.
		for(Variable variable : solution.problem.variables) {
			// Get the variable's domain.
			Domain domain = solution.getDomain(variable);
			// Only consider variables which are not already set.
			if(domain.size() > 1) {
				// Currently, this method simply chooses the first variable it
				// finds which has not yet been set. When you are ready to work
				// on this method, delete the two lines below.
				best = variable;
				break;
				// We want to choose the most constrained variable (i.e. the
				// one with the fewest values in its domain.

			}
		}
		// Return the best variable.
		return best;
	}
	
	/**
	 * Given a list of next states to consider, this method orders them to
	 * search the most promising ones first.
	 * 
	 * @param children the list of next states to consider
	 */
	private static void orderChildren(List<Solution> children) {
		// Sort the list using a java.util.Comparator.
		children.sort(new Comparator<Solution>(){
			@Override
			public int compare(Solution child1, Solution child2) {
				// When you are ready to work on this method, delete the line
				// below.
				return 0;
				// The compare method should return:
				// * -1 if child1 should come before child2
				// * 0 if their order does not matter
				// * 1 if child2 should come before child1
				// Read the documentation for Solution#size() and use that
				// method to order the list of children from least to most
				// constrained.
				
			}
		});
	}
}
